1// 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 "strings" 21 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 46 47 // Modules that provide extra lint checks 48 Extra_check_modules []string 49 } 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 71 72 reports android.Paths 73 74 buildModuleReportZip bool 75} 76 77type lintOutputs struct { 78 html android.Path 79 text android.Path 80 xml android.Path 81 82 depSets LintDepSets 83} 84 85type lintOutputsIntf interface { 86 lintOutputs() *lintOutputs 87} 88 89type lintDepSetsIntf interface { 90 LintDepSets() LintDepSets 91} 92 93type LintDepSets struct { 94 HTML, Text, XML *android.DepSet 95} 96 97type LintDepSetsBuilder struct { 98 HTML, Text, XML *android.DepSetBuilder 99} 100 101func NewLintDepSetBuilder() LintDepSetsBuilder { 102 return LintDepSetsBuilder{ 103 HTML: android.NewDepSetBuilder(android.POSTORDER), 104 Text: android.NewDepSetBuilder(android.POSTORDER), 105 XML: android.NewDepSetBuilder(android.POSTORDER), 106 } 107} 108 109func (l LintDepSetsBuilder) Direct(html, text, xml android.Path) LintDepSetsBuilder { 110 l.HTML.Direct(html) 111 l.Text.Direct(text) 112 l.XML.Direct(xml) 113 return l 114} 115 116func (l LintDepSetsBuilder) Transitive(depSets LintDepSets) LintDepSetsBuilder { 117 if depSets.HTML != nil { 118 l.HTML.Transitive(depSets.HTML) 119 } 120 if depSets.Text != nil { 121 l.Text.Transitive(depSets.Text) 122 } 123 if depSets.XML != nil { 124 l.XML.Transitive(depSets.XML) 125 } 126 return l 127} 128 129func (l LintDepSetsBuilder) Build() LintDepSets { 130 return LintDepSets{ 131 HTML: l.HTML.Build(), 132 Text: l.Text.Build(), 133 XML: l.XML.Build(), 134 } 135} 136 137func (l *linter) LintDepSets() LintDepSets { 138 return l.outputs.depSets 139} 140 141var _ lintDepSetsIntf = (*linter)(nil) 142 143var _ lintOutputsIntf = (*linter)(nil) 144 145func (l *linter) lintOutputs() *lintOutputs { 146 return &l.outputs 147} 148 149func (l *linter) enabled() bool { 150 return BoolDefault(l.properties.Lint.Enabled, true) 151} 152 153func (l *linter) deps(ctx android.BottomUpMutatorContext) { 154 if !l.enabled() { 155 return 156 } 157 158 extraCheckModules := l.properties.Lint.Extra_check_modules 159 160 if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" { 161 if checkOnlyModules := ctx.Config().Getenv("ANDROID_LINT_CHECK_EXTRA_MODULES"); checkOnlyModules != "" { 162 extraCheckModules = strings.Split(checkOnlyModules, ",") 163 } 164 } 165 166 ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), 167 extraLintCheckTag, extraCheckModules...) 168} 169 170func (l *linter) writeLintProjectXML(ctx android.ModuleContext, 171 rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir, homeDir android.WritablePath, deps android.Paths) { 172 173 var resourcesList android.WritablePath 174 if len(l.resources) > 0 { 175 // The list of resources may be too long to put on the command line, but 176 // we can't use the rsp file because it is already being used for srcs. 177 // Insert a second rule to write out the list of resources to a file. 178 resourcesList = android.PathForModuleOut(ctx, "lint", "resources.list") 179 resListRule := android.NewRuleBuilder() 180 resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList) 181 resListRule.Build(pctx, ctx, "lint_resources_list", "lint resources list") 182 deps = append(deps, l.resources...) 183 } 184 185 projectXMLPath = android.PathForModuleOut(ctx, "lint", "project.xml") 186 // Lint looks for a lint.xml file next to the project.xml file, give it one. 187 configXMLPath = android.PathForModuleOut(ctx, "lint", "lint.xml") 188 cacheDir = android.PathForModuleOut(ctx, "lint", "cache") 189 homeDir = android.PathForModuleOut(ctx, "lint", "home") 190 191 srcJarDir := android.PathForModuleOut(ctx, "lint-srcjars") 192 srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars) 193 194 cmd := rule.Command(). 195 BuiltTool(ctx, "lint-project-xml"). 196 FlagWithOutput("--project_out ", projectXMLPath). 197 FlagWithOutput("--config_out ", configXMLPath). 198 FlagWithArg("--name ", ctx.ModuleName()) 199 200 if l.library { 201 cmd.Flag("--library") 202 } 203 if l.test { 204 cmd.Flag("--test") 205 } 206 if l.manifest != nil { 207 deps = append(deps, l.manifest) 208 cmd.FlagWithArg("--manifest ", l.manifest.String()) 209 } 210 if l.mergedManifest != nil { 211 deps = append(deps, l.mergedManifest) 212 cmd.FlagWithArg("--merged_manifest ", l.mergedManifest.String()) 213 } 214 215 // TODO(ccross): some of the files in l.srcs are generated sources and should be passed to 216 // lint separately. 217 cmd.FlagWithRspFileInputList("--srcs ", l.srcs) 218 deps = append(deps, l.srcs...) 219 220 cmd.FlagWithInput("--generated_srcs ", srcJarList) 221 deps = append(deps, l.srcJars...) 222 223 if resourcesList != nil { 224 cmd.FlagWithInput("--resources ", resourcesList) 225 } 226 227 if l.classes != nil { 228 deps = append(deps, l.classes) 229 cmd.FlagWithArg("--classes ", l.classes.String()) 230 } 231 232 cmd.FlagForEachArg("--classpath ", l.classpath.Strings()) 233 deps = append(deps, l.classpath...) 234 235 cmd.FlagForEachArg("--extra_checks_jar ", l.extraLintCheckJars.Strings()) 236 deps = append(deps, l.extraLintCheckJars...) 237 238 cmd.FlagWithArg("--root_dir ", "$PWD") 239 240 // The cache tag in project.xml is relative to the root dir, or the project.xml file if 241 // the root dir is not set. 242 cmd.FlagWithArg("--cache_dir ", cacheDir.String()) 243 244 cmd.FlagWithInput("@", 245 android.PathForSource(ctx, "build/soong/java/lint_defaults.txt")) 246 247 cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks) 248 cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks) 249 cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks) 250 cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks) 251 252 return projectXMLPath, configXMLPath, cacheDir, homeDir, deps 253} 254 255// generateManifest adds a command to the rule to write a simple manifest that contains the 256// minSdkVersion and targetSdkVersion for modules (like java_library) that don't have a manifest. 257func (l *linter) generateManifest(ctx android.ModuleContext, rule *android.RuleBuilder) android.Path { 258 manifestPath := android.PathForModuleOut(ctx, "lint", "AndroidManifest.xml") 259 260 rule.Command().Text("("). 261 Text(`echo "<?xml version='1.0' encoding='utf-8'?>" &&`). 262 Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`). 263 Text(`echo " android:versionCode='1' android:versionName='1' >" &&`). 264 Textf(`echo " <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`, 265 l.minSdkVersion, l.targetSdkVersion). 266 Text(`echo "</manifest>"`). 267 Text(") >").Output(manifestPath) 268 269 return manifestPath 270} 271 272func (l *linter) lint(ctx android.ModuleContext) { 273 if !l.enabled() { 274 return 275 } 276 277 extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag) 278 for _, extraLintCheckModule := range extraLintCheckModules { 279 if dep, ok := extraLintCheckModule.(Dependency); ok { 280 l.extraLintCheckJars = append(l.extraLintCheckJars, dep.ImplementationAndResourcesJars()...) 281 } else { 282 ctx.PropertyErrorf("lint.extra_check_modules", 283 "%s is not a java module", ctx.OtherModuleName(extraLintCheckModule)) 284 } 285 } 286 287 rule := android.NewRuleBuilder() 288 289 if l.manifest == nil { 290 manifest := l.generateManifest(ctx, rule) 291 l.manifest = manifest 292 } 293 294 projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule) 295 296 html := android.PathForModuleOut(ctx, "lint-report.html") 297 text := android.PathForModuleOut(ctx, "lint-report.txt") 298 xml := android.PathForModuleOut(ctx, "lint-report.xml") 299 300 depSetsBuilder := NewLintDepSetBuilder().Direct(html, text, xml) 301 302 ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) { 303 if depLint, ok := dep.(lintDepSetsIntf); ok { 304 depSetsBuilder.Transitive(depLint.LintDepSets()) 305 } 306 }) 307 308 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String()) 309 rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String()) 310 311 var annotationsZipPath, apiVersionsXMLPath android.Path 312 if ctx.Config().UnbundledBuildUsePrebuiltSdks() { 313 annotationsZipPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/annotations.zip") 314 apiVersionsXMLPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/api-versions.xml") 315 } else { 316 annotationsZipPath = copiedAnnotationsZipPath(ctx) 317 apiVersionsXMLPath = copiedAPIVersionsXmlPath(ctx) 318 } 319 320 cmd := rule.Command(). 321 Text("("). 322 Flag("JAVA_OPTS=-Xmx2048m"). 323 FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()). 324 FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath). 325 FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath). 326 Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")). 327 Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")). 328 Flag("--quiet"). 329 FlagWithInput("--project ", projectXML). 330 FlagWithInput("--config ", lintXML). 331 FlagWithOutput("--html ", html). 332 FlagWithOutput("--text ", text). 333 FlagWithOutput("--xml ", xml). 334 FlagWithArg("--compile-sdk-version ", l.compileSdkVersion). 335 FlagWithArg("--java-language-level ", l.javaLanguageLevel). 336 FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel). 337 FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())). 338 Flag("--exitcode"). 339 Flags(l.properties.Lint.Flags). 340 Implicits(deps) 341 342 if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" { 343 cmd.FlagWithArg("--check ", checkOnly) 344 } 345 346 cmd.Text("|| (").Text("cat").Input(text).Text("; exit 7)").Text(")") 347 348 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String()) 349 350 rule.Build(pctx, ctx, "lint", "lint") 351 352 l.outputs = lintOutputs{ 353 html: html, 354 text: text, 355 xml: xml, 356 357 depSets: depSetsBuilder.Build(), 358 } 359 360 if l.buildModuleReportZip { 361 l.reports = BuildModuleLintReportZips(ctx, l.LintDepSets()) 362 } 363} 364 365func BuildModuleLintReportZips(ctx android.ModuleContext, depSets LintDepSets) android.Paths { 366 htmlList := depSets.HTML.ToSortedList() 367 textList := depSets.Text.ToSortedList() 368 xmlList := depSets.XML.ToSortedList() 369 370 if len(htmlList) == 0 && len(textList) == 0 && len(xmlList) == 0 { 371 return nil 372 } 373 374 htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip") 375 lintZip(ctx, htmlList, htmlZip) 376 377 textZip := android.PathForModuleOut(ctx, "lint-report-text.zip") 378 lintZip(ctx, textList, textZip) 379 380 xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip") 381 lintZip(ctx, xmlList, xmlZip) 382 383 return android.Paths{htmlZip, textZip, xmlZip} 384} 385 386type lintSingleton struct { 387 htmlZip android.WritablePath 388 textZip android.WritablePath 389 xmlZip android.WritablePath 390} 391 392func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) { 393 l.generateLintReportZips(ctx) 394 l.copyLintDependencies(ctx) 395} 396 397func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) { 398 if ctx.Config().UnbundledBuildUsePrebuiltSdks() { 399 return 400 } 401 402 var frameworkDocStubs android.Module 403 ctx.VisitAllModules(func(m android.Module) { 404 if ctx.ModuleName(m) == "framework-doc-stubs" { 405 if frameworkDocStubs == nil { 406 frameworkDocStubs = m 407 } else { 408 ctx.Errorf("lint: multiple framework-doc-stubs modules found: %s and %s", 409 ctx.ModuleSubDir(m), ctx.ModuleSubDir(frameworkDocStubs)) 410 } 411 } 412 }) 413 414 if frameworkDocStubs == nil { 415 if !ctx.Config().AllowMissingDependencies() { 416 ctx.Errorf("lint: missing framework-doc-stubs") 417 } 418 return 419 } 420 421 ctx.Build(pctx, android.BuildParams{ 422 Rule: android.Cp, 423 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"), 424 Output: copiedAnnotationsZipPath(ctx), 425 }) 426 427 ctx.Build(pctx, android.BuildParams{ 428 Rule: android.Cp, 429 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"), 430 Output: copiedAPIVersionsXmlPath(ctx), 431 }) 432} 433 434func copiedAnnotationsZipPath(ctx android.PathContext) android.WritablePath { 435 return android.PathForOutput(ctx, "lint", "annotations.zip") 436} 437 438func copiedAPIVersionsXmlPath(ctx android.PathContext) android.WritablePath { 439 return android.PathForOutput(ctx, "lint", "api_versions.xml") 440} 441 442func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) { 443 if ctx.Config().UnbundledBuild() { 444 return 445 } 446 447 var outputs []*lintOutputs 448 var dirs []string 449 ctx.VisitAllModules(func(m android.Module) { 450 if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() { 451 return 452 } 453 454 if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() && apex.IsForPlatform() { 455 // There are stray platform variants of modules in apexes that are not available for 456 // the platform, and they sometimes can't be built. Don't depend on them. 457 return 458 } 459 460 if l, ok := m.(lintOutputsIntf); ok { 461 outputs = append(outputs, l.lintOutputs()) 462 } 463 }) 464 465 dirs = android.SortedUniqueStrings(dirs) 466 467 zip := func(outputPath android.WritablePath, get func(*lintOutputs) android.Path) { 468 var paths android.Paths 469 470 for _, output := range outputs { 471 if p := get(output); p != nil { 472 paths = append(paths, p) 473 } 474 } 475 476 lintZip(ctx, paths, outputPath) 477 } 478 479 l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip") 480 zip(l.htmlZip, func(l *lintOutputs) android.Path { return l.html }) 481 482 l.textZip = android.PathForOutput(ctx, "lint-report-text.zip") 483 zip(l.textZip, func(l *lintOutputs) android.Path { return l.text }) 484 485 l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip") 486 zip(l.xmlZip, func(l *lintOutputs) android.Path { return l.xml }) 487 488 ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip) 489} 490 491func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) { 492 if !ctx.Config().UnbundledBuild() { 493 ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip) 494 } 495} 496 497var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil) 498 499func init() { 500 android.RegisterSingletonType("lint", 501 func() android.Singleton { return &lintSingleton{} }) 502} 503 504func lintZip(ctx android.BuilderContext, paths android.Paths, outputPath android.WritablePath) { 505 paths = android.SortedUniquePaths(android.CopyOfPaths(paths)) 506 507 sort.Slice(paths, func(i, j int) bool { 508 return paths[i].String() < paths[j].String() 509 }) 510 511 rule := android.NewRuleBuilder() 512 513 rule.Command().BuiltTool(ctx, "soong_zip"). 514 FlagWithOutput("-o ", outputPath). 515 FlagWithArg("-C ", android.PathForIntermediates(ctx).String()). 516 FlagWithRspFileInputList("-l ", paths) 517 518 rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base()) 519} 520