blob: fc65c0b283c1cdf60ceb1b43f4ef87970884ec0e [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
Colin Cross8a6ed372020-07-06 11:45:51 -0700223 var annotationsZipPath, apiVersionsXMLPath android.Path
224 if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
225 annotationsZipPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/annotations.zip")
226 apiVersionsXMLPath = android.PathForSource(ctx, "prebuilts/sdk/current/public/data/api-versions.xml")
227 } else {
228 annotationsZipPath = copiedAnnotationsZipPath(ctx)
229 apiVersionsXMLPath = copiedAPIVersionsXmlPath(ctx)
230 }
231
Colin Cross014489c2020-06-02 20:09:13 -0700232 rule.Command().
233 Text("(").
Colin Crosse2221792020-07-13 13:23:00 -0700234 Flag("JAVA_OPTS=-Xmx3072m").
Colin Cross977b6a82020-06-23 10:22:49 -0700235 FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()).
Colin Cross8a6ed372020-07-06 11:45:51 -0700236 FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath).
237 FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath).
Colin Cross014489c2020-06-02 20:09:13 -0700238 Tool(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/bin/lint")).
239 Implicit(android.PathForSource(ctx, "prebuilts/cmdline-tools/tools/lib/lint-classpath.jar")).
240 Flag("--quiet").
241 FlagWithInput("--project ", projectXML).
242 FlagWithInput("--config ", lintXML).
243 FlagWithOutput("--html ", l.outputs.html).
244 FlagWithOutput("--text ", l.outputs.text).
245 FlagWithOutput("--xml ", l.outputs.xml).
246 FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
247 FlagWithArg("--java-language-level ", l.javaLanguageLevel).
248 FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
249 FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
250 Flag("--exitcode").
251 Flags(l.properties.Lint.Flags).
252 Implicits(deps).
253 Text("|| (").Text("cat").Input(l.outputs.text).Text("; exit 7)").
254 Text(")")
255
Colin Cross977b6a82020-06-23 10:22:49 -0700256 rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
Colin Cross014489c2020-06-02 20:09:13 -0700257
258 rule.Build(pctx, ctx, "lint", "lint")
259}
260
261func (l *linter) lintOutputs() *lintOutputs {
262 return &l.outputs
263}
264
265type lintOutputIntf interface {
266 lintOutputs() *lintOutputs
267}
268
269var _ lintOutputIntf = (*linter)(nil)
270
271type lintSingleton struct {
272 htmlZip android.WritablePath
273 textZip android.WritablePath
274 xmlZip android.WritablePath
275}
276
277func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) {
278 l.generateLintReportZips(ctx)
279 l.copyLintDependencies(ctx)
280}
281
282func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) {
Colin Cross8a6ed372020-07-06 11:45:51 -0700283 if ctx.Config().UnbundledBuildUsePrebuiltSdks() {
Colin Cross014489c2020-06-02 20:09:13 -0700284 return
285 }
286
287 var frameworkDocStubs android.Module
288 ctx.VisitAllModules(func(m android.Module) {
289 if ctx.ModuleName(m) == "framework-doc-stubs" {
290 if frameworkDocStubs == nil {
291 frameworkDocStubs = m
292 } else {
293 ctx.Errorf("lint: multiple framework-doc-stubs modules found: %s and %s",
294 ctx.ModuleSubDir(m), ctx.ModuleSubDir(frameworkDocStubs))
295 }
296 }
297 })
298
299 if frameworkDocStubs == nil {
300 if !ctx.Config().AllowMissingDependencies() {
301 ctx.Errorf("lint: missing framework-doc-stubs")
302 }
303 return
304 }
305
306 ctx.Build(pctx, android.BuildParams{
307 Rule: android.Cp,
308 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
Colin Cross8a6ed372020-07-06 11:45:51 -0700309 Output: copiedAnnotationsZipPath(ctx),
Colin Cross014489c2020-06-02 20:09:13 -0700310 })
311
312 ctx.Build(pctx, android.BuildParams{
313 Rule: android.Cp,
314 Input: android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
Colin Cross8a6ed372020-07-06 11:45:51 -0700315 Output: copiedAPIVersionsXmlPath(ctx),
Colin Cross014489c2020-06-02 20:09:13 -0700316 })
317}
318
Colin Cross8a6ed372020-07-06 11:45:51 -0700319func copiedAnnotationsZipPath(ctx android.PathContext) android.WritablePath {
Colin Cross014489c2020-06-02 20:09:13 -0700320 return android.PathForOutput(ctx, "lint", "annotations.zip")
321}
322
Colin Cross8a6ed372020-07-06 11:45:51 -0700323func copiedAPIVersionsXmlPath(ctx android.PathContext) android.WritablePath {
Colin Cross014489c2020-06-02 20:09:13 -0700324 return android.PathForOutput(ctx, "lint", "api_versions.xml")
325}
326
327func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) {
Colin Cross8a6ed372020-07-06 11:45:51 -0700328 if ctx.Config().UnbundledBuild() {
329 return
330 }
331
Colin Cross014489c2020-06-02 20:09:13 -0700332 var outputs []*lintOutputs
333 var dirs []string
334 ctx.VisitAllModules(func(m android.Module) {
335 if ctx.Config().EmbeddedInMake() && !m.ExportedToMake() {
336 return
337 }
338
339 if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() && apex.IsForPlatform() {
340 // There are stray platform variants of modules in apexes that are not available for
341 // the platform, and they sometimes can't be built. Don't depend on them.
342 return
343 }
344
345 if l, ok := m.(lintOutputIntf); ok {
346 outputs = append(outputs, l.lintOutputs())
347 }
348 })
349
350 dirs = android.SortedUniqueStrings(dirs)
351
352 zip := func(outputPath android.WritablePath, get func(*lintOutputs) android.Path) {
353 var paths android.Paths
354
355 for _, output := range outputs {
356 paths = append(paths, get(output))
357 }
358
359 sort.Slice(paths, func(i, j int) bool {
360 return paths[i].String() < paths[j].String()
361 })
362
363 rule := android.NewRuleBuilder()
364
365 rule.Command().BuiltTool(ctx, "soong_zip").
366 FlagWithOutput("-o ", outputPath).
367 FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
368 FlagWithRspFileInputList("-l ", paths)
369
370 rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
371 }
372
373 l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
374 zip(l.htmlZip, func(l *lintOutputs) android.Path { return l.html })
375
376 l.textZip = android.PathForOutput(ctx, "lint-report-text.zip")
377 zip(l.textZip, func(l *lintOutputs) android.Path { return l.text })
378
379 l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip")
380 zip(l.xmlZip, func(l *lintOutputs) android.Path { return l.xml })
381
382 ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip)
383}
384
385func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
Colin Cross8a6ed372020-07-06 11:45:51 -0700386 if !ctx.Config().UnbundledBuild() {
387 ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip)
388 }
Colin Cross014489c2020-06-02 20:09:13 -0700389}
390
391var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
392
393func init() {
394 android.RegisterSingletonType("lint",
395 func() android.Singleton { return &lintSingleton{} })
396}