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