Add check_nullability_warnings support to droidstubs.

This takes the warnings file written by metalava and diffs it against
the expected (checked-in) file, in a manner similar to the checking
and updating of the signature files. This makes it possible for a
developer adding a large API surface (e.g. by moving to a new version
of upstream sources for ojluni) to do so without being blocked on
adding the annotations, while ensuring that at all times there is an
up-to-date record of the API surface which is missing annotations.

Bug: 73448108
Test: See the other change in this topic.
Change-Id: If9ed470ef3355a0d713bb556b5561fb255e4e277
diff --git a/java/droiddoc.go b/java/droiddoc.go
index b724e15..9dadb30 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -106,6 +106,12 @@
 		},
 		"srcJarDir", "srcJars", "javaVersion", "bootclasspathArgs", "classpathArgs", "sourcepathArgs", "opts", "msg")
 
+	nullabilityWarningsCheck = pctx.AndroidStaticRule("nullabilityWarningsCheck",
+		blueprint.RuleParams{
+			Command: `( diff $expected $actual && touch $out ) || ( echo -e "$msg" ; exit 38 )`,
+		},
+		"expected", "actual", "msg")
+
 	dokka = pctx.AndroidStaticRule("dokka",
 		blueprint.RuleParams{
 			Command: `rm -rf "$outDir" "$srcJarDir" "$stubsDir" && ` +
@@ -354,6 +360,9 @@
 	// a list of top-level directories containing Java stub files to merge show/hide annotations from.
 	Merge_inclusion_annotations_dirs []string
 
+	// a file containing expected warnings produced by validation of nullability annotations.
+	Check_nullability_warnings *string
+
 	// if set to true, allow Metalava to generate doc_stubs source files. Defaults to false.
 	Create_doc_stubs *bool
 
@@ -1211,23 +1220,26 @@
 type Droidstubs struct {
 	Javadoc
 
-	properties             DroidstubsProperties
-	apiFile                android.WritablePath
-	apiXmlFile             android.WritablePath
-	lastReleasedApiXmlFile android.WritablePath
-	dexApiFile             android.WritablePath
-	privateApiFile         android.WritablePath
-	privateDexApiFile      android.WritablePath
-	removedApiFile         android.WritablePath
-	removedDexApiFile      android.WritablePath
-	apiMappingFile         android.WritablePath
-	exactApiFile           android.WritablePath
-	proguardFile           android.WritablePath
+	properties              DroidstubsProperties
+	apiFile                 android.WritablePath
+	apiXmlFile              android.WritablePath
+	lastReleasedApiXmlFile  android.WritablePath
+	dexApiFile              android.WritablePath
+	privateApiFile          android.WritablePath
+	privateDexApiFile       android.WritablePath
+	removedApiFile          android.WritablePath
+	removedDexApiFile       android.WritablePath
+	apiMappingFile          android.WritablePath
+	exactApiFile            android.WritablePath
+	proguardFile            android.WritablePath
+	nullabilityWarningsFile android.WritablePath
 
 	checkCurrentApiTimestamp      android.WritablePath
 	updateCurrentApiTimestamp     android.WritablePath
 	checkLastReleasedApiTimestamp android.WritablePath
 
+	checkNullabilityWarningsTimestamp android.WritablePath
+
 	annotationsZip android.WritablePath
 	apiVersionsXml android.WritablePath
 
@@ -1290,6 +1302,10 @@
 		}
 	}
 
+	if String(d.properties.Check_nullability_warnings) != "" {
+		android.ExtractSourceDeps(ctx, d.properties.Check_nullability_warnings)
+	}
+
 	if len(d.properties.Api_levels_annotations_dirs) != 0 {
 		for _, apiLevelsAnnotationsDir := range d.properties.Api_levels_annotations_dirs {
 			ctx.AddDependency(ctx.Module(), metalavaAPILevelsAnnotationsDirTag, apiLevelsAnnotationsDir)
@@ -1407,9 +1423,9 @@
 			flags += " --migrate-nullness " + previousApi.String()
 		}
 		if validatingNullability {
-			nullabilityWarningsTxt := android.PathForModuleOut(ctx, ctx.ModuleName()+"_nullability_warnings.txt")
-			*implicitOutputs = append(*implicitOutputs, nullabilityWarningsTxt)
-			flags += " --nullability-warnings-txt " + nullabilityWarningsTxt.String()
+			d.nullabilityWarningsFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_nullability_warnings.txt")
+			*implicitOutputs = append(*implicitOutputs, d.nullabilityWarningsFile)
+			flags += " --nullability-warnings-txt " + d.nullabilityWarningsFile.String()
 		}
 
 		d.annotationsZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"_annotations.zip")
@@ -1685,6 +1701,36 @@
 			d.checkLastReleasedApiTimestamp)
 	}
 
+	if String(d.properties.Check_nullability_warnings) != "" {
+		if d.nullabilityWarningsFile == nil {
+			ctx.PropertyErrorf("check_nullability_warnings",
+				"Cannot specify check_nullability_warnings unless validating nullability")
+		}
+		checkNullabilityWarnings := ctx.ExpandSource(String(d.properties.Check_nullability_warnings),
+			"check_nullability_warnings")
+		d.checkNullabilityWarningsTimestamp = android.PathForModuleOut(ctx, "check_nullability_warnings.timestamp")
+		msg := fmt.Sprintf(`\n******************************\n`+
+			`The warnings encountered during nullability annotation validation did\n`+
+			`not match the checked in file of expected warnings. The diffs are shown\n`+
+			`above. You have two options:\n`+
+			`   1. Resolve the differences by editing the nullability annotations.\n`+
+			`   2. Update the file of expected warnings by running:\n`+
+			`         cp %s %s\n`+
+			`       and submitting the updated file as part of your change.`,
+			d.nullabilityWarningsFile, checkNullabilityWarnings)
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        nullabilityWarningsCheck,
+			Description: "Nullability Warnings Check",
+			Output:      d.checkNullabilityWarningsTimestamp,
+			Implicits:   android.Paths{checkNullabilityWarnings, d.nullabilityWarningsFile},
+			Args: map[string]string{
+				"expected": checkNullabilityWarnings.String(),
+				"actual":   d.nullabilityWarningsFile.String(),
+				"msg":      msg,
+			},
+		})
+	}
+
 	if Bool(d.properties.Jdiff_enabled) && !ctx.Config().IsPdkBuild() {
 
 		// Please sync with android-api-council@ before making any changes for the name of jdiffDocZip below