blob: 20a7dc49f02c47ebda4807ea2f62ec37e3f85307 [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
Colin Cross92e4b462020-06-18 15:56:48 -070045
46 // Modules that provide extra lint checks
47 Extra_check_modules []string
Colin Cross014489c2020-06-02 20:09:13 -070048 }
49}
50
51type linter struct {
52 name string
53 manifest android.Path
54 mergedManifest android.Path
55 srcs android.Paths
56 srcJars android.Paths
57 resources android.Paths
58 classpath android.Paths
59 classes android.Path
60 extraLintCheckJars android.Paths
61 test bool
62 library bool
63 minSdkVersion string
64 targetSdkVersion string
65 compileSdkVersion string
66 javaLanguageLevel string
67 kotlinLanguageLevel string
68 outputs lintOutputs
69 properties LintProperties
Colin Crossc0efd1d2020-07-03 11:56:24 -070070
71 buildModuleReportZip bool
Colin Cross014489c2020-06-02 20:09:13 -070072}
73
74type lintOutputs struct {
75 html android.ModuleOutPath
76 text android.ModuleOutPath
77 xml android.ModuleOutPath
Colin Crossc0efd1d2020-07-03 11:56:24 -070078
79 transitiveHTML *android.DepSet
80 transitiveText *android.DepSet
81 transitiveXML *android.DepSet
82
83 transitiveHTMLZip android.OptionalPath
84 transitiveTextZip android.OptionalPath
85 transitiveXMLZip android.OptionalPath
86}
87
88type lintOutputIntf interface {
89 lintOutputs() *lintOutputs
90}
91
92var _ lintOutputIntf = (*linter)(nil)
93
94func (l *linter) lintOutputs() *lintOutputs {
95 return &l.outputs
Colin Cross014489c2020-06-02 20:09:13 -070096}
97
98func (l *linter) enabled() bool {
99 return BoolDefault(l.properties.Lint.Enabled, true)
100}
101
Colin Cross92e4b462020-06-18 15:56:48 -0700102func (l *linter) deps(ctx android.BottomUpMutatorContext) {
103 if !l.enabled() {
104 return
105 }
106
107 ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), extraLintCheckTag, l.properties.Lint.Extra_check_modules...)
108}
109
Colin Cross014489c2020-06-02 20:09:13 -0700110func (l *linter) writeLintProjectXML(ctx android.ModuleContext,
Colin Cross977b6a82020-06-23 10:22:49 -0700111 rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir, homeDir android.WritablePath, deps android.Paths) {
Colin Cross014489c2020-06-02 20:09:13 -0700112
113 var resourcesList android.WritablePath
114 if len(l.resources) > 0 {
115 // The list of resources may be too long to put on the command line, but
116 // we can't use the rsp file because it is already being used for srcs.
117 // Insert a second rule to write out the list of resources to a file.
118 resourcesList = android.PathForModuleOut(ctx, "lint", "resources.list")
119 resListRule := android.NewRuleBuilder()
120 resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList)
121 resListRule.Build(pctx, ctx, "lint_resources_list", "lint resources list")
122 deps = append(deps, l.resources...)
123 }
124
125 projectXMLPath = android.PathForModuleOut(ctx, "lint", "project.xml")
126 // Lint looks for a lint.xml file next to the project.xml file, give it one.
127 configXMLPath = android.PathForModuleOut(ctx, "lint", "lint.xml")
128 cacheDir = android.PathForModuleOut(ctx, "lint", "cache")
Colin Cross977b6a82020-06-23 10:22:49 -0700129 homeDir = android.PathForModuleOut(ctx, "lint", "home")
Colin Cross014489c2020-06-02 20:09:13 -0700130
131 srcJarDir := android.PathForModuleOut(ctx, "lint-srcjars")
132 srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
133
134 cmd := rule.Command().
135 BuiltTool(ctx, "lint-project-xml").
136 FlagWithOutput("--project_out ", projectXMLPath).
137 FlagWithOutput("--config_out ", configXMLPath).
138 FlagWithArg("--name ", ctx.ModuleName())
139
140 if l.library {
141 cmd.Flag("--library")
142 }
143 if l.test {
144 cmd.Flag("--test")
145 }
146 if l.manifest != nil {
147 deps = append(deps, l.manifest)
148 cmd.FlagWithArg("--manifest ", l.manifest.String())
149 }
150 if l.mergedManifest != nil {
151 deps = append(deps, l.mergedManifest)
152 cmd.FlagWithArg("--merged_manifest ", l.mergedManifest.String())
153 }
154
155 // TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
156 // lint separately.
157 cmd.FlagWithRspFileInputList("--srcs ", l.srcs)
158 deps = append(deps, l.srcs...)
159
160 cmd.FlagWithInput("--generated_srcs ", srcJarList)
161 deps = append(deps, l.srcJars...)
162
163 if resourcesList != nil {
164 cmd.FlagWithInput("--resources ", resourcesList)
165 }
166
167 if l.classes != nil {
168 deps = append(deps, l.classes)
169 cmd.FlagWithArg("--classes ", l.classes.String())
170 }
171
172 cmd.FlagForEachArg("--classpath ", l.classpath.Strings())
173 deps = append(deps, l.classpath...)
174
175 cmd.FlagForEachArg("--extra_checks_jar ", l.extraLintCheckJars.Strings())
176 deps = append(deps, l.extraLintCheckJars...)
177
Colin Crossc31efeb2020-06-23 10:25:26 -0700178 cmd.FlagWithArg("--root_dir ", "$PWD")
179
180 // The cache tag in project.xml is relative to the root dir, or the project.xml file if
181 // the root dir is not set.
182 cmd.FlagWithArg("--cache_dir ", cacheDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700183
184 cmd.FlagWithInput("@",
185 android.PathForSource(ctx, "build/soong/java/lint_defaults.txt"))
186
187 cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks)
188 cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks)
189 cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
190 cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
191
Colin Cross977b6a82020-06-23 10:22:49 -0700192 return projectXMLPath, configXMLPath, cacheDir, homeDir, deps
Colin Cross014489c2020-06-02 20:09:13 -0700193}
194
195// generateManifest adds a command to the rule to write a dummy manifest cat contains the
196// minSdkVersion and targetSdkVersion for modules (like java_library) that don't have a manifest.
197func (l *linter) generateManifest(ctx android.ModuleContext, rule *android.RuleBuilder) android.Path {
198 manifestPath := android.PathForModuleOut(ctx, "lint", "AndroidManifest.xml")
199
200 rule.Command().Text("(").
201 Text(`echo "<?xml version='1.0' encoding='utf-8'?>" &&`).
202 Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`).
203 Text(`echo " android:versionCode='1' android:versionName='1' >" &&`).
204 Textf(`echo " <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`,
205 l.minSdkVersion, l.targetSdkVersion).
206 Text(`echo "</manifest>"`).
207 Text(") >").Output(manifestPath)
208
209 return manifestPath
210}
211
212func (l *linter) lint(ctx android.ModuleContext) {
213 if !l.enabled() {
214 return
215 }
216
Colin Cross92e4b462020-06-18 15:56:48 -0700217 extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag)
218 for _, extraLintCheckModule := range extraLintCheckModules {
219 if dep, ok := extraLintCheckModule.(Dependency); ok {
220 l.extraLintCheckJars = append(l.extraLintCheckJars, dep.ImplementationAndResourcesJars()...)
221 } else {
222 ctx.PropertyErrorf("lint.extra_check_modules",
223 "%s is not a java module", ctx.OtherModuleName(extraLintCheckModule))
224 }
225 }
226
Colin Cross014489c2020-06-02 20:09:13 -0700227 rule := android.NewRuleBuilder()
228
229 if l.manifest == nil {
230 manifest := l.generateManifest(ctx, rule)
231 l.manifest = manifest
232 }
233
Colin Cross977b6a82020-06-23 10:22:49 -0700234 projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule)
Colin Cross014489c2020-06-02 20:09:13 -0700235
Colin Crossc0efd1d2020-07-03 11:56:24 -0700236 html := android.PathForModuleOut(ctx, "lint-report.html")
237 text := android.PathForModuleOut(ctx, "lint-report.txt")
238 xml := android.PathForModuleOut(ctx, "lint-report.xml")
239
240 htmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(html)
241 textDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(text)
242 xmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(xml)
243
244 ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) {
245 if depLint, ok := dep.(lintOutputIntf); ok {
246 depLintOutputs := depLint.lintOutputs()
247 htmlDeps.Transitive(depLintOutputs.transitiveHTML)
248 textDeps.Transitive(depLintOutputs.transitiveText)
249 xmlDeps.Transitive(depLintOutputs.transitiveXML)
250 }
251 })
Colin Cross014489c2020-06-02 20:09:13 -0700252
Colin Cross977b6a82020-06-23 10:22:49 -0700253 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
254 rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700255
Colin Cross8a6ed372020-07-06 11:45:51 -0700256 var annotationsZipPath, apiVersionsXMLPath android.Path
257 if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
258 annotationsZipPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/annotations.zip")
259 apiVersionsXMLPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/api-versions.xml")
260 } else {
261 annotationsZipPath = copiedAnnotationsZipPath(ctx)
262 apiVersionsXMLPath = copiedAPIVersionsXmlPath(ctx)
263 }
264
Colin Cross014489c2020-06-02 20:09:13 -0700265 rule.Command().
266 Text("(").
267 Flag("JAVA_OPTS=-Xmx2048m").
Colin Cross977b6a82020-06-23 10:22:49 -0700268 FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()).
Colin Cross8a6ed372020-07-06 11:45:51 -0700269 FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath).
270 FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath).
Colin Cross014489c2020-06-02 20:09:13 -0700271 Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")).
272 Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")).
273 Flag("--quiet").
274 FlagWithInput("--project ", projectXML).
275 FlagWithInput("--config ", lintXML).
Colin Crossc0efd1d2020-07-03 11:56:24 -0700276 FlagWithOutput("--html ", html).
277 FlagWithOutput("--text ", text).
278 FlagWithOutput("--xml ", xml).
Colin Cross014489c2020-06-02 20:09:13 -0700279 FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
280 FlagWithArg("--java-language-level ", l.javaLanguageLevel).
281 FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
282 FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
283 Flag("--exitcode").
284 Flags(l.properties.Lint.Flags).
285 Implicits(deps).
Colin Crossc0efd1d2020-07-03 11:56:24 -0700286 Text("|| (").Text("cat").Input(text).Text("; exit 7)").
Colin Cross014489c2020-06-02 20:09:13 -0700287 Text(")")
288
Colin Cross977b6a82020-06-23 10:22:49 -0700289 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700290
291 rule.Build(pctx, ctx, "lint", "lint")
Colin Cross014489c2020-06-02 20:09:13 -0700292
Colin Crossc0efd1d2020-07-03 11:56:24 -0700293 l.outputs = lintOutputs{
294 html: html,
295 text: text,
296 xml: xml,
Colin Cross014489c2020-06-02 20:09:13 -0700297
Colin Crossc0efd1d2020-07-03 11:56:24 -0700298 transitiveHTML: htmlDeps.Build(),
299 transitiveText: textDeps.Build(),
300 transitiveXML: xmlDeps.Build(),
301 }
Colin Cross014489c2020-06-02 20:09:13 -0700302
Colin Crossc0efd1d2020-07-03 11:56:24 -0700303 if l.buildModuleReportZip {
304 htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip")
305 l.outputs.transitiveHTMLZip = android.OptionalPathForPath(htmlZip)
306 lintZip(ctx, l.outputs.transitiveHTML.ToSortedList(), htmlZip)
307
308 textZip := android.PathForModuleOut(ctx, "lint-report-text.zip")
309 l.outputs.transitiveTextZip = android.OptionalPathForPath(textZip)
310 lintZip(ctx, l.outputs.transitiveText.ToSortedList(), textZip)
311
312 xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip")
313 l.outputs.transitiveXMLZip = android.OptionalPathForPath(xmlZip)
314 lintZip(ctx, l.outputs.transitiveXML.ToSortedList(), xmlZip)
315 }
316}
Colin Cross014489c2020-06-02 20:09:13 -0700317
318type lintSingleton struct {
319 htmlZip android.WritablePath
320 textZip android.WritablePath
321 xmlZip android.WritablePath
322}
323
324func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) {
325 l.generateLintReportZips(ctx)
326 l.copyLintDependencies(ctx)
327}
328
329func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) {
Colin Cross8a6ed372020-07-06 11:45:51 -0700330 if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
Colin Cross014489c2020-06-02 20:09:13 -0700331 return
332 }
333
334 var frameworkDocStubs android.Module
335 ctx.VisitAllModules(func(m android.Module) {
336 if ctx.ModuleName(m) == "framework-doc-stubs" {
337 if frameworkDocStubs == nil {
338 frameworkDocStubs = m
339 } else {
340 ctx.Errorf("lint: multiple framework-doc-stubs modules found: %s and %s",
341 ctx.ModuleSubDir(m), ctx.ModuleSubDir(frameworkDocStubs))
342 }
343 }
344 })
345
346 if frameworkDocStubs == nil {
347 if !ctx.Config().AllowMissingDependencies() {
348 ctx.Errorf("lint: missing framework-doc-stubs")
349 }
350 return
351 }
352
353 ctx.Build(pctx, android.BuildParams{
354 Rule: android.Cp,
355 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
Colin Cross8a6ed372020-07-06 11:45:51 -0700356 Output: copiedAnnotationsZipPath(ctx),
Colin Cross014489c2020-06-02 20:09:13 -0700357 })
358
359 ctx.Build(pctx, android.BuildParams{
360 Rule: android.Cp,
361 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
Colin Cross8a6ed372020-07-06 11:45:51 -0700362 Output: copiedAPIVersionsXmlPath(ctx),
Colin Cross014489c2020-06-02 20:09:13 -0700363 })
364}
365
Colin Cross8a6ed372020-07-06 11:45:51 -0700366func copiedAnnotationsZipPath(ctx android.PathContext) android.WritablePath {
Colin Cross014489c2020-06-02 20:09:13 -0700367 return android.PathForOutput(ctx, "lint", "annotations.zip")
368}
369
Colin Cross8a6ed372020-07-06 11:45:51 -0700370func copiedAPIVersionsXmlPath(ctx android.PathContext) android.WritablePath {
Colin Cross014489c2020-06-02 20:09:13 -0700371 return android.PathForOutput(ctx, "lint", "api_versions.xml")
372}
373
374func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) {
Colin Cross8a6ed372020-07-06 11:45:51 -0700375 if ctx.Config().UnbundledBuild() {
376 return
377 }
378
Colin Cross014489c2020-06-02 20:09:13 -0700379 var outputs []*lintOutputs
380 var dirs []string
381 ctx.VisitAllModules(func(m android.Module) {
382 if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() {
383 return
384 }
385
386 if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() && apex.IsForPlatform() {
387 // There are stray platform variants of modules in apexes that are not available for
388 // the platform, and they sometimes can't be built. Don't depend on them.
389 return
390 }
391
392 if l, ok := m.(lintOutputIntf); ok {
393 outputs = append(outputs, l.lintOutputs())
394 }
395 })
396
397 dirs = android.SortedUniqueStrings(dirs)
398
399 zip := func(outputPath android.WritablePath, get func(*lintOutputs) android.Path) {
400 var paths android.Paths
401
402 for _, output := range outputs {
403 paths = append(paths, get(output))
404 }
405
Colin Crossc0efd1d2020-07-03 11:56:24 -0700406 lintZip(ctx, paths, outputPath)
Colin Cross014489c2020-06-02 20:09:13 -0700407 }
408
409 l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
410 zip(l.htmlZip, func(l *lintOutputs) android.Path { return l.html })
411
412 l.textZip = android.PathForOutput(ctx, "lint-report-text.zip")
413 zip(l.textZip, func(l *lintOutputs) android.Path { return l.text })
414
415 l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip")
416 zip(l.xmlZip, func(l *lintOutputs) android.Path { return l.xml })
417
418 ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip)
419}
420
421func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
Colin Cross8a6ed372020-07-06 11:45:51 -0700422 if !ctx.Config().UnbundledBuild() {
423 ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip)
424 }
Colin Cross014489c2020-06-02 20:09:13 -0700425}
426
427var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
428
429func init() {
430 android.RegisterSingletonType("lint",
431 func() android.Singleton { return &lintSingleton{} })
432}
Colin Crossc0efd1d2020-07-03 11:56:24 -0700433
434func lintZip(ctx android.BuilderContext, paths android.Paths, outputPath android.WritablePath) {
435 paths = android.SortedUniquePaths(android.CopyOfPaths(paths))
436
437 sort.Slice(paths, func(i, j int) bool {
438 return paths[i].String() < paths[j].String()
439 })
440
441 rule := android.NewRuleBuilder()
442
443 rule.Command().BuiltTool(ctx, "soong_zip").
444 FlagWithOutput("-o ", outputPath).
445 FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
446 FlagWithRspFileInputList("-l ", paths)
447
448 rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
449}