blob: 5d2c4f6142598474f61cdb396dd57767f84bf03c [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"
Colin Cross988dfcc2020-07-16 17:32:17 -070020 "strings"
Colin Cross014489c2020-06-02 20:09:13 -070021
22 "android/soong/android"
23)
24
25type LintProperties struct {
26 // Controls for running Android Lint on the module.
27 Lint struct {
28
29 // If true, run Android Lint on the module. Defaults to true.
30 Enabled *bool
31
32 // Flags to pass to the Android Lint tool.
33 Flags []string
34
35 // Checks that should be treated as fatal.
36 Fatal_checks []string
37
38 // Checks that should be treated as errors.
39 Error_checks []string
40
41 // Checks that should be treated as warnings.
42 Warning_checks []string
43
44 // Checks that should be skipped.
45 Disabled_checks []string
Colin Cross92e4b462020-06-18 15:56:48 -070046
47 // Modules that provide extra lint checks
48 Extra_check_modules []string
Colin Cross014489c2020-06-02 20:09:13 -070049 }
50}
51
52type linter struct {
53 name string
54 manifest android.Path
55 mergedManifest android.Path
56 srcs android.Paths
57 srcJars android.Paths
58 resources android.Paths
59 classpath android.Paths
60 classes android.Path
61 extraLintCheckJars android.Paths
62 test bool
63 library bool
64 minSdkVersion string
65 targetSdkVersion string
66 compileSdkVersion string
67 javaLanguageLevel string
68 kotlinLanguageLevel string
69 outputs lintOutputs
70 properties LintProperties
Colin Crossc0efd1d2020-07-03 11:56:24 -070071
72 buildModuleReportZip bool
Colin Cross014489c2020-06-02 20:09:13 -070073}
74
75type lintOutputs struct {
76 html android.ModuleOutPath
77 text android.ModuleOutPath
78 xml android.ModuleOutPath
Colin Crossc0efd1d2020-07-03 11:56:24 -070079
80 transitiveHTML *android.DepSet
81 transitiveText *android.DepSet
82 transitiveXML *android.DepSet
83
84 transitiveHTMLZip android.OptionalPath
85 transitiveTextZip android.OptionalPath
86 transitiveXMLZip android.OptionalPath
87}
88
89type lintOutputIntf interface {
90 lintOutputs() *lintOutputs
91}
92
93var _ lintOutputIntf = (*linter)(nil)
94
95func (l *linter) lintOutputs() *lintOutputs {
96 return &l.outputs
Colin Cross014489c2020-06-02 20:09:13 -070097}
98
99func (l *linter) enabled() bool {
100 return BoolDefault(l.properties.Lint.Enabled, true)
101}
102
Colin Cross92e4b462020-06-18 15:56:48 -0700103func (l *linter) deps(ctx android.BottomUpMutatorContext) {
104 if !l.enabled() {
105 return
106 }
107
Colin Cross988dfcc2020-07-16 17:32:17 -0700108 extraCheckModules := l.properties.Lint.Extra_check_modules
109
110 if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" {
111 if checkOnlyModules := ctx.Config().Getenv("ANDROID_LINT_CHECK_EXTRA_MODULES"); checkOnlyModules != "" {
112 extraCheckModules = strings.Split(checkOnlyModules, ",")
113 }
114 }
115
116 ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(),
117 extraLintCheckTag, extraCheckModules...)
Colin Cross92e4b462020-06-18 15:56:48 -0700118}
119
Colin Cross014489c2020-06-02 20:09:13 -0700120func (l *linter) writeLintProjectXML(ctx android.ModuleContext,
Colin Cross977b6a82020-06-23 10:22:49 -0700121 rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir, homeDir android.WritablePath, deps android.Paths) {
Colin Cross014489c2020-06-02 20:09:13 -0700122
123 var resourcesList android.WritablePath
124 if len(l.resources) > 0 {
125 // The list of resources may be too long to put on the command line, but
126 // we can't use the rsp file because it is already being used for srcs.
127 // Insert a second rule to write out the list of resources to a file.
128 resourcesList = android.PathForModuleOut(ctx, "lint", "resources.list")
129 resListRule := android.NewRuleBuilder()
130 resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList)
131 resListRule.Build(pctx, ctx, "lint_resources_list", "lint resources list")
132 deps = append(deps, l.resources...)
133 }
134
135 projectXMLPath = android.PathForModuleOut(ctx, "lint", "project.xml")
136 // Lint looks for a lint.xml file next to the project.xml file, give it one.
137 configXMLPath = android.PathForModuleOut(ctx, "lint", "lint.xml")
138 cacheDir = android.PathForModuleOut(ctx, "lint", "cache")
Colin Cross977b6a82020-06-23 10:22:49 -0700139 homeDir = android.PathForModuleOut(ctx, "lint", "home")
Colin Cross014489c2020-06-02 20:09:13 -0700140
141 srcJarDir := android.PathForModuleOut(ctx, "lint-srcjars")
142 srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
143
144 cmd := rule.Command().
145 BuiltTool(ctx, "lint-project-xml").
146 FlagWithOutput("--project_out ", projectXMLPath).
147 FlagWithOutput("--config_out ", configXMLPath).
148 FlagWithArg("--name ", ctx.ModuleName())
149
150 if l.library {
151 cmd.Flag("--library")
152 }
153 if l.test {
154 cmd.Flag("--test")
155 }
156 if l.manifest != nil {
157 deps = append(deps, l.manifest)
158 cmd.FlagWithArg("--manifest ", l.manifest.String())
159 }
160 if l.mergedManifest != nil {
161 deps = append(deps, l.mergedManifest)
162 cmd.FlagWithArg("--merged_manifest ", l.mergedManifest.String())
163 }
164
165 // TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
166 // lint separately.
167 cmd.FlagWithRspFileInputList("--srcs ", l.srcs)
168 deps = append(deps, l.srcs...)
169
170 cmd.FlagWithInput("--generated_srcs ", srcJarList)
171 deps = append(deps, l.srcJars...)
172
173 if resourcesList != nil {
174 cmd.FlagWithInput("--resources ", resourcesList)
175 }
176
177 if l.classes != nil {
178 deps = append(deps, l.classes)
179 cmd.FlagWithArg("--classes ", l.classes.String())
180 }
181
182 cmd.FlagForEachArg("--classpath ", l.classpath.Strings())
183 deps = append(deps, l.classpath...)
184
185 cmd.FlagForEachArg("--extra_checks_jar ", l.extraLintCheckJars.Strings())
186 deps = append(deps, l.extraLintCheckJars...)
187
Colin Crossc31efeb2020-06-23 10:25:26 -0700188 cmd.FlagWithArg("--root_dir ", "$PWD")
189
190 // The cache tag in project.xml is relative to the root dir, or the project.xml file if
191 // the root dir is not set.
192 cmd.FlagWithArg("--cache_dir ", cacheDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700193
194 cmd.FlagWithInput("@",
195 android.PathForSource(ctx, "build/soong/java/lint_defaults.txt"))
196
197 cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks)
198 cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks)
199 cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
200 cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
201
Colin Cross977b6a82020-06-23 10:22:49 -0700202 return projectXMLPath, configXMLPath, cacheDir, homeDir, deps
Colin Cross014489c2020-06-02 20:09:13 -0700203}
204
205// generateManifest adds a command to the rule to write a dummy manifest cat contains the
206// minSdkVersion and targetSdkVersion for modules (like java_library) that don't have a manifest.
207func (l *linter) generateManifest(ctx android.ModuleContext, rule *android.RuleBuilder) android.Path {
208 manifestPath := android.PathForModuleOut(ctx, "lint", "AndroidManifest.xml")
209
210 rule.Command().Text("(").
211 Text(`echo "<?xml version='1.0' encoding='utf-8'?>" &&`).
212 Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`).
213 Text(`echo " android:versionCode='1' android:versionName='1' >" &&`).
214 Textf(`echo " <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`,
215 l.minSdkVersion, l.targetSdkVersion).
216 Text(`echo "</manifest>"`).
217 Text(") >").Output(manifestPath)
218
219 return manifestPath
220}
221
222func (l *linter) lint(ctx android.ModuleContext) {
223 if !l.enabled() {
224 return
225 }
226
Colin Cross92e4b462020-06-18 15:56:48 -0700227 extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag)
228 for _, extraLintCheckModule := range extraLintCheckModules {
229 if dep, ok := extraLintCheckModule.(Dependency); ok {
230 l.extraLintCheckJars = append(l.extraLintCheckJars, dep.ImplementationAndResourcesJars()...)
231 } else {
232 ctx.PropertyErrorf("lint.extra_check_modules",
233 "%s is not a java module", ctx.OtherModuleName(extraLintCheckModule))
234 }
235 }
236
Colin Cross014489c2020-06-02 20:09:13 -0700237 rule := android.NewRuleBuilder()
238
239 if l.manifest == nil {
240 manifest := l.generateManifest(ctx, rule)
241 l.manifest = manifest
242 }
243
Colin Cross977b6a82020-06-23 10:22:49 -0700244 projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule)
Colin Cross014489c2020-06-02 20:09:13 -0700245
Colin Crossc0efd1d2020-07-03 11:56:24 -0700246 html := android.PathForModuleOut(ctx, "lint-report.html")
247 text := android.PathForModuleOut(ctx, "lint-report.txt")
248 xml := android.PathForModuleOut(ctx, "lint-report.xml")
249
250 htmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(html)
251 textDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(text)
252 xmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(xml)
253
254 ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) {
255 if depLint, ok := dep.(lintOutputIntf); ok {
256 depLintOutputs := depLint.lintOutputs()
257 htmlDeps.Transitive(depLintOutputs.transitiveHTML)
258 textDeps.Transitive(depLintOutputs.transitiveText)
259 xmlDeps.Transitive(depLintOutputs.transitiveXML)
260 }
261 })
Colin Cross014489c2020-06-02 20:09:13 -0700262
Colin Cross977b6a82020-06-23 10:22:49 -0700263 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
264 rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700265
Colin Cross8a6ed372020-07-06 11:45:51 -0700266 var annotationsZipPath, apiVersionsXMLPath android.Path
267 if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
268 annotationsZipPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/annotations.zip")
269 apiVersionsXMLPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/api-versions.xml")
270 } else {
271 annotationsZipPath = copiedAnnotationsZipPath(ctx)
272 apiVersionsXMLPath = copiedAPIVersionsXmlPath(ctx)
273 }
274
Colin Cross988dfcc2020-07-16 17:32:17 -0700275 cmd := rule.Command().
Colin Cross014489c2020-06-02 20:09:13 -0700276 Text("(").
277 Flag("JAVA_OPTS=-Xmx2048m").
Colin Cross977b6a82020-06-23 10:22:49 -0700278 FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()).
Colin Cross8a6ed372020-07-06 11:45:51 -0700279 FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath).
280 FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath).
Colin Cross014489c2020-06-02 20:09:13 -0700281 Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")).
282 Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")).
283 Flag("--quiet").
284 FlagWithInput("--project ", projectXML).
285 FlagWithInput("--config ", lintXML).
Colin Crossc0efd1d2020-07-03 11:56:24 -0700286 FlagWithOutput("--html ", html).
287 FlagWithOutput("--text ", text).
288 FlagWithOutput("--xml ", xml).
Colin Cross014489c2020-06-02 20:09:13 -0700289 FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
290 FlagWithArg("--java-language-level ", l.javaLanguageLevel).
291 FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
292 FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
293 Flag("--exitcode").
294 Flags(l.properties.Lint.Flags).
Colin Cross988dfcc2020-07-16 17:32:17 -0700295 Implicits(deps)
296
297 if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" {
298 cmd.FlagWithArg("--check ", checkOnly)
299 }
300
301 cmd.Text("|| (").Text("cat").Input(text).Text("; exit 7)").Text(")")
Colin Cross014489c2020-06-02 20:09:13 -0700302
Colin Cross977b6a82020-06-23 10:22:49 -0700303 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700304
305 rule.Build(pctx, ctx, "lint", "lint")
Colin Cross014489c2020-06-02 20:09:13 -0700306
Colin Crossc0efd1d2020-07-03 11:56:24 -0700307 l.outputs = lintOutputs{
308 html: html,
309 text: text,
310 xml: xml,
Colin Cross014489c2020-06-02 20:09:13 -0700311
Colin Crossc0efd1d2020-07-03 11:56:24 -0700312 transitiveHTML: htmlDeps.Build(),
313 transitiveText: textDeps.Build(),
314 transitiveXML: xmlDeps.Build(),
315 }
Colin Cross014489c2020-06-02 20:09:13 -0700316
Colin Crossc0efd1d2020-07-03 11:56:24 -0700317 if l.buildModuleReportZip {
318 htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip")
319 l.outputs.transitiveHTMLZip = android.OptionalPathForPath(htmlZip)
320 lintZip(ctx, l.outputs.transitiveHTML.ToSortedList(), htmlZip)
321
322 textZip := android.PathForModuleOut(ctx, "lint-report-text.zip")
323 l.outputs.transitiveTextZip = android.OptionalPathForPath(textZip)
324 lintZip(ctx, l.outputs.transitiveText.ToSortedList(), textZip)
325
326 xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip")
327 l.outputs.transitiveXMLZip = android.OptionalPathForPath(xmlZip)
328 lintZip(ctx, l.outputs.transitiveXML.ToSortedList(), xmlZip)
329 }
330}
Colin Cross014489c2020-06-02 20:09:13 -0700331
332type lintSingleton struct {
333 htmlZip android.WritablePath
334 textZip android.WritablePath
335 xmlZip android.WritablePath
336}
337
338func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) {
339 l.generateLintReportZips(ctx)
340 l.copyLintDependencies(ctx)
341}
342
343func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) {
Colin Cross8a6ed372020-07-06 11:45:51 -0700344 if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
Colin Cross014489c2020-06-02 20:09:13 -0700345 return
346 }
347
348 var frameworkDocStubs android.Module
349 ctx.VisitAllModules(func(m android.Module) {
350 if ctx.ModuleName(m) == "framework-doc-stubs" {
351 if frameworkDocStubs == nil {
352 frameworkDocStubs = m
353 } else {
354 ctx.Errorf("lint: multiple framework-doc-stubs modules found: %s and %s",
355 ctx.ModuleSubDir(m), ctx.ModuleSubDir(frameworkDocStubs))
356 }
357 }
358 })
359
360 if frameworkDocStubs == nil {
361 if !ctx.Config().AllowMissingDependencies() {
362 ctx.Errorf("lint: missing framework-doc-stubs")
363 }
364 return
365 }
366
367 ctx.Build(pctx, android.BuildParams{
368 Rule: android.Cp,
369 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
Colin Cross8a6ed372020-07-06 11:45:51 -0700370 Output: copiedAnnotationsZipPath(ctx),
Colin Cross014489c2020-06-02 20:09:13 -0700371 })
372
373 ctx.Build(pctx, android.BuildParams{
374 Rule: android.Cp,
375 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
Colin Cross8a6ed372020-07-06 11:45:51 -0700376 Output: copiedAPIVersionsXmlPath(ctx),
Colin Cross014489c2020-06-02 20:09:13 -0700377 })
378}
379
Colin Cross8a6ed372020-07-06 11:45:51 -0700380func copiedAnnotationsZipPath(ctx android.PathContext) android.WritablePath {
Colin Cross014489c2020-06-02 20:09:13 -0700381 return android.PathForOutput(ctx, "lint", "annotations.zip")
382}
383
Colin Cross8a6ed372020-07-06 11:45:51 -0700384func copiedAPIVersionsXmlPath(ctx android.PathContext) android.WritablePath {
Colin Cross014489c2020-06-02 20:09:13 -0700385 return android.PathForOutput(ctx, "lint", "api_versions.xml")
386}
387
388func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) {
Colin Cross8a6ed372020-07-06 11:45:51 -0700389 if ctx.Config().UnbundledBuild() {
390 return
391 }
392
Colin Cross014489c2020-06-02 20:09:13 -0700393 var outputs []*lintOutputs
394 var dirs []string
395 ctx.VisitAllModules(func(m android.Module) {
396 if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() {
397 return
398 }
399
400 if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() && apex.IsForPlatform() {
401 // There are stray platform variants of modules in apexes that are not available for
402 // the platform, and they sometimes can't be built. Don't depend on them.
403 return
404 }
405
406 if l, ok := m.(lintOutputIntf); ok {
407 outputs = append(outputs, l.lintOutputs())
408 }
409 })
410
411 dirs = android.SortedUniqueStrings(dirs)
412
413 zip := func(outputPath android.WritablePath, get func(*lintOutputs) android.Path) {
414 var paths android.Paths
415
416 for _, output := range outputs {
417 paths = append(paths, get(output))
418 }
419
Colin Crossc0efd1d2020-07-03 11:56:24 -0700420 lintZip(ctx, paths, outputPath)
Colin Cross014489c2020-06-02 20:09:13 -0700421 }
422
423 l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
424 zip(l.htmlZip, func(l *lintOutputs) android.Path { return l.html })
425
426 l.textZip = android.PathForOutput(ctx, "lint-report-text.zip")
427 zip(l.textZip, func(l *lintOutputs) android.Path { return l.text })
428
429 l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip")
430 zip(l.xmlZip, func(l *lintOutputs) android.Path { return l.xml })
431
432 ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip)
433}
434
435func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
Colin Cross8a6ed372020-07-06 11:45:51 -0700436 if !ctx.Config().UnbundledBuild() {
437 ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip)
438 }
Colin Cross014489c2020-06-02 20:09:13 -0700439}
440
441var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
442
443func init() {
444 android.RegisterSingletonType("lint",
445 func() android.Singleton { return &lintSingleton{} })
446}
Colin Crossc0efd1d2020-07-03 11:56:24 -0700447
448func lintZip(ctx android.BuilderContext, paths android.Paths, outputPath android.WritablePath) {
449 paths = android.SortedUniquePaths(android.CopyOfPaths(paths))
450
451 sort.Slice(paths, func(i, j int) bool {
452 return paths[i].String() < paths[j].String()
453 })
454
455 rule := android.NewRuleBuilder()
456
457 rule.Command().BuiltTool(ctx, "soong_zip").
458 FlagWithOutput("-o ", outputPath).
459 FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
460 FlagWithRspFileInputList("-l ", paths)
461
462 rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
463}