blob: ca87dbab6a5166808c70f8037778e5db2c307e7c [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
70}
71
72type lintOutputs struct {
73 html android.ModuleOutPath
74 text android.ModuleOutPath
75 xml android.ModuleOutPath
76}
77
78func (l *linter) enabled() bool {
79 return BoolDefault(l.properties.Lint.Enabled, true)
80}
81
Colin Cross92e4b462020-06-18 15:56:48 -070082func (l *linter) deps(ctx android.BottomUpMutatorContext) {
83 if !l.enabled() {
84 return
85 }
86
87 ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), extraLintCheckTag, l.properties.Lint.Extra_check_modules...)
88}
89
Colin Cross014489c2020-06-02 20:09:13 -070090func (l *linter) writeLintProjectXML(ctx android.ModuleContext,
Colin Cross977b6a82020-06-23 10:22:49 -070091 rule *android.RuleBuilder) (projectXMLPath, configXMLPath, cacheDir, homeDir android.WritablePath, deps android.Paths) {
Colin Cross014489c2020-06-02 20:09:13 -070092
93 var resourcesList android.WritablePath
94 if len(l.resources) > 0 {
95 // The list of resources may be too long to put on the command line, but
96 // we can't use the rsp file because it is already being used for srcs.
97 // Insert a second rule to write out the list of resources to a file.
98 resourcesList = android.PathForModuleOut(ctx, "lint", "resources.list")
99 resListRule := android.NewRuleBuilder()
100 resListRule.Command().Text("cp").FlagWithRspFileInputList("", l.resources).Output(resourcesList)
101 resListRule.Build(pctx, ctx, "lint_resources_list", "lint resources list")
102 deps = append(deps, l.resources...)
103 }
104
105 projectXMLPath = android.PathForModuleOut(ctx, "lint", "project.xml")
106 // Lint looks for a lint.xml file next to the project.xml file, give it one.
107 configXMLPath = android.PathForModuleOut(ctx, "lint", "lint.xml")
108 cacheDir = android.PathForModuleOut(ctx, "lint", "cache")
Colin Cross977b6a82020-06-23 10:22:49 -0700109 homeDir = android.PathForModuleOut(ctx, "lint", "home")
Colin Cross014489c2020-06-02 20:09:13 -0700110
111 srcJarDir := android.PathForModuleOut(ctx, "lint-srcjars")
112 srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
113
114 cmd := rule.Command().
115 BuiltTool(ctx, "lint-project-xml").
116 FlagWithOutput("--project_out ", projectXMLPath).
117 FlagWithOutput("--config_out ", configXMLPath).
118 FlagWithArg("--name ", ctx.ModuleName())
119
120 if l.library {
121 cmd.Flag("--library")
122 }
123 if l.test {
124 cmd.Flag("--test")
125 }
126 if l.manifest != nil {
127 deps = append(deps, l.manifest)
128 cmd.FlagWithArg("--manifest ", l.manifest.String())
129 }
130 if l.mergedManifest != nil {
131 deps = append(deps, l.mergedManifest)
132 cmd.FlagWithArg("--merged_manifest ", l.mergedManifest.String())
133 }
134
135 // TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
136 // lint separately.
137 cmd.FlagWithRspFileInputList("--srcs ", l.srcs)
138 deps = append(deps, l.srcs...)
139
140 cmd.FlagWithInput("--generated_srcs ", srcJarList)
141 deps = append(deps, l.srcJars...)
142
143 if resourcesList != nil {
144 cmd.FlagWithInput("--resources ", resourcesList)
145 }
146
147 if l.classes != nil {
148 deps = append(deps, l.classes)
149 cmd.FlagWithArg("--classes ", l.classes.String())
150 }
151
152 cmd.FlagForEachArg("--classpath ", l.classpath.Strings())
153 deps = append(deps, l.classpath...)
154
155 cmd.FlagForEachArg("--extra_checks_jar ", l.extraLintCheckJars.Strings())
156 deps = append(deps, l.extraLintCheckJars...)
157
158 // The cache tag in project.xml is relative to the project.xml file.
159 cmd.FlagWithArg("--cache_dir ", "cache")
160
161 cmd.FlagWithInput("@",
162 android.PathForSource(ctx, "build/soong/java/lint_defaults.txt"))
163
164 cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks)
165 cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks)
166 cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
167 cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
168
Colin Cross977b6a82020-06-23 10:22:49 -0700169 return projectXMLPath, configXMLPath, cacheDir, homeDir, deps
Colin Cross014489c2020-06-02 20:09:13 -0700170}
171
172// generateManifest adds a command to the rule to write a dummy manifest cat contains the
173// minSdkVersion and targetSdkVersion for modules (like java_library) that don't have a manifest.
174func (l *linter) generateManifest(ctx android.ModuleContext, rule *android.RuleBuilder) android.Path {
175 manifestPath := android.PathForModuleOut(ctx, "lint", "AndroidManifest.xml")
176
177 rule.Command().Text("(").
178 Text(`echo "<?xml version='1.0' encoding='utf-8'?>" &&`).
179 Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`).
180 Text(`echo " android:versionCode='1' android:versionName='1' >" &&`).
181 Textf(`echo " <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`,
182 l.minSdkVersion, l.targetSdkVersion).
183 Text(`echo "</manifest>"`).
184 Text(") >").Output(manifestPath)
185
186 return manifestPath
187}
188
189func (l *linter) lint(ctx android.ModuleContext) {
190 if !l.enabled() {
191 return
192 }
193
Colin Cross92e4b462020-06-18 15:56:48 -0700194 extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag)
195 for _, extraLintCheckModule := range extraLintCheckModules {
196 if dep, ok := extraLintCheckModule.(Dependency); ok {
197 l.extraLintCheckJars = append(l.extraLintCheckJars, dep.ImplementationAndResourcesJars()...)
198 } else {
199 ctx.PropertyErrorf("lint.extra_check_modules",
200 "%s is not a java module", ctx.OtherModuleName(extraLintCheckModule))
201 }
202 }
203
Colin Cross014489c2020-06-02 20:09:13 -0700204 rule := android.NewRuleBuilder()
205
206 if l.manifest == nil {
207 manifest := l.generateManifest(ctx, rule)
208 l.manifest = manifest
209 }
210
Colin Cross977b6a82020-06-23 10:22:49 -0700211 projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule)
Colin Cross014489c2020-06-02 20:09:13 -0700212
213 l.outputs.html = android.PathForModuleOut(ctx, "lint-report.html")
214 l.outputs.text = android.PathForModuleOut(ctx, "lint-report.txt")
215 l.outputs.xml = android.PathForModuleOut(ctx, "lint-report.xml")
216
Colin Cross977b6a82020-06-23 10:22:49 -0700217 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
218 rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700219
220 rule.Command().
221 Text("(").
222 Flag("JAVA_OPTS=-Xmx2048m").
Colin Cross977b6a82020-06-23 10:22:49 -0700223 FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()).
Colin Cross014489c2020-06-02 20:09:13 -0700224 FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath(ctx)).
225 FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXmlPath(ctx)).
226 Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")).
227 Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")).
228 Flag("--quiet").
229 FlagWithInput("--project ", projectXML).
230 FlagWithInput("--config ", lintXML).
231 FlagWithOutput("--html ", l.outputs.html).
232 FlagWithOutput("--text ", l.outputs.text).
233 FlagWithOutput("--xml ", l.outputs.xml).
234 FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
235 FlagWithArg("--java-language-level ", l.javaLanguageLevel).
236 FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
237 FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
238 Flag("--exitcode").
239 Flags(l.properties.Lint.Flags).
240 Implicits(deps).
241 Text("|| (").Text("cat").Input(l.outputs.text).Text("; exit 7)").
242 Text(")")
243
Colin Cross977b6a82020-06-23 10:22:49 -0700244 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700245
246 rule.Build(pctx, ctx, "lint", "lint")
247}
248
249func (l *linter) lintOutputs() *lintOutputs {
250 return &l.outputs
251}
252
253type lintOutputIntf interface {
254 lintOutputs() *lintOutputs
255}
256
257var _ lintOutputIntf = (*linter)(nil)
258
259type lintSingleton struct {
260 htmlZip android.WritablePath
261 textZip android.WritablePath
262 xmlZip android.WritablePath
263}
264
265func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) {
266 l.generateLintReportZips(ctx)
267 l.copyLintDependencies(ctx)
268}
269
270func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) {
271 if ctx.Config().UnbundledBuild() {
272 return
273 }
274
275 var frameworkDocStubs android.Module
276 ctx.VisitAllModules(func(m android.Module) {
277 if ctx.ModuleName(m) == "framework-doc-stubs" {
278 if frameworkDocStubs == nil {
279 frameworkDocStubs = m
280 } else {
281 ctx.Errorf("lint: multiple framework-doc-stubs modules found: %s and %s",
282 ctx.ModuleSubDir(m), ctx.ModuleSubDir(frameworkDocStubs))
283 }
284 }
285 })
286
287 if frameworkDocStubs == nil {
288 if !ctx.Config().AllowMissingDependencies() {
289 ctx.Errorf("lint: missing framework-doc-stubs")
290 }
291 return
292 }
293
294 ctx.Build(pctx, android.BuildParams{
295 Rule: android.Cp,
296 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
297 Output: annotationsZipPath(ctx),
298 })
299
300 ctx.Build(pctx, android.BuildParams{
301 Rule: android.Cp,
302 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
303 Output: apiVersionsXmlPath(ctx),
304 })
305}
306
307func annotationsZipPath(ctx android.PathContext) android.WritablePath {
308 return android.PathForOutput(ctx, "lint", "annotations.zip")
309}
310
311func apiVersionsXmlPath(ctx android.PathContext) android.WritablePath {
312 return android.PathForOutput(ctx, "lint", "api_versions.xml")
313}
314
315func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) {
316 var outputs []*lintOutputs
317 var dirs []string
318 ctx.VisitAllModules(func(m android.Module) {
319 if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() {
320 return
321 }
322
323 if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() && apex.IsForPlatform() {
324 // There are stray platform variants of modules in apexes that are not available for
325 // the platform, and they sometimes can't be built. Don't depend on them.
326 return
327 }
328
329 if l, ok := m.(lintOutputIntf); ok {
330 outputs = append(outputs, l.lintOutputs())
331 }
332 })
333
334 dirs = android.SortedUniqueStrings(dirs)
335
336 zip := func(outputPath android.WritablePath, get func(*lintOutputs) android.Path) {
337 var paths android.Paths
338
339 for _, output := range outputs {
340 paths = append(paths, get(output))
341 }
342
343 sort.Slice(paths, func(i, j int) bool {
344 return paths[i].String() < paths[j].String()
345 })
346
347 rule := android.NewRuleBuilder()
348
349 rule.Command().BuiltTool(ctx, "soong_zip").
350 FlagWithOutput("-o ", outputPath).
351 FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
352 FlagWithRspFileInputList("-l ", paths)
353
354 rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
355 }
356
357 l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
358 zip(l.htmlZip, func(l *lintOutputs) android.Path { return l.html })
359
360 l.textZip = android.PathForOutput(ctx, "lint-report-text.zip")
361 zip(l.textZip, func(l *lintOutputs) android.Path { return l.text })
362
363 l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip")
364 zip(l.xmlZip, func(l *lintOutputs) android.Path { return l.xml })
365
366 ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip)
367}
368
369func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
370 ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip)
371}
372
373var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
374
375func init() {
376 android.RegisterSingletonType("lint",
377 func() android.Singleton { return &lintSingleton{} })
378}