Build transitive lint reports for apex modules

Build and export transitive lint report zips for apex modules.

Bug: 153485543
Test: m TARGET_BUILD_APPS=com.google.android.wifi lint-check dist
Change-Id: I5a1805440452301a7e2c4ca91482b989638b54fb
diff --git a/android/androidmk.go b/android/androidmk.go
index 94b4b81..7e86140 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -126,6 +126,26 @@
 	}
 }
 
+func (a *AndroidMkEntries) SetPaths(name string, paths Paths) {
+	if _, ok := a.EntryMap[name]; !ok {
+		a.entryOrder = append(a.entryOrder, name)
+	}
+	a.EntryMap[name] = paths.Strings()
+}
+
+func (a *AndroidMkEntries) SetOptionalPaths(name string, paths Paths) {
+	if len(paths) > 0 {
+		a.SetPaths(name, paths)
+	}
+}
+
+func (a *AndroidMkEntries) AddPaths(name string, paths Paths) {
+	if _, ok := a.EntryMap[name]; !ok {
+		a.entryOrder = append(a.entryOrder, name)
+	}
+	a.EntryMap[name] = append(a.EntryMap[name], paths.Strings()...)
+}
+
 func (a *AndroidMkEntries) SetBoolIfTrue(name string, flag bool) {
 	if flag {
 		if _, ok := a.EntryMap[name]; !ok {
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 10cc4b6..e739e2b 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -353,6 +353,10 @@
 				if apexType == imageApex {
 					fmt.Fprintln(w, "ALL_MODULES.$(my_register_name).BUNDLE :=", a.bundleModuleFile.String())
 				}
+				if len(a.lintReports) > 0 {
+					fmt.Fprintln(w, "ALL_MODULES.$(my_register_name).LINT_REPORTS :=",
+						strings.Join(a.lintReports.Strings(), " "))
+				}
 
 				if a.installedFilesFile != nil {
 					goal := "checkbuild"
diff --git a/apex/apex.go b/apex/apex.go
index d0c4e6e..7f695b4 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1154,6 +1154,7 @@
 	hostRequiredModuleNames   []string
 
 	jacocoReportClassesFile android.Path     // only for javalibs and apps
+	lintDepSets             java.LintDepSets // only for javalibs and apps
 	certificate             java.Certificate // only for apps
 	overriddenPackageName   string           // only for apps
 
@@ -1278,6 +1279,9 @@
 
 	// Struct holding the merged notice file paths in different formats
 	mergedNotices android.NoticeOutputs
+
+	// Optional list of lint report zip files for apexes that contain java or app modules
+	lintReports android.Paths
 }
 
 func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext,
@@ -1666,9 +1670,16 @@
 type javaDependency interface {
 	DexJarBuildPath() android.Path
 	JacocoReportClassesFile() android.Path
+	LintDepSets() java.LintDepSets
+
 	Stem() string
 }
 
+var _ javaDependency = (*java.Library)(nil)
+var _ javaDependency = (*java.SdkLibrary)(nil)
+var _ javaDependency = (*java.DexImport)(nil)
+var _ javaDependency = (*java.SdkLibraryImport)(nil)
+
 func apexFileForJavaLibrary(ctx android.BaseModuleContext, lib javaDependency, module android.Module) apexFile {
 	dirInApex := "javalib"
 	fileToCopy := lib.DexJarBuildPath()
@@ -1676,6 +1687,7 @@
 	name := strings.TrimPrefix(module.Name(), "prebuilt_")
 	af := newApexFile(ctx, fileToCopy, name, dirInApex, javaSharedLib, module)
 	af.jacocoReportClassesFile = lib.JacocoReportClassesFile()
+	af.lintDepSets = lib.LintDepSets()
 	af.stem = lib.Stem() + ".jar"
 	return af
 }
@@ -2275,6 +2287,8 @@
 	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
 
 	a.buildApexDependencyInfo(ctx)
+
+	a.buildLintReports(ctx)
 }
 
 // Enforce that Java deps of the apex are using stable SDKs to compile
diff --git a/apex/builder.go b/apex/builder.go
index a70c767..0a1ec3e 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -815,3 +815,12 @@
 		},
 	})
 }
+
+func (a *apexBundle) buildLintReports(ctx android.ModuleContext) {
+	depSetsBuilder := java.NewLintDepSetBuilder()
+	for _, fi := range a.filesInfo {
+		depSetsBuilder.Transitive(fi.lintDepSets)
+	}
+
+	a.lintReports = java.BuildModuleLintReportZips(ctx, depSetsBuilder.Build())
+}
diff --git a/java/androidmk.go b/java/androidmk.go
index 03994bf..25dd329 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -132,9 +132,7 @@
 					}
 					entries.SetString("LOCAL_MODULE_STEM", library.Stem())
 
-					entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", library.linter.outputs.transitiveHTMLZip)
-					entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", library.linter.outputs.transitiveTextZip)
-					entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", library.linter.outputs.transitiveXMLZip)
+					entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", library.linter.reports)
 				},
 			},
 		}
@@ -394,9 +392,7 @@
 					entries.AddStrings("LOCAL_SOONG_BUILT_INSTALLED", extra.String()+":"+install)
 				}
 
-				entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", app.linter.outputs.transitiveHTMLZip)
-				entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", app.linter.outputs.transitiveTextZip)
-				entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", app.linter.outputs.transitiveXMLZip)
+				entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", app.linter.reports)
 			},
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
diff --git a/java/java.go b/java/java.go
index 367b09c..ef9613d 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1707,6 +1707,9 @@
 		j.linter.compileSdkVersion = lintSDKVersionString(j.sdkVersion())
 		j.linter.javaLanguageLevel = flags.javaVersion.String()
 		j.linter.kotlinLanguageLevel = "1.3"
+		if j.ApexName() != "" && ctx.Config().UnbundledBuildApps() {
+			j.linter.buildModuleReportZip = true
+		}
 		j.linter.lint(ctx)
 	}
 
@@ -2789,6 +2792,10 @@
 	return nil
 }
 
+func (a *DexImport) LintDepSets() LintDepSets {
+	return LintDepSets{}
+}
+
 func (j *DexImport) IsInstallable() bool {
 	return true
 }
diff --git a/java/lint.go b/java/lint.go
index 5d2c4f6..6391067 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -69,28 +69,78 @@
 	outputs             lintOutputs
 	properties          LintProperties
 
+	reports android.Paths
+
 	buildModuleReportZip bool
 }
 
 type lintOutputs struct {
-	html android.ModuleOutPath
-	text android.ModuleOutPath
-	xml  android.ModuleOutPath
+	html android.Path
+	text android.Path
+	xml  android.Path
 
-	transitiveHTML *android.DepSet
-	transitiveText *android.DepSet
-	transitiveXML  *android.DepSet
-
-	transitiveHTMLZip android.OptionalPath
-	transitiveTextZip android.OptionalPath
-	transitiveXMLZip  android.OptionalPath
+	depSets LintDepSets
 }
 
-type lintOutputIntf interface {
+type lintOutputsIntf interface {
 	lintOutputs() *lintOutputs
 }
 
-var _ lintOutputIntf = (*linter)(nil)
+type lintDepSetsIntf interface {
+	LintDepSets() LintDepSets
+}
+
+type LintDepSets struct {
+	HTML, Text, XML *android.DepSet
+}
+
+type LintDepSetsBuilder struct {
+	HTML, Text, XML *android.DepSetBuilder
+}
+
+func NewLintDepSetBuilder() LintDepSetsBuilder {
+	return LintDepSetsBuilder{
+		HTML: android.NewDepSetBuilder(android.POSTORDER),
+		Text: android.NewDepSetBuilder(android.POSTORDER),
+		XML:  android.NewDepSetBuilder(android.POSTORDER),
+	}
+}
+
+func (l LintDepSetsBuilder) Direct(html, text, xml android.Path) LintDepSetsBuilder {
+	l.HTML.Direct(html)
+	l.Text.Direct(text)
+	l.XML.Direct(xml)
+	return l
+}
+
+func (l LintDepSetsBuilder) Transitive(depSets LintDepSets) LintDepSetsBuilder {
+	if depSets.HTML != nil {
+		l.HTML.Transitive(depSets.HTML)
+	}
+	if depSets.Text != nil {
+		l.Text.Transitive(depSets.Text)
+	}
+	if depSets.XML != nil {
+		l.XML.Transitive(depSets.XML)
+	}
+	return l
+}
+
+func (l LintDepSetsBuilder) Build() LintDepSets {
+	return LintDepSets{
+		HTML: l.HTML.Build(),
+		Text: l.Text.Build(),
+		XML:  l.XML.Build(),
+	}
+}
+
+func (l *linter) LintDepSets() LintDepSets {
+	return l.outputs.depSets
+}
+
+var _ lintDepSetsIntf = (*linter)(nil)
+
+var _ lintOutputsIntf = (*linter)(nil)
 
 func (l *linter) lintOutputs() *lintOutputs {
 	return &l.outputs
@@ -247,16 +297,11 @@
 	text := android.PathForModuleOut(ctx, "lint-report.txt")
 	xml := android.PathForModuleOut(ctx, "lint-report.xml")
 
-	htmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(html)
-	textDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(text)
-	xmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(xml)
+	depSetsBuilder := NewLintDepSetBuilder().Direct(html, text, xml)
 
 	ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) {
-		if depLint, ok := dep.(lintOutputIntf); ok {
-			depLintOutputs := depLint.lintOutputs()
-			htmlDeps.Transitive(depLintOutputs.transitiveHTML)
-			textDeps.Transitive(depLintOutputs.transitiveText)
-			xmlDeps.Transitive(depLintOutputs.transitiveXML)
+		if depLint, ok := dep.(lintDepSetsIntf); ok {
+			depSetsBuilder.Transitive(depLint.LintDepSets())
 		}
 	})
 
@@ -309,26 +354,35 @@
 		text: text,
 		xml:  xml,
 
-		transitiveHTML: htmlDeps.Build(),
-		transitiveText: textDeps.Build(),
-		transitiveXML:  xmlDeps.Build(),
+		depSets: depSetsBuilder.Build(),
 	}
 
 	if l.buildModuleReportZip {
-		htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip")
-		l.outputs.transitiveHTMLZip = android.OptionalPathForPath(htmlZip)
-		lintZip(ctx, l.outputs.transitiveHTML.ToSortedList(), htmlZip)
-
-		textZip := android.PathForModuleOut(ctx, "lint-report-text.zip")
-		l.outputs.transitiveTextZip = android.OptionalPathForPath(textZip)
-		lintZip(ctx, l.outputs.transitiveText.ToSortedList(), textZip)
-
-		xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip")
-		l.outputs.transitiveXMLZip = android.OptionalPathForPath(xmlZip)
-		lintZip(ctx, l.outputs.transitiveXML.ToSortedList(), xmlZip)
+		l.reports = BuildModuleLintReportZips(ctx, l.LintDepSets())
 	}
 }
 
+func BuildModuleLintReportZips(ctx android.ModuleContext, depSets LintDepSets) android.Paths {
+	htmlList := depSets.HTML.ToSortedList()
+	textList := depSets.Text.ToSortedList()
+	xmlList := depSets.XML.ToSortedList()
+
+	if len(htmlList) == 0 && len(textList) == 0 && len(xmlList) == 0 {
+		return nil
+	}
+
+	htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip")
+	lintZip(ctx, htmlList, htmlZip)
+
+	textZip := android.PathForModuleOut(ctx, "lint-report-text.zip")
+	lintZip(ctx, textList, textZip)
+
+	xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip")
+	lintZip(ctx, xmlList, xmlZip)
+
+	return android.Paths{htmlZip, textZip, xmlZip}
+}
+
 type lintSingleton struct {
 	htmlZip android.WritablePath
 	textZip android.WritablePath
@@ -403,7 +457,7 @@
 			return
 		}
 
-		if l, ok := m.(lintOutputIntf); ok {
+		if l, ok := m.(lintOutputsIntf); ok {
 			outputs = append(outputs, l.lintOutputs())
 		}
 	})
@@ -414,7 +468,9 @@
 		var paths android.Paths
 
 		for _, output := range outputs {
-			paths = append(paths, get(output))
+			if p := get(output); p != nil {
+				paths = append(paths, p)
+			}
 		}
 
 		lintZip(ctx, paths, outputPath)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index e3ba2c7..8a8d0c9 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1996,6 +1996,15 @@
 }
 
 // to satisfy apex.javaDependency interface
+func (module *SdkLibraryImport) LintDepSets() LintDepSets {
+	if module.implLibraryModule == nil {
+		return LintDepSets{}
+	} else {
+		return module.implLibraryModule.LintDepSets()
+	}
+}
+
+// to satisfy apex.javaDependency interface
 func (module *SdkLibraryImport) Stem() string {
 	return module.BaseModuleName()
 }