Build a zip of transitive lint reports for apps

Add a rule to build a zip containing the lint reports from transitive
dependencies for apps, and pass it to Make.

Bug: 153485543
Test: m TARGET_BUILD_APPS=Gallery2 lint-check
Change-Id: I523c09016251377ff89d76084769be7401b95425
diff --git a/android/androidmk.go b/android/androidmk.go
index 045cb59..dfc68c4 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -107,6 +107,25 @@
 	a.EntryMap[name] = []string{path.String()}
 }
 
+func (a *AndroidMkEntries) SetOptionalPath(name string, path OptionalPath) {
+	if path.Valid() {
+		a.SetPath(name, path.Path())
+	}
+}
+
+func (a *AndroidMkEntries) AddPath(name string, path Path) {
+	if _, ok := a.EntryMap[name]; !ok {
+		a.entryOrder = append(a.entryOrder, name)
+	}
+	a.EntryMap[name] = append(a.EntryMap[name], path.String())
+}
+
+func (a *AndroidMkEntries) AddOptionalPath(name string, path OptionalPath) {
+	if path.Valid() {
+		a.AddPath(name, path.Path())
+	}
+}
+
 func (a *AndroidMkEntries) SetBoolIfTrue(name string, flag bool) {
 	if flag {
 		if _, ok := a.EntryMap[name]; !ok {
diff --git a/android/paths.go b/android/paths.go
index 066baf2..eef71b7 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -484,6 +484,10 @@
 	return ret
 }
 
+func CopyOfPaths(paths Paths) Paths {
+	return append(Paths(nil), paths...)
+}
+
 // FirstUniquePaths returns all unique elements of a Paths, keeping the first copy of each.  It
 // modifies the Paths slice contents in place, and returns a subslice of the original slice.
 func FirstUniquePaths(list Paths) Paths {
@@ -494,7 +498,8 @@
 	return firstUniquePathsList(list)
 }
 
-// SortedUniquePaths returns what its name says
+// SortedUniquePaths returns all unique elements of a Paths in sorted order.  It modifies the
+// Paths slice contents in place, and returns a subslice of the original slice.
 func SortedUniquePaths(list Paths) Paths {
 	unique := FirstUniquePaths(list)
 	sort.Slice(unique, func(i, j int) bool {
diff --git a/java/androidmk.go b/java/androidmk.go
index 618e15d..99dd823 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -131,6 +131,10 @@
 						entries.SetPath("LOCAL_SOONG_PROGUARD_DICT", library.proguardDictionary)
 					}
 					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)
 				},
 			},
 		}
@@ -383,6 +387,10 @@
 					install := app.onDeviceDir + "/" + extra.Base()
 					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)
 			},
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
diff --git a/java/app.go b/java/app.go
index 98bce94..e6e0e1a 100755
--- a/java/app.go
+++ b/java/app.go
@@ -752,6 +752,7 @@
 	a.linter.mergedManifest = a.aapt.mergedManifestFile
 	a.linter.manifest = a.aapt.manifestPath
 	a.linter.resources = a.aapt.resourceFiles
+	a.linter.buildModuleReportZip = ctx.Config().UnbundledBuildApps()
 
 	dexJarFile := a.dexBuildActions(ctx)
 
diff --git a/java/lint.go b/java/lint.go
index 6fbef18..20a7dc4 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -67,12 +67,32 @@
 	kotlinLanguageLevel string
 	outputs             lintOutputs
 	properties          LintProperties
+
+	buildModuleReportZip bool
 }
 
 type lintOutputs struct {
 	html android.ModuleOutPath
 	text android.ModuleOutPath
 	xml  android.ModuleOutPath
+
+	transitiveHTML *android.DepSet
+	transitiveText *android.DepSet
+	transitiveXML  *android.DepSet
+
+	transitiveHTMLZip android.OptionalPath
+	transitiveTextZip android.OptionalPath
+	transitiveXMLZip  android.OptionalPath
+}
+
+type lintOutputIntf interface {
+	lintOutputs() *lintOutputs
+}
+
+var _ lintOutputIntf = (*linter)(nil)
+
+func (l *linter) lintOutputs() *lintOutputs {
+	return &l.outputs
 }
 
 func (l *linter) enabled() bool {
@@ -213,9 +233,22 @@
 
 	projectXML, lintXML, cacheDir, homeDir, deps := l.writeLintProjectXML(ctx, rule)
 
-	l.outputs.html = android.PathForModuleOut(ctx, "lint-report.html")
-	l.outputs.text = android.PathForModuleOut(ctx, "lint-report.txt")
-	l.outputs.xml = android.PathForModuleOut(ctx, "lint-report.xml")
+	html := android.PathForModuleOut(ctx, "lint-report.html")
+	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)
+
+	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)
+		}
+	})
 
 	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(cacheDir.String()).Flag(homeDir.String())
@@ -240,9 +273,9 @@
 		Flag("--quiet").
 		FlagWithInput("--project ", projectXML).
 		FlagWithInput("--config ", lintXML).
-		FlagWithOutput("--html ", l.outputs.html).
-		FlagWithOutput("--text ", l.outputs.text).
-		FlagWithOutput("--xml ", l.outputs.xml).
+		FlagWithOutput("--html ", html).
+		FlagWithOutput("--text ", text).
+		FlagWithOutput("--xml ", xml).
 		FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
 		FlagWithArg("--java-language-level ", l.javaLanguageLevel).
 		FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
@@ -250,23 +283,37 @@
 		Flag("--exitcode").
 		Flags(l.properties.Lint.Flags).
 		Implicits(deps).
-		Text("|| (").Text("cat").Input(l.outputs.text).Text("; exit 7)").
+		Text("|| (").Text("cat").Input(text).Text("; exit 7)").
 		Text(")")
 
 	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
 
 	rule.Build(pctx, ctx, "lint", "lint")
-}
 
-func (l *linter) lintOutputs() *lintOutputs {
-	return &l.outputs
-}
+	l.outputs = lintOutputs{
+		html: html,
+		text: text,
+		xml:  xml,
 
-type lintOutputIntf interface {
-	lintOutputs() *lintOutputs
-}
+		transitiveHTML: htmlDeps.Build(),
+		transitiveText: textDeps.Build(),
+		transitiveXML:  xmlDeps.Build(),
+	}
 
-var _ lintOutputIntf = (*linter)(nil)
+	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)
+	}
+}
 
 type lintSingleton struct {
 	htmlZip android.WritablePath
@@ -356,18 +403,7 @@
 			paths = append(paths, get(output))
 		}
 
-		sort.Slice(paths, func(i, j int) bool {
-			return paths[i].String() < paths[j].String()
-		})
-
-		rule := android.NewRuleBuilder()
-
-		rule.Command().BuiltTool(ctx, "soong_zip").
-			FlagWithOutput("-o ", outputPath).
-			FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
-			FlagWithRspFileInputList("-l ", paths)
-
-		rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
+		lintZip(ctx, paths, outputPath)
 	}
 
 	l.htmlZip = android.PathForOutput(ctx, "lint-report-html.zip")
@@ -394,3 +430,20 @@
 	android.RegisterSingletonType("lint",
 		func() android.Singleton { return &lintSingleton{} })
 }
+
+func lintZip(ctx android.BuilderContext, paths android.Paths, outputPath android.WritablePath) {
+	paths = android.SortedUniquePaths(android.CopyOfPaths(paths))
+
+	sort.Slice(paths, func(i, j int) bool {
+		return paths[i].String() < paths[j].String()
+	})
+
+	rule := android.NewRuleBuilder()
+
+	rule.Command().BuiltTool(ctx, "soong_zip").
+		FlagWithOutput("-o ", outputPath).
+		FlagWithArg("-C ", android.PathForIntermediates(ctx).String()).
+		FlagWithRspFileInputList("-l ", paths)
+
+	rule.Build(pctx, ctx, outputPath.Base(), outputPath.Base())
+}