java_library support for building headers-only

Flag for java_library modules to build just the Turbine headers and
skip building an impl jar.

Test: go test java
Bug: 289776578
Change-Id: Iad0babf951710476bc32df93c25d17065a14ab84
diff --git a/android/neverallow.go b/android/neverallow.go
index 24031ba..2be6a74 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -60,6 +60,7 @@
 	AddNeverAllowRules(createBp2BuildRule())
 	AddNeverAllowRules(createCcStubsRule())
 	AddNeverAllowRules(createJavaExcludeStaticLibsRule())
+	AddNeverAllowRules(createProhibitHeaderOnlyRule())
 }
 
 // Add a NeverAllow rule to the set of rules to apply.
@@ -264,6 +265,13 @@
 		Because("exclude_static_libs property is only allowed for java modules defined in build/soong, libcore, and frameworks/base/api")
 }
 
+func createProhibitHeaderOnlyRule() Rule {
+	return NeverAllow().
+		Without("name", "framework-minus-apex-headers").
+		With("headers_only", "true").
+		Because("headers_only can only be used for generating framework-minus-apex headers for non-updatable modules")
+}
+
 func neverallowMutator(ctx BottomUpMutatorContext) {
 	m, ok := ctx.Module().(Module)
 	if !ok {
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 2a938b8..b2620ef 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -361,6 +361,21 @@
 			`exclude_static_libs property is only allowed for java modules defined in build/soong, libcore, and frameworks/base/api`,
 		},
 	},
+	// Test for only allowing headers_only for framework-minus-apex-headers
+	{
+		name: `"headers_only" outside framework-minus-apex-headers modules`,
+		fs: map[string][]byte{
+			"a/b/Android.bp": []byte(`
+				java_library {
+					name: "baz",
+					headers_only: true,
+				}
+			`),
+		},
+		expectedErrors: []string{
+			`headers_only can only be used for generating framework-minus-apex headers for non-updatable modules`,
+		},
+	},
 }
 
 var prepareForNeverAllowTest = GroupFixturePreparers(
@@ -451,6 +466,7 @@
 	Sdk_version         *string
 	Uncompress_dex      *bool
 	Exclude_static_libs []string
+	Headers_only        *bool
 }
 
 type mockJavaLibraryModule struct {
diff --git a/java/androidmk.go b/java/androidmk.go
index 82505e9..b7e2d2f 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -79,6 +79,9 @@
 	} else if !library.ApexModuleBase.AvailableFor(android.AvailableToPlatform) {
 		// Platform variant.  If not available for the platform, we don't need Make module.
 		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
+	} else if library.properties.Headers_only {
+		// If generating headers only then don't expose to Make.
+		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
 	} else {
 		entriesList = append(entriesList, android.AndroidMkEntries{
 			Class:      "JAVA_LIBRARIES",
diff --git a/java/base.go b/java/base.go
index f5eb01c..58ba410 100644
--- a/java/base.go
+++ b/java/base.go
@@ -192,6 +192,9 @@
 
 	// Additional srcJars tacked in by GeneratedJavaLibraryModule
 	Generated_srcjars []android.Path `android:"mutated"`
+
+	// If true, then only the headers are built and not the implementation jar.
+	Headers_only bool
 }
 
 // Properties that are specific to device modules. Host module factories should not add these when
@@ -574,6 +577,17 @@
 	}
 }
 
+func (j *Module) checkHeadersOnly(ctx android.ModuleContext) {
+	if _, ok := ctx.Module().(android.SdkContext); ok {
+		headersOnly := proptools.Bool(&j.properties.Headers_only)
+		installable := proptools.Bool(j.properties.Installable)
+
+		if headersOnly && installable {
+			ctx.PropertyErrorf("headers_only", "This module has conflicting settings. headers_only is true which, which means this module doesn't generate an implementation jar. However installable is set to true.")
+		}
+	}
+}
+
 func (j *Module) addHostProperties() {
 	j.AddProperties(
 		&j.properties,
@@ -1151,6 +1165,36 @@
 	// final R classes from the app.
 	flags.classpath = append(android.CopyOf(extraClasspathJars), flags.classpath...)
 
+	// If compiling headers then compile them and skip the rest
+	if j.properties.Headers_only {
+		if srcFiles.HasExt(".kt") {
+			ctx.ModuleErrorf("Compiling headers_only with .kt not supported")
+		}
+		if ctx.Config().IsEnvFalse("TURBINE_ENABLED") || disableTurbine {
+			ctx.ModuleErrorf("headers_only is enabled but Turbine is disabled.")
+		}
+
+		_, j.headerJarFile =
+			j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName,
+				extraCombinedJars)
+		if ctx.Failed() {
+			return
+		}
+
+		ctx.SetProvider(JavaInfoProvider, JavaInfo{
+			HeaderJars:                     android.PathsIfNonNil(j.headerJarFile),
+			TransitiveLibsHeaderJars:       j.transitiveLibsHeaderJars,
+			TransitiveStaticLibsHeaderJars: j.transitiveStaticLibsHeaderJars,
+			AidlIncludeDirs:                j.exportAidlIncludeDirs,
+			ExportedPlugins:                j.exportedPluginJars,
+			ExportedPluginClasses:          j.exportedPluginClasses,
+			ExportedPluginDisableTurbine:   j.exportedDisableTurbine,
+		})
+
+		j.outputFile = j.headerJarFile
+		return
+	}
+
 	if srcFiles.HasExt(".kt") {
 		// When using kotlin sources turbine is used to generate annotation processor sources,
 		// including for annotation processors that generate API, so we can use turbine for
diff --git a/java/java.go b/java/java.go
index 70aba8e..c60226d 100644
--- a/java/java.go
+++ b/java/java.go
@@ -692,6 +692,7 @@
 	}
 
 	j.checkSdkVersions(ctx)
+	j.checkHeadersOnly(ctx)
 	if ctx.Device() {
 		j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
 			ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
diff --git a/java/java_test.go b/java/java_test.go
index 6110e21..27933c3 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -2370,3 +2370,21 @@
 		t.Errorf("Module output does not contain expected jar %s", "test.jar")
 	}
 }
+
+func TestHeadersOnly(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			headers_only: true,
+		}
+	`)
+
+	turbine := ctx.ModuleForTests("foo", "android_common").Rule("turbine")
+	if len(turbine.Inputs) != 1 || turbine.Inputs[0].String() != "a.java" {
+		t.Errorf(`foo inputs %v != ["a.java"]`, turbine.Inputs)
+	}
+
+	javac := ctx.ModuleForTests("foo", "android_common").MaybeRule("javac")
+	android.AssertDeepEquals(t, "javac rule", nil, javac.Rule)
+}