blob: b73d6a51a480ba019bd18df4748611fe5b333531 [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
Colin Crossc31efeb2020-06-23 10:25:26 -0700158 cmd.FlagWithArg("--root_dir ", "$PWD")
159
160 // The cache tag in project.xml is relative to the root dir, or the project.xml file if
161 // the root dir is not set.
162 cmd.FlagWithArg("--cache_dir ", cacheDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700163
164 cmd.FlagWithInput("@",
165 android.PathForSource(ctx, "build/soong/java/lint_defaults.txt"))
166
167 cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks)
168 cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks)
169 cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
170 cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
171
Colin Cross977b6a82020-06-23 10:22:49 -0700172 return projectXMLPath, configXMLPath, cacheDir, homeDir, deps
Colin Cross014489c2020-06-02 20:09:13 -0700173}
174
175// generateManifest adds a command to the rule to write a dummy manifest cat contains the
176// minSdkVersion and targetSdkVersion for modules (like java_library) that don't have a manifest.
177func (l *linter) generateManifest(ctx android.ModuleContext, rule *android.RuleBuilder) android.Path {
178 manifestPath := android.PathForModuleOut(ctx, "lint", "AndroidManifest.xml")
179
180 rule.Command().Text("(").
181 Text(`echo "<?xml version='1.0' encoding='utf-8'?>" &&`).
182 Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`).
183 Text(`echo " android:versionCode='1' android:versionName='1' >" &&`).
184 Textf(`echo " <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`,
185 l.minSdkVersion, l.targetSdkVersion).
186 Text(`echo "</manifest>"`).
187 Text(") >").Output(manifestPath)
188
189 return manifestPath
190}
191
192func (l *linter) lint(ctx android.ModuleContext) {
193 if !l.enabled() {
194 return
195 }
196
Colin Cross92e4b462020-06-18 15:56:48 -0700197 extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag)
198 for _, extraLintCheckModule := range extraLintCheckModules {
199 if dep, ok := extraLintCheckModule.(Dependency); ok {
200 l.extraLintCheckJars = append(l.extraLintCheckJars, dep.ImplementationAndResourcesJars()...)
201 } else {
202 ctx.PropertyErrorf("lint.extra_check_modules",
203 "%s is not a java module", ctx.OtherModuleName(extraLintCheckModule))
204 }
205 }
206
Colin Cross014489c2020-06-02 20:09:13 -0700207 rule := android.NewRuleBuilder()
208
209 if l.manifest == nil {
210 manifest := l.generateManifest(ctx, rule)
211 l.manifest = manifest
212 }
213
Colin Cross977b6a82020-06-23 10:22:49 -0700214 projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule)
Colin Cross014489c2020-06-02 20:09:13 -0700215
216 l.outputs.html = android.PathForModuleOut(ctx, "lint-report.html")
217 l.outputs.text = android.PathForModuleOut(ctx, "lint-report.txt")
218 l.outputs.xml = android.PathForModuleOut(ctx, "lint-report.xml")
219
Colin Cross977b6a82020-06-23 10:22:49 -0700220 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
221 rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700222
223 rule.Command().
224 Text("(").
225 Flag("JAVA_OPTS=-Xmx2048m").
Colin Cross977b6a82020-06-23 10:22:49 -0700226 FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()).
Colin Cross014489c2020-06-02 20:09:13 -0700227 FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath(ctx)).
228 FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXmlPath(ctx)).
229 Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")).
230 Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")).
231 Flag("--quiet").
232 FlagWithInput("--project ", projectXML).
233 FlagWithInput("--config ", lintXML).
234 FlagWithOutput("--html ", l.outputs.html).
235 FlagWithOutput("--text ", l.outputs.text).
236 FlagWithOutput("--xml ", l.outputs.xml).
237 FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
238 FlagWithArg("--java-language-level ", l.javaLanguageLevel).
239 FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
240 FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
241 Flag("--exitcode").
242 Flags(l.properties.Lint.Flags).
243 Implicits(deps).
244 Text("|| (").Text("cat").Input(l.outputs.text).Text("; exit 7)").
245 Text(")")
246
Colin Cross977b6a82020-06-23 10:22:49 -0700247 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700248
249 rule.Build(pctx, ctx, "lint", "lint")
250}
251
252func (l *linter) lintOutputs() *lintOutputs {
253 return &l.outputs
254}
255
256type lintOutputIntf interface {
257 lintOutputs() *lintOutputs
258}
259
260var _ lintOutputIntf = (*linter)(nil)
261
262type lintSingleton struct {
263 htmlZip android.WritablePath
264 textZip android.WritablePath
265 xmlZip android.WritablePath
266}
267
268func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) {
269 l.generateLintReportZips(ctx)
270 l.copyLintDependencies(ctx)
271}
272
273func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) {
274 if ctx.Config().UnbundledBuild() {
275 return
276 }
277
278 var frameworkDocStubs android.Module
279 ctx.VisitAllModules(func(m android.Module) {
280 if ctx.ModuleName(m) == "framework-doc-stubs" {
281 if frameworkDocStubs == nil {
282 frameworkDocStubs = m
283 } else {
284 ctx.Errorf("lint: multiple framework-doc-stubs modules found: %s and %s",
285 ctx.ModuleSubDir(m), ctx.ModuleSubDir(frameworkDocStubs))
286 }
287 }
288 })
289
290 if frameworkDocStubs == nil {
291 if !ctx.Config().AllowMissingDependencies() {
292 ctx.Errorf("lint: missing framework-doc-stubs")
293 }
294 return
295 }
296
297 ctx.Build(pctx, android.BuildParams{
298 Rule: android.Cp,
299 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
300 Output: annotationsZipPath(ctx),
301 })
302
303 ctx.Build(pctx, android.BuildParams{
304 Rule: android.Cp,
305 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
306 Output: apiVersionsXmlPath(ctx),
307 })
308}
309
310func annotationsZipPath(ctx android.PathContext) android.WritablePath {
311 return android.PathForOutput(ctx, "lint", "annotations.zip")
312}
313
314func apiVersionsXmlPath(ctx android.PathContext) android.WritablePath {
315 return android.PathForOutput(ctx, "lint", "api_versions.xml")
316}
317
318func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) {
319 var outputs []*lintOutputs
320 var dirs []string
321 ctx.VisitAllModules(func(m android.Module) {
322 if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() {
323 return
324 }
325
326 if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() && apex.IsForPlatform() {
327 // There are stray platform variants of modules in apexes that are not available for
328 // the platform, and they sometimes can't be built. Don't depend on them.
329 return
330 }
331
332 if l, ok := m.(lintOutputIntf); ok {
333 outputs = append(outputs, l.lintOutputs())
334 }
335 })
336
337 dirs = android.SortedUniqueStrings(dirs)
338
339 zip := func(outputPath android.WritablePath, get func(*lintOutputs) android.Path) {
340 var paths android.Paths
341
342 for _, output := range outputs {
343 paths = append(paths, get(output))
344 }
345
346 sort.Slice(paths, func(i, j int) bool {
347 return paths[i].String() < paths[j].String()
348 })
349
350 rule := android.NewRuleBuilder()
351
352 rule.Command().BuiltTool(ctx, "soong_zip").
353 FlagWithOutput("-o ", outputPath).
354 FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
355 FlagWithRspFileInputList("-l ", paths)
356
357 rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
358 }
359
360 l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
361 zip(l.htmlZip, func(l *lintOutputs) android.Path { return l.html })
362
363 l.textZip = android.PathForOutput(ctx, "lint-report-text.zip")
364 zip(l.textZip, func(l *lintOutputs) android.Path { return l.text })
365
366 l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip")
367 zip(l.xmlZip, func(l *lintOutputs) android.Path { return l.xml })
368
369 ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip)
370}
371
372func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
373 ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip)
374}
375
376var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
377
378func init() {
379 android.RegisterSingletonType("lint",
380 func() android.Singleton { return &lintSingleton{} })
381}