blob: 441e110cd64cb81ede8d8c3a3717f553c9711ffc [file] [log] [blame]
Colin Cross014489c2020-06-02 20:09:13 -07001// Copyright 2020 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package java
16
17import (
18 "fmt"
19 "sort"
20
21 "android/soong/android"
22)
23
24type LintProperties struct {
25 // Controls for running Android Lint on the module.
26 Lint struct {
27
28 // If true, run Android Lint on the module. Defaults to true.
29 Enabled *bool
30
31 // Flags to pass to the Android Lint tool.
32 Flags []string
33
34 // Checks that should be treated as fatal.
35 Fatal_checks []string
36
37 // Checks that should be treated as errors.
38 Error_checks []string
39
40 // Checks that should be treated as warnings.
41 Warning_checks []string
42
43 // Checks that should be skipped.
44 Disabled_checks []string
45 }
46}
47
48type linter struct {
49 name string
50 manifest android.Path
51 mergedManifest android.Path
52 srcs android.Paths
53 srcJars android.Paths
54 resources android.Paths
55 classpath android.Paths
56 classes android.Path
57 extraLintCheckJars android.Paths
58 test bool
59 library bool
60 minSdkVersion string
61 targetSdkVersion string
62 compileSdkVersion string
63 javaLanguageLevel string
64 kotlinLanguageLevel string
65 outputs lintOutputs
66 properties LintProperties
67}
68
69type lintOutputs struct {
70 html android.ModuleOutPath
71 text android.ModuleOutPath
72 xml android.ModuleOutPath
73}
74
75func (l *linter) enabled() bool {
76 return BoolDefault(l.properties.Lint.Enabled, true)
77}
78
79func (l *linter) writeLintProjectXML(ctx android.ModuleContext,
80 rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir android.WritablePath, deps android.Paths) {
81
82 var resourcesList android.WritablePath
83 if len(l.resources) > 0 {
84 // The list of resources may be too long to put on the command line, but
85 // we can't use the rsp file because it is already being used for srcs.
86 // Insert a second rule to write out the list of resources to a file.
87 resourcesList = android.PathForModuleOut(ctx, "lint", "resources.list")
88 resListRule := android.NewRuleBuilder()
89 resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList)
90 resListRule.Build(pctx, ctx, "lint_resources_list", "lint resources list")
91 deps = append(deps, l.resources...)
92 }
93
94 projectXMLPath = android.PathForModuleOut(ctx, "lint", "project.xml")
95 // Lint looks for a lint.xml file next to the project.xml file, give it one.
96 configXMLPath = android.PathForModuleOut(ctx, "lint", "lint.xml")
97 cacheDir = android.PathForModuleOut(ctx, "lint", "cache")
98
99 srcJarDir := android.PathForModuleOut(ctx, "lint-srcjars")
100 srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
101
102 cmd := rule.Command().
103 BuiltTool(ctx, "lint-project-xml").
104 FlagWithOutput("--project_out ", projectXMLPath).
105 FlagWithOutput("--config_out ", configXMLPath).
106 FlagWithArg("--name ", ctx.ModuleName())
107
108 if l.library {
109 cmd.Flag("--library")
110 }
111 if l.test {
112 cmd.Flag("--test")
113 }
114 if l.manifest != nil {
115 deps = append(deps, l.manifest)
116 cmd.FlagWithArg("--manifest ", l.manifest.String())
117 }
118 if l.mergedManifest != nil {
119 deps = append(deps, l.mergedManifest)
120 cmd.FlagWithArg("--merged_manifest ", l.mergedManifest.String())
121 }
122
123 // TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
124 // lint separately.
125 cmd.FlagWithRspFileInputList("--srcs ", l.srcs)
126 deps = append(deps, l.srcs...)
127
128 cmd.FlagWithInput("--generated_srcs ", srcJarList)
129 deps = append(deps, l.srcJars...)
130
131 if resourcesList != nil {
132 cmd.FlagWithInput("--resources ", resourcesList)
133 }
134
135 if l.classes != nil {
136 deps = append(deps, l.classes)
137 cmd.FlagWithArg("--classes ", l.classes.String())
138 }
139
140 cmd.FlagForEachArg("--classpath ", l.classpath.Strings())
141 deps = append(deps, l.classpath...)
142
143 cmd.FlagForEachArg("--extra_checks_jar ", l.extraLintCheckJars.Strings())
144 deps = append(deps, l.extraLintCheckJars...)
145
146 // The cache tag in project.xml is relative to the project.xml file.
147 cmd.FlagWithArg("--cache_dir ", "cache")
148
149 cmd.FlagWithInput("@",
150 android.PathForSource(ctx, "build/soong/java/lint_defaults.txt"))
151
152 cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks)
153 cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks)
154 cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
155 cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
156
157 return projectXMLPath, configXMLPath, cacheDir, deps
158}
159
160// generateManifest adds a command to the rule to write a dummy manifest cat contains the
161// minSdkVersion and targetSdkVersion for modules (like java_library) that don't have a manifest.
162func (l *linter) generateManifest(ctx android.ModuleContext, rule *android.RuleBuilder) android.Path {
163 manifestPath := android.PathForModuleOut(ctx, "lint", "AndroidManifest.xml")
164
165 rule.Command().Text("(").
166 Text(`echo "<?xml version='1.0' encoding='utf-8'?>" &&`).
167 Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`).
168 Text(`echo " android:versionCode='1' android:versionName='1' >" &&`).
169 Textf(`echo " <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`,
170 l.minSdkVersion, l.targetSdkVersion).
171 Text(`echo "</manifest>"`).
172 Text(") >").Output(manifestPath)
173
174 return manifestPath
175}
176
177func (l *linter) lint(ctx android.ModuleContext) {
178 if !l.enabled() {
179 return
180 }
181
182 rule := android.NewRuleBuilder()
183
184 if l.manifest == nil {
185 manifest := l.generateManifest(ctx, rule)
186 l.manifest = manifest
187 }
188
189 projectXML, lintXML, cacheDir, deps := l.writeLintProjectXML(ctx, rule)
190
191 l.outputs.html = android.PathForModuleOut(ctx, "lint-report.html")
192 l.outputs.text = android.PathForModuleOut(ctx, "lint-report.txt")
193 l.outputs.xml = android.PathForModuleOut(ctx, "lint-report.xml")
194
195 rule.Command().Text("rm -rf").Flag(cacheDir.String())
196 rule.Command().Text("mkdir -p").Flag(cacheDir.String())
197
198 rule.Command().
199 Text("(").
200 Flag("JAVA_OPTS=-Xmx2048m").
201 FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath(ctx)).
202 FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXmlPath(ctx)).
203 Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")).
204 Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")).
205 Flag("--quiet").
206 FlagWithInput("--project ", projectXML).
207 FlagWithInput("--config ", lintXML).
208 FlagWithOutput("--html ", l.outputs.html).
209 FlagWithOutput("--text ", l.outputs.text).
210 FlagWithOutput("--xml ", l.outputs.xml).
211 FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
212 FlagWithArg("--java-language-level ", l.javaLanguageLevel).
213 FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
214 FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
215 Flag("--exitcode").
216 Flags(l.properties.Lint.Flags).
217 Implicits(deps).
218 Text("|| (").Text("cat").Input(l.outputs.text).Text("; exit 7)").
219 Text(")")
220
221 rule.Command().Text("rm -rf").Flag(cacheDir.String())
222
223 rule.Build(pctx, ctx, "lint", "lint")
224}
225
226func (l *linter) lintOutputs() *lintOutputs {
227 return &l.outputs
228}
229
230type lintOutputIntf interface {
231 lintOutputs() *lintOutputs
232}
233
234var _ lintOutputIntf = (*linter)(nil)
235
236type lintSingleton struct {
237 htmlZip android.WritablePath
238 textZip android.WritablePath
239 xmlZip android.WritablePath
240}
241
242func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) {
243 l.generateLintReportZips(ctx)
244 l.copyLintDependencies(ctx)
245}
246
247func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) {
248 if ctx.Config().UnbundledBuild() {
249 return
250 }
251
252 var frameworkDocStubs android.Module
253 ctx.VisitAllModules(func(m android.Module) {
254 if ctx.ModuleName(m) == "framework-doc-stubs" {
255 if frameworkDocStubs == nil {
256 frameworkDocStubs = m
257 } else {
258 ctx.Errorf("lint: multiple framework-doc-stubs modules found: %s and %s",
259 ctx.ModuleSubDir(m), ctx.ModuleSubDir(frameworkDocStubs))
260 }
261 }
262 })
263
264 if frameworkDocStubs == nil {
265 if !ctx.Config().AllowMissingDependencies() {
266 ctx.Errorf("lint: missing framework-doc-stubs")
267 }
268 return
269 }
270
271 ctx.Build(pctx, android.BuildParams{
272 Rule: android.Cp,
273 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
274 Output: annotationsZipPath(ctx),
275 })
276
277 ctx.Build(pctx, android.BuildParams{
278 Rule: android.Cp,
279 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
280 Output: apiVersionsXmlPath(ctx),
281 })
282}
283
284func annotationsZipPath(ctx android.PathContext) android.WritablePath {
285 return android.PathForOutput(ctx, "lint", "annotations.zip")
286}
287
288func apiVersionsXmlPath(ctx android.PathContext) android.WritablePath {
289 return android.PathForOutput(ctx, "lint", "api_versions.xml")
290}
291
292func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) {
293 var outputs []*lintOutputs
294 var dirs []string
295 ctx.VisitAllModules(func(m android.Module) {
296 if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() {
297 return
298 }
299
300 if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() && apex.IsForPlatform() {
301 // There are stray platform variants of modules in apexes that are not available for
302 // the platform, and they sometimes can't be built. Don't depend on them.
303 return
304 }
305
306 if l, ok := m.(lintOutputIntf); ok {
307 outputs = append(outputs, l.lintOutputs())
308 }
309 })
310
311 dirs = android.SortedUniqueStrings(dirs)
312
313 zip := func(outputPath android.WritablePath, get func(*lintOutputs) android.Path) {
314 var paths android.Paths
315
316 for _, output := range outputs {
317 paths = append(paths, get(output))
318 }
319
320 sort.Slice(paths, func(i, j int) bool {
321 return paths[i].String() < paths[j].String()
322 })
323
324 rule := android.NewRuleBuilder()
325
326 rule.Command().BuiltTool(ctx, "soong_zip").
327 FlagWithOutput("-o ", outputPath).
328 FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
329 FlagWithRspFileInputList("-l ", paths)
330
331 rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
332 }
333
334 l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
335 zip(l.htmlZip, func(l *lintOutputs) android.Path { return l.html })
336
337 l.textZip = android.PathForOutput(ctx, "lint-report-text.zip")
338 zip(l.textZip, func(l *lintOutputs) android.Path { return l.text })
339
340 l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip")
341 zip(l.xmlZip, func(l *lintOutputs) android.Path { return l.xml })
342
343 ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip)
344}
345
346func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
347 ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip)
348}
349
350var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
351
352func init() {
353 android.RegisterSingletonType("lint",
354 func() android.Singleton { return &lintSingleton{} })
355}