diff options
author | 2025-01-30 13:51:14 -0800 | |
---|---|---|
committer | 2025-02-07 11:47:27 -0800 | |
commit | 8546f0c8832ed6e3093995e2359b7fc55d1e64be (patch) | |
tree | d7a3a33f3c5b57c3aa7ac1e6fd6e350d0ba40c96 | |
parent | 25ec2c1d642538e14b163281526311717d0eacb9 (diff) |
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
-rw-r--r-- | java/Android.bp | 2 | ||||
-rw-r--r-- | java/genrule_combiner.go | 252 | ||||
-rw-r--r-- | java/genrule_combiner_test.go | 237 | ||||
-rw-r--r-- | java/java.go | 1 |
4 files changed, 492 insertions, 0 deletions
diff --git a/java/Android.bp b/java/Android.bp index 885e6825a..911af8336 100644 --- a/java/Android.bp +++ b/java/Android.bp @@ -51,6 +51,7 @@ bootstrap_go_package { "gen.go", "generated_java_library.go", "genrule.go", + "genrule_combiner.go", "hiddenapi.go", "hiddenapi_modular.go", "hiddenapi_monolithic.go", @@ -95,6 +96,7 @@ bootstrap_go_package { "droiddoc_test.go", "droidstubs_test.go", "fuzz_test.go", + "genrule_combiner_test.go", "genrule_test.go", "generated_java_library_test.go", "hiddenapi_singleton_test.go", diff --git a/java/genrule_combiner.go b/java/genrule_combiner.go new file mode 100644 index 000000000..357dc2c76 --- /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)...) +} diff --git a/java/genrule_combiner_test.go b/java/genrule_combiner_test.go new file mode 100644 index 000000000..a45895202 --- /dev/null +++ b/java/genrule_combiner_test.go @@ -0,0 +1,237 @@ +// Copyright 2018 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 ( + "reflect" + "testing" + + "android/soong/android" +) + +func TestJarGenruleCombinerSingle(t *testing.T) { + t.Parallel() + t.Helper() + ctx := prepareForJavaTest.RunTestWithBp(t, ` + java_library { + name: "foo", + srcs: ["a.java"], + } + + java_genrule { + name: "gen", + tool_files: ["b.java"], + cmd: "$(location b.java) $(in) $(out)", + out: ["gen.jar"], + srcs: [":foo"], + } + + java_genrule_combiner { + name: "jarcomb", + static_libs: ["gen"], + headers: ["foo"], + } + + java_library { + name: "bar", + static_libs: ["jarcomb"], + srcs: ["c.java"], + } + + java_library { + name: "baz", + libs: ["jarcomb"], + srcs: ["c.java"], + } + `).TestContext + + fooMod := ctx.ModuleForTests("foo", "android_common") + fooCombined := fooMod.Output("turbine-combined/foo.jar") + fooOutputFiles, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), fooMod.Module(), android.OutputFilesProvider) + fooHeaderJars := fooOutputFiles.TaggedOutputFiles[".hjar"] + + genMod := ctx.ModuleForTests("gen", "android_common") + gen := genMod.Output("gen.jar") + + jarcombMod := ctx.ModuleForTests("jarcomb", "android_common") + jarcombInfo, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), jarcombMod.Module(), JavaInfoProvider) + jarcombOutputFiles, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), jarcombMod.Module(), android.OutputFilesProvider) + + // Confirm that jarcomb simply forwards the jarcomb implementation and the foo headers. + if len(jarcombOutputFiles.DefaultOutputFiles) != 1 || + android.PathRelativeToTop(jarcombOutputFiles.DefaultOutputFiles[0]) != android.PathRelativeToTop(gen.Output) { + t.Errorf("jarcomb Implementation %v is not [%q]", + android.PathsRelativeToTop(jarcombOutputFiles.DefaultOutputFiles), android.PathRelativeToTop(gen.Output)) + } + jarcombHeaderJars := jarcombOutputFiles.TaggedOutputFiles[".hjar"] + if !reflect.DeepEqual(jarcombHeaderJars, fooHeaderJars) { + t.Errorf("jarcomb Header jar %v is not %q", + jarcombHeaderJars, fooHeaderJars) + } + + // Confirm that JavaInfoProvider agrees. + if len(jarcombInfo.ImplementationJars) != 1 || + android.PathRelativeToTop(jarcombInfo.ImplementationJars[0]) != android.PathRelativeToTop(gen.Output) { + t.Errorf("jarcomb ImplementationJars %v is not [%q]", + android.PathsRelativeToTop(jarcombInfo.ImplementationJars), android.PathRelativeToTop(gen.Output)) + } + if len(jarcombInfo.HeaderJars) != 1 || + android.PathRelativeToTop(jarcombInfo.HeaderJars[0]) != android.PathRelativeToTop(fooCombined.Output) { + t.Errorf("jarcomb HeaderJars %v is not [%q]", + android.PathsRelativeToTop(jarcombInfo.HeaderJars), android.PathRelativeToTop(fooCombined.Output)) + } + + barMod := ctx.ModuleForTests("bar", "android_common") + bar := barMod.Output("javac/bar.jar") + barCombined := barMod.Output("combined/bar.jar") + + // Confirm that bar uses the Implementation from gen and headerJars from foo. + if len(barCombined.Inputs) != 2 || + barCombined.Inputs[0].String() != bar.Output.String() || + barCombined.Inputs[1].String() != gen.Output.String() { + t.Errorf("bar combined jar inputs %v is not [%q, %q]", + barCombined.Inputs.Strings(), bar.Output.String(), gen.Output.String()) + } + + bazMod := ctx.ModuleForTests("baz", "android_common") + baz := bazMod.Output("javac/baz.jar") + + string_in_list := func(s string, l []string) bool { + for _, v := range l { + if s == v { + return true + } + } + return false + } + + // Confirm that baz uses the headerJars from foo. + bazImplicitsRel := android.PathsRelativeToTop(baz.Implicits) + for _, v := range android.PathsRelativeToTop(fooHeaderJars) { + if !string_in_list(v, bazImplicitsRel) { + t.Errorf("baz Implicits %v does not contain %q", bazImplicitsRel, v) + } + } +} + +func TestJarGenruleCombinerMulti(t *testing.T) { + t.Parallel() + t.Helper() + ctx := prepareForJavaTest.RunTestWithBp(t, ` + java_library { + name: "foo1", + srcs: ["foo1_a.java"], + } + + java_library { + name: "foo2", + srcs: ["foo2_a.java"], + } + + java_genrule { + name: "gen1", + tool_files: ["b.java"], + cmd: "$(location b.java) $(in) $(out)", + out: ["gen1.jar"], + srcs: [":foo1"], + } + + java_genrule { + name: "gen2", + tool_files: ["b.java"], + cmd: "$(location b.java) $(in) $(out)", + out: ["gen2.jar"], + srcs: [":foo2"], + } + + // Combine multiple java_genrule modules. + java_genrule_combiner { + name: "jarcomb", + static_libs: ["gen1", "gen2"], + headers: ["foo1", "foo2"], + } + + java_library { + name: "bar", + static_libs: ["jarcomb"], + srcs: ["c.java"], + } + + java_library { + name: "baz", + libs: ["jarcomb"], + srcs: ["c.java"], + } + `).TestContext + + gen1Mod := ctx.ModuleForTests("gen1", "android_common") + gen1 := gen1Mod.Output("gen1.jar") + gen2Mod := ctx.ModuleForTests("gen2", "android_common") + gen2 := gen2Mod.Output("gen2.jar") + + jarcombMod := ctx.ModuleForTests("jarcomb", "android_common") + jarcomb := jarcombMod.Output("combined/jarcomb.jar") + jarcombTurbine := jarcombMod.Output("turbine-combined/jarcomb.jar") + _ = jarcombTurbine + jarcombInfo, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), jarcombMod.Module(), JavaInfoProvider) + _ = jarcombInfo + jarcombOutputFiles, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), jarcombMod.Module(), android.OutputFilesProvider) + jarcombHeaderJars := jarcombOutputFiles.TaggedOutputFiles[".hjar"] + + if len(jarcomb.Inputs) != 2 || + jarcomb.Inputs[0].String() != gen1.Output.String() || + jarcomb.Inputs[1].String() != gen2.Output.String() { + t.Errorf("jarcomb inputs %v are not [%q, %q]", + jarcomb.Inputs.Strings(), gen1.Output.String(), gen2.Output.String()) + } + + if len(jarcombHeaderJars) != 1 || + android.PathRelativeToTop(jarcombHeaderJars[0]) != android.PathRelativeToTop(jarcombTurbine.Output) { + t.Errorf("jarcomb Header jars %v is not [%q]", + android.PathsRelativeToTop(jarcombHeaderJars), android.PathRelativeToTop(jarcombTurbine.Output)) + } + + barMod := ctx.ModuleForTests("bar", "android_common") + bar := barMod.Output("javac/bar.jar") + barCombined := barMod.Output("combined/bar.jar") + + // Confirm that bar uses the Implementation and Headers from jarcomb. + if len(barCombined.Inputs) != 2 || + barCombined.Inputs[0].String() != bar.Output.String() || + barCombined.Inputs[1].String() != jarcomb.Output.String() { + t.Errorf("bar combined jar inputs %v is not [%q, %q]", + barCombined.Inputs.Strings(), bar.Output.String(), jarcomb.Output.String()) + } + + bazMod := ctx.ModuleForTests("baz", "android_common") + baz := bazMod.Output("javac/baz.jar") + + string_in_list := func(s string, l []string) bool { + for _, v := range l { + if s == v { + return true + } + } + return false + } + + // Confirm that baz uses the headerJars from foo. + bazImplicitsRel := android.PathsRelativeToTop(baz.Implicits) + for _, v := range android.PathsRelativeToTop(jarcombHeaderJars) { + if !string_in_list(v, bazImplicitsRel) { + t.Errorf("baz Implicits %v does not contain %q", bazImplicitsRel, v) + } + } +} diff --git a/java/java.go b/java/java.go index 900f0e32f..fbd4d573d 100644 --- a/java/java.go +++ b/java/java.go @@ -64,6 +64,7 @@ func registerJavaBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("java_api_library", ApiLibraryFactory) ctx.RegisterModuleType("java_api_contribution", ApiContributionFactory) ctx.RegisterModuleType("java_api_contribution_import", ApiContributionImportFactory) + ctx.RegisterModuleType("java_genrule_combiner", GenruleCombinerFactory) // This mutator registers dependencies on dex2oat for modules that should be // dexpreopted. This is done late when the final variants have been |