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