Add java_genrule_combiner module

This module is intended to combine an implemenation generated by
java_genrule with headers from the rule's inputs.

Bug: b/285975842
Test: manual, TH
Change-Id: I9d83634ad7fa56fe5e0a6363d2891d34ac58257e
diff --git a/java/genrule_combiner.go b/java/genrule_combiner.go
new file mode 100644
index 0000000..357dc2c
--- /dev/null
+++ b/java/genrule_combiner.go
@@ -0,0 +1,252 @@
+// Copyright 2019 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"fmt"
+	"io"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+
+	"github.com/google/blueprint/depset"
+	"github.com/google/blueprint/proptools"
+)
+
+type GenruleCombiner struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+
+	genruleCombinerProperties GenruleCombinerProperties
+
+	headerJars                    android.Paths
+	implementationJars            android.Paths
+	implementationAndResourceJars android.Paths
+	resourceJars                  android.Paths
+	aconfigProtoFiles             android.Paths
+
+	srcJarArgs []string
+	srcJarDeps android.Paths
+
+	headerDirs android.Paths
+
+	combinedHeaderJar         android.Path
+	combinedImplementationJar android.Path
+}
+
+type GenruleCombinerProperties struct {
+	// List of modules whose implementation (and resources) jars will be visible to modules
+	// that depend on this module.
+	Static_libs proptools.Configurable[[]string] `android:"arch_variant"`
+
+	// List of modules whose header jars will be visible to modules that depend on this module.
+	Headers proptools.Configurable[[]string] `android:"arch_variant"`
+}
+
+// java_genrule_combiner provides the implementation and resource jars from `static_libs`, with
+// the header jars from `headers`.
+//
+// This is useful when a java_genrule is used to change the implementation of a java library
+// without requiring a change in the header jars.
+func GenruleCombinerFactory() android.Module {
+	module := &GenruleCombiner{}
+
+	module.AddProperties(&module.genruleCombinerProperties)
+	InitJavaModule(module, android.HostAndDeviceSupported)
+	return module
+}
+
+var genruleCombinerHeaderDepTag = dependencyTag{name: "genrule_combiner_header"}
+
+func (j *GenruleCombiner) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddVariationDependencies(nil, staticLibTag,
+		j.genruleCombinerProperties.Static_libs.GetOrDefault(ctx, nil)...)
+	ctx.AddVariationDependencies(nil, genruleCombinerHeaderDepTag,
+		j.genruleCombinerProperties.Headers.GetOrDefault(ctx, nil)...)
+}
+
+func (j *GenruleCombiner) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if len(j.genruleCombinerProperties.Static_libs.GetOrDefault(ctx, nil)) < 1 {
+		ctx.PropertyErrorf("static_libs", "at least one dependency is required")
+	}
+
+	if len(j.genruleCombinerProperties.Headers.GetOrDefault(ctx, nil)) < 1 {
+		ctx.PropertyErrorf("headers", "at least one dependency is required")
+	}
+
+	var transitiveHeaderJars []depset.DepSet[android.Path]
+	var transitiveImplementationJars []depset.DepSet[android.Path]
+	var transitiveResourceJars []depset.DepSet[android.Path]
+	var sdkVersion android.SdkSpec
+	var stubsLinkType StubsLinkType
+	moduleWithSdkDepInfo := &ModuleWithSdkDepInfo{}
+
+	// Collect the headers first, so that aconfig flag values for the libraries will override
+	// values from the headers (if they are different).
+	ctx.VisitDirectDepsWithTag(genruleCombinerHeaderDepTag, func(m android.Module) {
+		if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
+			j.headerJars = append(j.headerJars, dep.HeaderJars...)
+
+			j.srcJarArgs = append(j.srcJarArgs, dep.SrcJarArgs...)
+			j.srcJarDeps = append(j.srcJarDeps, dep.SrcJarDeps...)
+			j.aconfigProtoFiles = append(j.aconfigProtoFiles, dep.AconfigIntermediateCacheOutputPaths...)
+			sdkVersion = dep.SdkVersion
+			stubsLinkType = dep.StubsLinkType
+			*moduleWithSdkDepInfo = *dep.ModuleWithSdkDepInfo
+
+			transitiveHeaderJars = append(transitiveHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+		} else if dep, ok := android.OtherModuleProvider(ctx, m, android.CodegenInfoProvider); ok {
+			j.aconfigProtoFiles = append(j.aconfigProtoFiles, dep.IntermediateCacheOutputPaths...)
+		} else {
+			ctx.PropertyErrorf("headers", "module %q cannot be used as a dependency", ctx.OtherModuleName(m))
+		}
+	})
+	ctx.VisitDirectDepsWithTag(staticLibTag, func(m android.Module) {
+		if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
+			j.implementationJars = append(j.implementationJars, dep.ImplementationJars...)
+			j.implementationAndResourceJars = append(j.implementationAndResourceJars, dep.ImplementationAndResourcesJars...)
+			j.resourceJars = append(j.resourceJars, dep.ResourceJars...)
+
+			transitiveImplementationJars = append(transitiveImplementationJars, dep.TransitiveStaticLibsImplementationJars)
+			transitiveResourceJars = append(transitiveResourceJars, dep.TransitiveStaticLibsResourceJars)
+			j.aconfigProtoFiles = append(j.aconfigProtoFiles, dep.AconfigIntermediateCacheOutputPaths...)
+		} else if dep, ok := android.OtherModuleProvider(ctx, m, android.OutputFilesProvider); ok {
+			// This is provided by `java_genrule` modules.
+			j.implementationJars = append(j.implementationJars, dep.DefaultOutputFiles...)
+			j.implementationAndResourceJars = append(j.implementationAndResourceJars, dep.DefaultOutputFiles...)
+			stubsLinkType = Implementation
+		} else {
+			ctx.PropertyErrorf("static_libs", "module %q cannot be used as a dependency", ctx.OtherModuleName(m))
+		}
+	})
+
+	jarName := ctx.ModuleName() + ".jar"
+
+	if len(j.implementationAndResourceJars) > 1 {
+		outputFile := android.PathForModuleOut(ctx, "combined", jarName)
+		TransformJarsToJar(ctx, outputFile, "combine", j.implementationAndResourceJars,
+			android.OptionalPath{}, false, nil, nil)
+		j.combinedImplementationJar = outputFile
+	} else if len(j.implementationAndResourceJars) == 1 {
+		j.combinedImplementationJar = j.implementationAndResourceJars[0]
+	}
+
+	if len(j.headerJars) > 1 {
+		outputFile := android.PathForModuleOut(ctx, "turbine-combined", jarName)
+		TransformJarsToJar(ctx, outputFile, "turbine combine", j.headerJars,
+			android.OptionalPath{}, false, nil, []string{"META-INF/TRANSITIVE"})
+		j.combinedHeaderJar = outputFile
+		j.headerDirs = append(j.headerDirs, android.PathForModuleOut(ctx, "turbine-combined"))
+	} else if len(j.headerJars) == 1 {
+		j.combinedHeaderJar = j.headerJars[0]
+	}
+
+	javaInfo := &JavaInfo{
+		HeaderJars:                             android.Paths{j.combinedHeaderJar},
+		LocalHeaderJars:                        android.Paths{j.combinedHeaderJar},
+		TransitiveStaticLibsHeaderJars:         depset.New(depset.PREORDER, android.Paths{j.combinedHeaderJar}, transitiveHeaderJars),
+		TransitiveStaticLibsImplementationJars: depset.New(depset.PREORDER, android.Paths{j.combinedImplementationJar}, transitiveImplementationJars),
+		TransitiveStaticLibsResourceJars:       depset.New(depset.PREORDER, nil, transitiveResourceJars),
+		GeneratedSrcjars:                       android.Paths{j.combinedImplementationJar},
+		ImplementationAndResourcesJars:         android.Paths{j.combinedImplementationJar},
+		ImplementationJars:                     android.Paths{j.combinedImplementationJar},
+		ModuleWithSdkDepInfo:                   moduleWithSdkDepInfo,
+		ResourceJars:                           j.resourceJars,
+		OutputFile:                             j.combinedImplementationJar,
+		SdkVersion:                             sdkVersion,
+		SrcJarArgs:                             j.srcJarArgs,
+		SrcJarDeps:                             j.srcJarDeps,
+		StubsLinkType:                          stubsLinkType,
+		AconfigIntermediateCacheOutputPaths:    j.aconfigProtoFiles,
+	}
+	setExtraJavaInfo(ctx, j, javaInfo)
+	ctx.SetOutputFiles(android.Paths{javaInfo.OutputFile}, "")
+	ctx.SetOutputFiles(android.Paths{javaInfo.OutputFile}, android.DefaultDistTag)
+	ctx.SetOutputFiles(javaInfo.ImplementationAndResourcesJars, ".jar")
+	ctx.SetOutputFiles(javaInfo.HeaderJars, ".hjar")
+	android.SetProvider(ctx, JavaInfoProvider, javaInfo)
+
+}
+
+func (j *GenruleCombiner) GeneratedSourceFiles() android.Paths {
+	return append(android.Paths{}, j.combinedImplementationJar)
+}
+
+func (j *GenruleCombiner) GeneratedHeaderDirs() android.Paths {
+	return append(android.Paths{}, j.headerDirs...)
+}
+
+func (j *GenruleCombiner) GeneratedDeps() android.Paths {
+	return append(android.Paths{}, j.combinedImplementationJar)
+}
+
+func (j *GenruleCombiner) Srcs() android.Paths {
+	return append(android.Paths{}, j.implementationAndResourceJars...)
+}
+
+func (j *GenruleCombiner) HeaderJars() android.Paths {
+	return j.headerJars
+}
+
+func (j *GenruleCombiner) ImplementationAndResourcesJars() android.Paths {
+	return j.implementationAndResourceJars
+}
+
+func (j *GenruleCombiner) DexJarBuildPath(ctx android.ModuleErrorfContext) android.Path {
+	return nil
+}
+
+func (j *GenruleCombiner) DexJarInstallPath() android.Path {
+	return nil
+}
+
+func (j *GenruleCombiner) AidlIncludeDirs() android.Paths {
+	return nil
+}
+
+func (j *GenruleCombiner) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
+	return nil
+}
+
+func (j *GenruleCombiner) JacocoReportClassesFile() android.Path {
+	return nil
+}
+
+func (j *GenruleCombiner) AndroidMk() android.AndroidMkData {
+	return android.AndroidMkData{
+		Class:      "JAVA_LIBRARIES",
+		OutputFile: android.OptionalPathForPath(j.combinedImplementationJar),
+		// Make does not support Windows Java modules
+		Disabled: j.Os() == android.Windows,
+		Include:  "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
+		Extra: []android.AndroidMkExtraFunc{
+			func(w io.Writer, outputFile android.Path) {
+				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
+				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", j.combinedHeaderJar.String())
+				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", j.combinedImplementationJar.String())
+			},
+		},
+	}
+}
+
+// implement the following interface for IDE completion.
+var _ android.IDEInfo = (*GenruleCombiner)(nil)
+
+func (j *GenruleCombiner) IDEInfo(ctx android.BaseModuleContext, ideInfo *android.IdeInfo) {
+	ideInfo.Deps = append(ideInfo.Deps, j.genruleCombinerProperties.Static_libs.GetOrDefault(ctx, nil)...)
+	ideInfo.Libs = append(ideInfo.Libs, j.genruleCombinerProperties.Static_libs.GetOrDefault(ctx, nil)...)
+	ideInfo.Deps = append(ideInfo.Deps, j.genruleCombinerProperties.Headers.GetOrDefault(ctx, nil)...)
+	ideInfo.Libs = append(ideInfo.Libs, j.genruleCombinerProperties.Headers.GetOrDefault(ctx, nil)...)
+}