xref: /aosp_15_r20/build/soong/java/lint.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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	"github.com/google/blueprint"
23	"github.com/google/blueprint/depset"
24	"github.com/google/blueprint/proptools"
25
26	"android/soong/android"
27	"android/soong/java/config"
28	"android/soong/remoteexec"
29)
30
31// lint checks automatically enforced for modules that have different min_sdk_version than
32// sdk_version
33var updatabilityChecks = []string{"NewApi"}
34
35type LintProperties struct {
36	// Controls for running Android Lint on the module.
37	Lint struct {
38
39		// If true, run Android Lint on the module.  Defaults to true.
40		Enabled *bool
41
42		// Flags to pass to the Android Lint tool.
43		Flags []string
44
45		// Checks that should be treated as fatal.
46		Fatal_checks []string
47
48		// Checks that should be treated as errors.
49		Error_checks []string
50
51		// Checks that should be treated as warnings.
52		Warning_checks []string
53
54		// Checks that should be skipped.
55		Disabled_checks []string
56
57		// Modules that provide extra lint checks
58		Extra_check_modules []string
59
60		// The lint baseline file to use. If specified, lint warnings listed in this file will be
61		// suppressed during lint checks.
62		Baseline_filename *string
63
64		// If true, baselining updatability lint checks (e.g. NewApi) is prohibited. Defaults to false.
65		Strict_updatability_linting *bool
66
67		// Treat the code in this module as test code for @VisibleForTesting enforcement.
68		// This will be true by default for test module types, false otherwise.
69		// If soong gets support for testonly, this flag should be replaced with that.
70		Test *bool
71
72		// Whether to ignore the exit code of Android lint. This is the --exit_code
73		// option. Defaults to false.
74		Suppress_exit_code *bool
75	}
76}
77
78type linter struct {
79	name                    string
80	manifest                android.Path
81	mergedManifest          android.Path
82	srcs                    android.Paths
83	srcJars                 android.Paths
84	resources               android.Paths
85	classpath               android.Paths
86	classes                 android.Path
87	extraLintCheckJars      android.Paths
88	library                 bool
89	minSdkVersion           android.ApiLevel
90	targetSdkVersion        android.ApiLevel
91	compileSdkVersion       android.ApiLevel
92	compileSdkKind          android.SdkKind
93	javaLanguageLevel       string
94	kotlinLanguageLevel     string
95	properties              LintProperties
96	extraMainlineLintErrors []string
97	compile_data            android.Paths
98
99	reports android.Paths
100
101	buildModuleReportZip bool
102}
103
104type LintDepSets struct {
105	HTML, Text, XML, Baseline depset.DepSet[android.Path]
106}
107
108type LintDepSetsBuilder struct {
109	HTML, Text, XML, Baseline *depset.Builder[android.Path]
110}
111
112func NewLintDepSetBuilder() LintDepSetsBuilder {
113	return LintDepSetsBuilder{
114		HTML:     depset.NewBuilder[android.Path](depset.POSTORDER),
115		Text:     depset.NewBuilder[android.Path](depset.POSTORDER),
116		XML:      depset.NewBuilder[android.Path](depset.POSTORDER),
117		Baseline: depset.NewBuilder[android.Path](depset.POSTORDER),
118	}
119}
120
121func (l LintDepSetsBuilder) Direct(html, text, xml android.Path, baseline android.OptionalPath) LintDepSetsBuilder {
122	l.HTML.Direct(html)
123	l.Text.Direct(text)
124	l.XML.Direct(xml)
125	if baseline.Valid() {
126		l.Baseline.Direct(baseline.Path())
127	}
128	return l
129}
130
131func (l LintDepSetsBuilder) Transitive(info *LintInfo) LintDepSetsBuilder {
132	l.HTML.Transitive(info.TransitiveHTML)
133	l.Text.Transitive(info.TransitiveText)
134	l.XML.Transitive(info.TransitiveXML)
135	l.Baseline.Transitive(info.TransitiveBaseline)
136	return l
137}
138
139func (l LintDepSetsBuilder) Build() LintDepSets {
140	return LintDepSets{
141		HTML:     l.HTML.Build(),
142		Text:     l.Text.Build(),
143		XML:      l.XML.Build(),
144		Baseline: l.Baseline.Build(),
145	}
146}
147
148type lintDatabaseFiles struct {
149	apiVersionsModule       string
150	apiVersionsCopiedName   string
151	apiVersionsPrebuiltPath string
152	annotationsModule       string
153	annotationCopiedName    string
154	annotationPrebuiltpath  string
155}
156
157var allLintDatabasefiles = map[android.SdkKind]lintDatabaseFiles{
158	android.SdkPublic: {
159		apiVersionsModule:       "api_versions_public",
160		apiVersionsCopiedName:   "api_versions_public.xml",
161		apiVersionsPrebuiltPath: "prebuilts/sdk/current/public/data/api-versions.xml",
162		annotationsModule:       "sdk-annotations.zip",
163		annotationCopiedName:    "annotations-public.zip",
164		annotationPrebuiltpath:  "prebuilts/sdk/current/public/data/annotations.zip",
165	},
166	android.SdkSystem: {
167		apiVersionsModule:       "api_versions_system",
168		apiVersionsCopiedName:   "api_versions_system.xml",
169		apiVersionsPrebuiltPath: "prebuilts/sdk/current/system/data/api-versions.xml",
170		annotationsModule:       "sdk-annotations-system.zip",
171		annotationCopiedName:    "annotations-system.zip",
172		annotationPrebuiltpath:  "prebuilts/sdk/current/system/data/annotations.zip",
173	},
174	android.SdkModule: {
175		apiVersionsModule:       "api_versions_module_lib",
176		apiVersionsCopiedName:   "api_versions_module_lib.xml",
177		apiVersionsPrebuiltPath: "prebuilts/sdk/current/module-lib/data/api-versions.xml",
178		annotationsModule:       "sdk-annotations-module-lib.zip",
179		annotationCopiedName:    "annotations-module-lib.zip",
180		annotationPrebuiltpath:  "prebuilts/sdk/current/module-lib/data/annotations.zip",
181	},
182	android.SdkSystemServer: {
183		apiVersionsModule:       "api_versions_system_server",
184		apiVersionsCopiedName:   "api_versions_system_server.xml",
185		apiVersionsPrebuiltPath: "prebuilts/sdk/current/system-server/data/api-versions.xml",
186		annotationsModule:       "sdk-annotations-system-server.zip",
187		annotationCopiedName:    "annotations-system-server.zip",
188		annotationPrebuiltpath:  "prebuilts/sdk/current/system-server/data/annotations.zip",
189	},
190}
191
192var LintProvider = blueprint.NewProvider[*LintInfo]()
193
194type LintInfo struct {
195	HTML              android.Path
196	Text              android.Path
197	XML               android.Path
198	ReferenceBaseline android.Path
199
200	TransitiveHTML     depset.DepSet[android.Path]
201	TransitiveText     depset.DepSet[android.Path]
202	TransitiveXML      depset.DepSet[android.Path]
203	TransitiveBaseline depset.DepSet[android.Path]
204}
205
206func (l *linter) enabled() bool {
207	return BoolDefault(l.properties.Lint.Enabled, true)
208}
209
210func (l *linter) deps(ctx android.BottomUpMutatorContext) {
211	if !l.enabled() {
212		return
213	}
214
215	extraCheckModules := l.properties.Lint.Extra_check_modules
216
217	if extraCheckModulesEnv := ctx.Config().Getenv("ANDROID_LINT_CHECK_EXTRA_MODULES"); extraCheckModulesEnv != "" {
218		extraCheckModules = append(extraCheckModules, strings.Split(extraCheckModulesEnv, ",")...)
219	}
220
221	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(),
222		extraLintCheckTag, extraCheckModules...)
223}
224
225// lintPaths contains the paths to lint's inputs and outputs to make it easier to pass them
226// around.
227type lintPaths struct {
228	projectXML android.WritablePath
229	configXML  android.WritablePath
230	cacheDir   android.WritablePath
231	homeDir    android.WritablePath
232	srcjarDir  android.WritablePath
233}
234
235func lintRBEExecStrategy(ctx android.ModuleContext) string {
236	return ctx.Config().GetenvWithDefault("RBE_LINT_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
237}
238
239func (l *linter) writeLintProjectXML(ctx android.ModuleContext, rule *android.RuleBuilder, srcsList android.Path,
240	baselines android.Paths) lintPaths {
241
242	projectXMLPath := android.PathForModuleOut(ctx, "lint", "project.xml")
243	// Lint looks for a lint.xml file next to the project.xml file, give it one.
244	configXMLPath := android.PathForModuleOut(ctx, "lint", "lint.xml")
245	cacheDir := android.PathForModuleOut(ctx, "lint", "cache")
246	homeDir := android.PathForModuleOut(ctx, "lint", "home")
247
248	srcJarDir := android.PathForModuleOut(ctx, "lint", "srcjars")
249	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
250
251	cmd := rule.Command().
252		BuiltTool("lint_project_xml").
253		FlagWithOutput("--project_out ", projectXMLPath).
254		FlagWithOutput("--config_out ", configXMLPath).
255		FlagWithArg("--name ", ctx.ModuleName())
256
257	if l.library {
258		cmd.Flag("--library")
259	}
260	if proptools.BoolDefault(l.properties.Lint.Test, false) {
261		cmd.Flag("--test")
262	}
263	if l.manifest != nil {
264		cmd.FlagWithInput("--manifest ", l.manifest)
265	}
266	if l.mergedManifest != nil {
267		cmd.FlagWithInput("--merged_manifest ", l.mergedManifest)
268	}
269
270	// TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
271	// lint separately.
272	cmd.FlagWithInput("--srcs ", srcsList)
273
274	cmd.FlagWithInput("--generated_srcs ", srcJarList)
275
276	if len(l.resources) > 0 {
277		resourcesList := android.PathForModuleOut(ctx, "lint-resources.list")
278		cmd.FlagWithRspFileInputList("--resources ", resourcesList, l.resources)
279	}
280
281	if l.classes != nil {
282		cmd.FlagWithInput("--classes ", l.classes)
283	}
284
285	cmd.FlagForEachInput("--classpath ", l.classpath)
286
287	cmd.FlagForEachInput("--extra_checks_jar ", l.extraLintCheckJars)
288
289	cmd.FlagWithArg("--root_dir ", "$PWD")
290
291	// The cache tag in project.xml is relative to the root dir, or the project.xml file if
292	// the root dir is not set.
293	cmd.FlagWithArg("--cache_dir ", cacheDir.String())
294
295	cmd.FlagWithInput("@",
296		android.PathForSource(ctx, "build/soong/java/lint_defaults.txt"))
297
298	cmd.FlagForEachArg("--error_check ", l.extraMainlineLintErrors)
299	cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks)
300	cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks)
301	cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
302	cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
303
304	if Bool(l.properties.Lint.Strict_updatability_linting) && len(baselines) > 0 {
305		// Verify the module does not baseline issues that endanger safe updatability.
306		strictUpdatabilityChecksOutputFile := VerifyStrictUpdatabilityChecks(ctx, baselines)
307		cmd.Validation(strictUpdatabilityChecksOutputFile)
308	}
309
310	return lintPaths{
311		projectXML: projectXMLPath,
312		configXML:  configXMLPath,
313		cacheDir:   cacheDir,
314		homeDir:    homeDir,
315	}
316
317}
318
319func VerifyStrictUpdatabilityChecks(ctx android.ModuleContext, baselines android.Paths) android.Path {
320	rule := android.NewRuleBuilder(pctx, ctx)
321	baselineRspFile := android.PathForModuleOut(ctx, "lint_strict_updatability_check_baselines.rsp")
322	outputFile := android.PathForModuleOut(ctx, "lint_strict_updatability_check.stamp")
323	rule.Command().Text("rm -f").Output(outputFile)
324	rule.Command().
325		BuiltTool("lint_strict_updatability_checks").
326		FlagWithArg("--name ", ctx.ModuleName()).
327		FlagWithRspFileInputList("--baselines ", baselineRspFile, baselines).
328		FlagForEachArg("--disallowed_issues ", updatabilityChecks)
329	rule.Command().Text("touch").Output(outputFile)
330	rule.Build("lint_strict_updatability_checks", "lint strict updatability checks")
331
332	return outputFile
333}
334
335// generateManifest adds a command to the rule to write a simple manifest that contains the
336// minSdkVersion and targetSdkVersion for modules (like java_library) that don't have a manifest.
337func (l *linter) generateManifest(ctx android.ModuleContext, rule *android.RuleBuilder) android.WritablePath {
338	manifestPath := android.PathForModuleOut(ctx, "lint", "AndroidManifest.xml")
339
340	rule.Command().Text("(").
341		Text(`echo "<?xml version='1.0' encoding='utf-8'?>" &&`).
342		Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`).
343		Text(`echo "    android:versionCode='1' android:versionName='1' >" &&`).
344		Textf(`echo "  <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`,
345			l.minSdkVersion.String(), l.targetSdkVersion.String()).
346		Text(`echo "</manifest>"`).
347		Text(") >").Output(manifestPath)
348
349	return manifestPath
350}
351
352func (l *linter) lint(ctx android.ModuleContext) {
353	if !l.enabled() {
354		return
355	}
356
357	for _, flag := range l.properties.Lint.Flags {
358		if strings.Contains(flag, "--disable") || strings.Contains(flag, "--enable") || strings.Contains(flag, "--check") {
359			ctx.PropertyErrorf("lint.flags", "Don't use --disable, --enable, or --check in the flags field, instead use the dedicated disabled_checks, warning_checks, error_checks, or fatal_checks fields")
360		}
361	}
362
363	if l.minSdkVersion.CompareTo(l.compileSdkVersion) == -1 {
364		l.extraMainlineLintErrors = append(l.extraMainlineLintErrors, updatabilityChecks...)
365		// Skip lint warning checks for NewApi warnings for libcore where they come from source
366		// files that reference the API they are adding (b/208656169).
367		if !strings.HasPrefix(ctx.ModuleDir(), "libcore") {
368			_, filtered := android.FilterList(l.properties.Lint.Warning_checks, updatabilityChecks)
369
370			if len(filtered) != 0 {
371				ctx.PropertyErrorf("lint.warning_checks",
372					"Can't treat %v checks as warnings if min_sdk_version is different from sdk_version.", filtered)
373			}
374		}
375
376		_, filtered := android.FilterList(l.properties.Lint.Disabled_checks, updatabilityChecks)
377		if len(filtered) != 0 {
378			ctx.PropertyErrorf("lint.disabled_checks",
379				"Can't disable %v checks if min_sdk_version is different from sdk_version.", filtered)
380		}
381
382		// TODO(b/238784089): Remove this workaround when the NewApi issues have been addressed in PermissionController
383		if ctx.ModuleName() == "PermissionController" {
384			l.extraMainlineLintErrors = android.FilterListPred(l.extraMainlineLintErrors, func(s string) bool {
385				return s != "NewApi"
386			})
387			l.properties.Lint.Warning_checks = append(l.properties.Lint.Warning_checks, "NewApi")
388		}
389	}
390
391	extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag)
392	for _, extraLintCheckModule := range extraLintCheckModules {
393		if dep, ok := android.OtherModuleProvider(ctx, extraLintCheckModule, JavaInfoProvider); ok {
394			l.extraLintCheckJars = append(l.extraLintCheckJars, dep.ImplementationAndResourcesJars...)
395		} else {
396			ctx.PropertyErrorf("lint.extra_check_modules",
397				"%s is not a java module", ctx.OtherModuleName(extraLintCheckModule))
398		}
399	}
400
401	l.extraLintCheckJars = append(l.extraLintCheckJars, android.PathForSource(ctx,
402		"prebuilts/cmdline-tools/AndroidGlobalLintChecker.jar"))
403
404	var baseline android.OptionalPath
405	if l.properties.Lint.Baseline_filename != nil {
406		baseline = android.OptionalPathForPath(android.PathForModuleSrc(ctx, *l.properties.Lint.Baseline_filename))
407	}
408
409	html := android.PathForModuleOut(ctx, "lint", "lint-report.html")
410	text := android.PathForModuleOut(ctx, "lint", "lint-report.txt")
411	xml := android.PathForModuleOut(ctx, "lint", "lint-report.xml")
412	referenceBaseline := android.PathForModuleOut(ctx, "lint", "lint-baseline.xml")
413
414	depSetsBuilder := NewLintDepSetBuilder().Direct(html, text, xml, baseline)
415
416	ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) {
417		if info, ok := android.OtherModuleProvider(ctx, dep, LintProvider); ok {
418			depSetsBuilder.Transitive(info)
419		}
420	})
421
422	depSets := depSetsBuilder.Build()
423
424	rule := android.NewRuleBuilder(pctx, ctx).
425		Sbox(android.PathForModuleOut(ctx, "lint"),
426			android.PathForModuleOut(ctx, "lint.sbox.textproto")).
427		SandboxInputs()
428
429	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_LINT") {
430		pool := ctx.Config().GetenvWithDefault("RBE_LINT_POOL", "java16")
431		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
432		rule.Rewrapper(&remoteexec.REParams{
433			Labels:          map[string]string{"type": "tool", "name": "lint"},
434			ExecStrategy:    lintRBEExecStrategy(ctx),
435			ToolchainInputs: []string{config.JavaCmd(ctx).String()},
436			Platform:        map[string]string{remoteexec.PoolKey: pool},
437		})
438	}
439
440	if l.manifest == nil {
441		manifest := l.generateManifest(ctx, rule)
442		l.manifest = manifest
443		rule.Temporary(manifest)
444	}
445
446	srcsList := android.PathForModuleOut(ctx, "lint", "lint-srcs.list")
447	srcsListRsp := android.PathForModuleOut(ctx, "lint-srcs.list.rsp")
448	rule.Command().Text("cp").FlagWithRspFileInputList("", srcsListRsp, l.srcs).Output(srcsList).Implicits(l.compile_data)
449
450	baselines := depSets.Baseline.ToList()
451
452	lintPaths := l.writeLintProjectXML(ctx, rule, srcsList, baselines)
453
454	rule.Command().Text("rm -rf").Flag(lintPaths.cacheDir.String()).Flag(lintPaths.homeDir.String())
455	rule.Command().Text("mkdir -p").Flag(lintPaths.cacheDir.String()).Flag(lintPaths.homeDir.String())
456	rule.Command().Text("rm -f").Output(html).Output(text).Output(xml)
457
458	files, ok := allLintDatabasefiles[l.compileSdkKind]
459	if !ok {
460		files = allLintDatabasefiles[android.SdkPublic]
461	}
462	var annotationsZipPath, apiVersionsXMLPath android.Path
463	if ctx.Config().AlwaysUsePrebuiltSdks() {
464		annotationsZipPath = android.PathForSource(ctx, files.annotationPrebuiltpath)
465		apiVersionsXMLPath = android.PathForSource(ctx, files.apiVersionsPrebuiltPath)
466	} else {
467		annotationsZipPath = copiedLintDatabaseFilesPath(ctx, files.annotationCopiedName)
468		apiVersionsXMLPath = copiedLintDatabaseFilesPath(ctx, files.apiVersionsCopiedName)
469	}
470
471	cmd := rule.Command()
472
473	cmd.Flag(`JAVA_OPTS="-Xmx3072m --add-opens java.base/java.util=ALL-UNNAMED"`).
474		FlagWithArg("ANDROID_SDK_HOME=", lintPaths.homeDir.String()).
475		FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath).
476		FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath)
477
478	cmd.BuiltTool("lint").ImplicitTool(ctx.Config().HostJavaToolPath(ctx, "lint.jar")).
479		Flag("--quiet").
480		Flag("--include-aosp-issues").
481		FlagWithInput("--project ", lintPaths.projectXML).
482		FlagWithInput("--config ", lintPaths.configXML).
483		FlagWithOutput("--html ", html).
484		FlagWithOutput("--text ", text).
485		FlagWithOutput("--xml ", xml).
486		FlagWithArg("--compile-sdk-version ", l.compileSdkVersion.String()).
487		FlagWithArg("--java-language-level ", l.javaLanguageLevel).
488		FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
489		FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
490		Flag("--apply-suggestions"). // applies suggested fixes to files in the sandbox
491		Flags(l.properties.Lint.Flags).
492		Implicit(annotationsZipPath).
493		Implicit(apiVersionsXMLPath)
494
495	rule.Temporary(lintPaths.projectXML)
496	rule.Temporary(lintPaths.configXML)
497
498	suppressExitCode := BoolDefault(l.properties.Lint.Suppress_exit_code, false)
499	if exitCode := ctx.Config().Getenv("ANDROID_LINT_SUPPRESS_EXIT_CODE"); exitCode == "" && !suppressExitCode {
500		cmd.Flag("--exitcode")
501	}
502
503	if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" {
504		cmd.FlagWithArg("--check ", checkOnly)
505	}
506
507	if baseline.Valid() {
508		cmd.FlagWithInput("--baseline ", baseline.Path())
509	}
510
511	cmd.FlagWithOutput("--write-reference-baseline ", referenceBaseline)
512
513	cmd.Text("; EXITCODE=$?; ")
514
515	// The sources in the sandbox may have been modified by --apply-suggestions, zip them up and
516	// export them out of the sandbox.  Do this before exiting so that the suggestions exit even after
517	// a fatal error.
518	cmd.BuiltTool("soong_zip").
519		FlagWithOutput("-o ", android.PathForModuleOut(ctx, "lint", "suggested-fixes.zip")).
520		FlagWithArg("-C ", cmd.PathForInput(android.PathForSource(ctx))).
521		FlagWithInput("-r ", srcsList)
522
523	cmd.Text("; if [ $EXITCODE != 0 ]; then if [ -e").Input(text).Text("]; then cat").Input(text).Text("; fi; exit $EXITCODE; fi")
524
525	rule.Command().Text("rm -rf").Flag(lintPaths.cacheDir.String()).Flag(lintPaths.homeDir.String())
526
527	// The HTML output contains a date, remove it to make the output deterministic.
528	rule.Command().Text(`sed -i.tmp -e 's|Check performed at .*\(</nav>\)|\1|'`).Output(html)
529
530	rule.Build("lint", "lint")
531
532	android.SetProvider(ctx, LintProvider, &LintInfo{
533		HTML:              html,
534		Text:              text,
535		XML:               xml,
536		ReferenceBaseline: referenceBaseline,
537
538		TransitiveHTML:     depSets.HTML,
539		TransitiveText:     depSets.Text,
540		TransitiveXML:      depSets.XML,
541		TransitiveBaseline: depSets.Baseline,
542	})
543
544	if l.buildModuleReportZip {
545		l.reports = BuildModuleLintReportZips(ctx, depSets, nil)
546	}
547
548	// Create a per-module phony target to run the lint check.
549	phonyName := ctx.ModuleName() + "-lint"
550	ctx.Phony(phonyName, xml)
551
552	ctx.SetOutputFiles(android.Paths{xml}, ".lint")
553}
554
555func BuildModuleLintReportZips(ctx android.ModuleContext, depSets LintDepSets, validations android.Paths) android.Paths {
556	htmlList := android.SortedUniquePaths(depSets.HTML.ToList())
557	textList := android.SortedUniquePaths(depSets.Text.ToList())
558	xmlList := android.SortedUniquePaths(depSets.XML.ToList())
559
560	if len(htmlList) == 0 && len(textList) == 0 && len(xmlList) == 0 {
561		return nil
562	}
563
564	htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip")
565	lintZip(ctx, htmlList, htmlZip, validations)
566
567	textZip := android.PathForModuleOut(ctx, "lint-report-text.zip")
568	lintZip(ctx, textList, textZip, validations)
569
570	xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip")
571	lintZip(ctx, xmlList, xmlZip, validations)
572
573	return android.Paths{htmlZip, textZip, xmlZip}
574}
575
576type lintSingleton struct {
577	htmlZip              android.WritablePath
578	textZip              android.WritablePath
579	xmlZip               android.WritablePath
580	referenceBaselineZip android.WritablePath
581}
582
583func (l *lintSingleton) GenerateBuildActions(ctx android.SingletonContext) {
584	l.generateLintReportZips(ctx)
585	l.copyLintDependencies(ctx)
586}
587
588func findModuleOrErr(ctx android.SingletonContext, moduleName string) android.Module {
589	var res android.Module
590	ctx.VisitAllModules(func(m android.Module) {
591		if ctx.ModuleName(m) == moduleName {
592			if res == nil {
593				res = m
594			} else {
595				ctx.Errorf("lint: multiple %s modules found: %s and %s", moduleName,
596					ctx.ModuleSubDir(m), ctx.ModuleSubDir(res))
597			}
598		}
599	})
600	return res
601}
602
603func (l *lintSingleton) copyLintDependencies(ctx android.SingletonContext) {
604	if ctx.Config().AlwaysUsePrebuiltSdks() {
605		return
606	}
607
608	for _, sdk := range android.SortedKeys(allLintDatabasefiles) {
609		files := allLintDatabasefiles[sdk]
610		apiVersionsDb := findModuleOrErr(ctx, files.apiVersionsModule)
611		if apiVersionsDb == nil {
612			if !ctx.Config().AllowMissingDependencies() {
613				ctx.Errorf("lint: missing module %s", files.apiVersionsModule)
614			}
615			return
616		}
617
618		sdkAnnotations := findModuleOrErr(ctx, files.annotationsModule)
619		if sdkAnnotations == nil {
620			if !ctx.Config().AllowMissingDependencies() {
621				ctx.Errorf("lint: missing module %s", files.annotationsModule)
622			}
623			return
624		}
625
626		ctx.Build(pctx, android.BuildParams{
627			Rule:   android.CpIfChanged,
628			Input:  android.OutputFileForModule(ctx, sdkAnnotations, ""),
629			Output: copiedLintDatabaseFilesPath(ctx, files.annotationCopiedName),
630		})
631
632		ctx.Build(pctx, android.BuildParams{
633			Rule:   android.CpIfChanged,
634			Input:  android.OutputFileForModule(ctx, apiVersionsDb, ".api_versions.xml"),
635			Output: copiedLintDatabaseFilesPath(ctx, files.apiVersionsCopiedName),
636		})
637	}
638}
639
640func copiedLintDatabaseFilesPath(ctx android.PathContext, name string) android.WritablePath {
641	return android.PathForOutput(ctx, "lint", name)
642}
643
644func (l *lintSingleton) generateLintReportZips(ctx android.SingletonContext) {
645	if ctx.Config().UnbundledBuild() {
646		return
647	}
648
649	var outputs []*LintInfo
650	var dirs []string
651	ctx.VisitAllModules(func(m android.Module) {
652		if ctx.Config().KatiEnabled() && !m.ExportedToMake() {
653			return
654		}
655
656		if apex, ok := m.(android.ApexModule); ok && apex.NotAvailableForPlatform() {
657			apexInfo, _ := android.OtherModuleProvider(ctx, m, android.ApexInfoProvider)
658			if apexInfo.IsForPlatform() {
659				// There are stray platform variants of modules in apexes that are not available for
660				// the platform, and they sometimes can't be built.  Don't depend on them.
661				return
662			}
663		}
664
665		if lintInfo, ok := android.OtherModuleProvider(ctx, m, LintProvider); ok {
666			outputs = append(outputs, lintInfo)
667		}
668	})
669
670	dirs = android.SortedUniqueStrings(dirs)
671
672	zip := func(outputPath android.WritablePath, get func(*LintInfo) android.Path) {
673		var paths android.Paths
674
675		for _, output := range outputs {
676			if p := get(output); p != nil {
677				paths = append(paths, p)
678			}
679		}
680
681		lintZip(ctx, paths, outputPath, nil)
682	}
683
684	l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
685	zip(l.htmlZip, func(l *LintInfo) android.Path { return l.HTML })
686
687	l.textZip = android.PathForOutput(ctx, "lint-report-text.zip")
688	zip(l.textZip, func(l *LintInfo) android.Path { return l.Text })
689
690	l.xmlZip = android.PathForOutput(ctx, "lint-report-xml.zip")
691	zip(l.xmlZip, func(l *LintInfo) android.Path { return l.XML })
692
693	l.referenceBaselineZip = android.PathForOutput(ctx, "lint-report-reference-baselines.zip")
694	zip(l.referenceBaselineZip, func(l *LintInfo) android.Path { return l.ReferenceBaseline })
695
696	ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip, l.referenceBaselineZip)
697}
698
699func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
700	if !ctx.Config().UnbundledBuild() {
701		ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip, l.referenceBaselineZip)
702	}
703}
704
705var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
706
707func init() {
708	android.RegisterParallelSingletonType("lint",
709		func() android.Singleton { return &lintSingleton{} })
710}
711
712func lintZip(ctx android.BuilderContext, paths android.Paths, outputPath android.WritablePath, validations android.Paths) {
713	paths = android.SortedUniquePaths(android.CopyOfPaths(paths))
714
715	sort.Slice(paths, func(i, j int) bool {
716		return paths[i].String() < paths[j].String()
717	})
718
719	rule := android.NewRuleBuilder(pctx, ctx)
720
721	rule.Command().BuiltTool("soong_zip").
722		FlagWithOutput("-o ", outputPath).
723		FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
724		FlagWithRspFileInputList("-r ", outputPath.ReplaceExtension(ctx, "rsp"), paths).
725		Validations(validations)
726
727	rule.Build(outputPath.Base(), outputPath.Base())
728}
729