From 0ac87c21bd8354b407a1dfff5fbed38d73ce3a1a Mon Sep 17 00:00:00 2001 From: Jihoon Kang Date: Tue, 15 Nov 2022 19:06:14 +0000 Subject: [PATCH] Create java_api_contribution and java_api_library module Context - Droidstubs module is currently responsible not only for java api stubs generation, but also for checking api equality and compatibility. - Generating stubs with incomplete api text file that does not list entire api surface is done through metalava implicitly adding unlisted methods from java source files to the stubs in droidstubs module. - These factors make java stubs generation harder to debug, thus introduce `java_api_contribution` and `java_api_library` modules to make java api stubs and jar generation more explicit in Android.bp level and eventually easier to debug. Implementation - `java_api_contribution` module is included in api domains' directory and lists api text file directory to be added to the api surface - `java_api_library` collects all api text file that forms the api surface and creates stubs invoking metalava. Generated java stub files are converted into `.srcjar`, and eventually `.jar` file which is the complete api surface. Test: m Change-Id: I86f097cc8592334a5eaa900cec12764c5fcc09e7 --- java/java.go | 179 ++++++++++++++++++++++++++++++++++++++++++++++ java/java_test.go | 105 +++++++++++++++++++++++++++ 2 files changed, 284 insertions(+) diff --git a/java/java.go b/java/java.go index b6fc6b831..6886511d7 100644 --- a/java/java.go +++ b/java/java.go @@ -25,6 +25,7 @@ import ( "android/soong/bazel" "android/soong/bazel/cquery" + "android/soong/remoteexec" "github.com/google/blueprint" "github.com/google/blueprint/proptools" @@ -59,6 +60,8 @@ func registerJavaBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("java_device_for_host", DeviceForHostFactory) ctx.RegisterModuleType("java_host_for_device", HostForDeviceFactory) ctx.RegisterModuleType("dex_import", DexImportFactory) + ctx.RegisterModuleType("java_api_library", ApiLibraryFactory) + ctx.RegisterModuleType("java_api_contribution", ApiContributionFactory) // This mutator registers dependencies on dex2oat for modules that should be // dexpreopted. This is done late when the final variants have been @@ -1501,6 +1504,182 @@ func BinaryHostFactory() android.Module { return module } +type JavaApiContribution struct { + android.ModuleBase + android.DefaultableModuleBase + + properties struct { + // name of the API surface + Api_surface *string + + // relative path to the API signature text file + Api_file *string `android:"path"` + } +} + +func ApiContributionFactory() android.Module { + module := &JavaApiContribution{} + android.InitAndroidModule(module) + android.InitDefaultableModule(module) + module.AddProperties(&module.properties) + return module +} + +type JavaApiImportInfo struct { + ApiFile android.Path +} + +var JavaApiImportProvider = blueprint.NewProvider(JavaApiImportInfo{}) + +func (ap *JavaApiContribution) GenerateAndroidBuildActions(ctx android.ModuleContext) { + apiFile := android.PathForModuleSrc(ctx, String(ap.properties.Api_file)) + ctx.SetProvider(JavaApiImportProvider, JavaApiImportInfo{ + ApiFile: apiFile, + }) +} + +type ApiLibrary struct { + android.ModuleBase + android.DefaultableModuleBase + + properties JavaApiLibraryProperties + + stubsSrcJar android.WritablePath + stubsJar android.WritablePath +} + +type JavaApiLibraryProperties struct { + // name of the API surface + Api_surface *string + + // list of API provider modules that consists this API surface + Api_providers []string + + // List of flags to be passed to the javac compiler to generate jar file + Javacflags []string +} + +func ApiLibraryFactory() android.Module { + module := &ApiLibrary{} + android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon) + android.InitDefaultableModule(module) + module.AddProperties(&module.properties) + return module +} + +func (al *ApiLibrary) ApiSurface() *string { + return al.properties.Api_surface +} + +func (al *ApiLibrary) StubsJar() android.Path { + return al.stubsJar +} + +func metalavaStubCmd(ctx android.ModuleContext, rule *android.RuleBuilder, + srcs android.Paths, homeDir android.WritablePath) *android.RuleBuilderCommand { + rule.Command().Text("rm -rf").Flag(homeDir.String()) + rule.Command().Text("mkdir -p").Flag(homeDir.String()) + + cmd := rule.Command() + cmd.FlagWithArg("ANDROID_PREFS_ROOT=", homeDir.String()) + + if metalavaUseRbe(ctx) { + rule.Remoteable(android.RemoteRuleSupports{RBE: true}) + execStrategy := ctx.Config().GetenvWithDefault("RBE_METALAVA_EXEC_STRATEGY", remoteexec.LocalExecStrategy) + labels := map[string]string{"type": "tool", "name": "metalava"} + + pool := ctx.Config().GetenvWithDefault("RBE_METALAVA_POOL", "java16") + rule.Rewrapper(&remoteexec.REParams{ + Labels: labels, + ExecStrategy: execStrategy, + ToolchainInputs: []string{config.JavaCmd(ctx).String()}, + Platform: map[string]string{remoteexec.PoolKey: pool}, + }) + } + + cmd.BuiltTool("metalava").ImplicitTool(ctx.Config().HostJavaToolPath(ctx, "metalava.jar")). + Flag(config.JavacVmFlags). + Flag("-J--add-opens=java.base/java.util=ALL-UNNAMED"). + FlagWithArg("-encoding ", "UTF-8"). + FlagWithInputList("--source-files ", srcs, " ") + + cmd.Flag("--no-banner"). + Flag("--color"). + Flag("--quiet"). + Flag("--format=v2"). + FlagWithArg("--repeat-errors-max ", "10"). + FlagWithArg("--hide ", "UnresolvedImport"). + FlagWithArg("--hide ", "InvalidNullabilityOverride"). + FlagWithArg("--hide ", "ChangedDefault") + + return cmd +} + +func (al *ApiLibrary) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath) { + if stubsDir.Valid() { + cmd.FlagWithArg("--stubs ", stubsDir.String()) + } +} + +var javaApiProviderTag = dependencyTag{name: "java-api-provider"} + +func (al *ApiLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { + apiProviders := al.properties.Api_providers + for _, apiProviderName := range apiProviders { + ctx.AddDependency(ctx.Module(), javaApiProviderTag, apiProviderName) + } +} + +func (al *ApiLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) { + + rule := android.NewRuleBuilder(pctx, ctx) + + rule.Sbox(android.PathForModuleOut(ctx, "metalava"), + android.PathForModuleOut(ctx, "metalava.sbox.textproto")). + SandboxInputs() + + var stubsDir android.OptionalPath + stubsDir = android.OptionalPathForPath(android.PathForModuleOut(ctx, "metalava", "stubsDir")) + rule.Command().Text("rm -rf").Text(stubsDir.String()) + rule.Command().Text("mkdir -p").Text(stubsDir.String()) + + homeDir := android.PathForModuleOut(ctx, "metalava", "home") + + apiProviders := al.properties.Api_providers + srcFiles := make([]android.Path, len(apiProviders)) + for i, apiProviderName := range apiProviders { + apiProvider := ctx.GetDirectDepWithTag(apiProviderName, javaApiProviderTag) + if apiProvider == nil { + panic(fmt.Errorf("Java API provider module %s not found, called from %s", apiProviderName, al.Name())) + } + provider := ctx.OtherModuleProvider(apiProvider, JavaApiImportProvider).(JavaApiImportInfo) + srcFiles[i] = android.PathForModuleSrc(ctx, provider.ApiFile.String()) + } + + cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir) + + al.stubsFlags(ctx, cmd, stubsDir) + + al.stubsSrcJar = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"-"+"stubs.srcjar") + rule.Command(). + BuiltTool("soong_zip"). + Flag("-write_if_changed"). + Flag("-jar"). + FlagWithOutput("-o ", al.stubsSrcJar). + FlagWithArg("-C ", stubsDir.String()). + FlagWithArg("-D ", stubsDir.String()) + + rule.Build("metalava", "metalava merged") + + al.stubsJar = android.PathForModuleOut(ctx, ctx.ModuleName(), "android.jar") + + var flags javaBuilderFlags + flags.javacFlags = strings.Join(al.properties.Javacflags, " ") + + TransformJavaToClasses(ctx, al.stubsJar, 0, android.Paths{}, + android.Paths{al.stubsSrcJar}, flags, android.Paths{}) +} + // // Java prebuilts // diff --git a/java/java_test.go b/java/java_test.go index f06b520d5..3f8cd8e90 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -1806,3 +1806,108 @@ func TestDeviceBinaryWrapperGeneration(t *testing.T) { srcs: ["foo.java"], }`) } + +func TestJavaApiLibraryAndProviderLink(t *testing.T) { + provider_bp_a := ` + java_api_contribution { + name: "foo1", + api_file: "foo1.txt", + } + ` + provider_bp_b := `java_api_contribution { + name: "foo2", + api_file: "foo2.txt", + } + ` + ctx, _ := testJavaWithFS(t, ` + java_api_library { + name: "bar1", + api_surface: "public", + api_providers: ["foo1"], + } + + java_api_library { + name: "bar2", + api_surface: "system", + api_providers: ["foo1", "foo2"], + } + `, + map[string][]byte{ + "a/Android.bp": []byte(provider_bp_a), + "b/Android.bp": []byte(provider_bp_b), + }) + + testcases := []struct { + moduleName string + sourceTextFileDirs []string + }{ + { + moduleName: "bar1", + sourceTextFileDirs: []string{"a/foo1.txt"}, + }, + { + moduleName: "bar2", + sourceTextFileDirs: []string{"a/foo1.txt", "b/foo2.txt"}, + }, + } + for _, c := range testcases { + m := ctx.ModuleForTests(c.moduleName, "android_common") + manifest := m.Output("metalava.sbox.textproto") + sboxProto := android.RuleBuilderSboxProtoForTests(t, manifest) + manifestCommand := sboxProto.Commands[0].GetCommand() + sourceFilesFlag := "--source-files " + strings.Join(c.sourceTextFileDirs, " ") + android.AssertStringDoesContain(t, "source text files not present", manifestCommand, sourceFilesFlag) + } +} + +func TestJavaApiLibraryJarGeneration(t *testing.T) { + provider_bp_a := ` + java_api_contribution { + name: "foo1", + api_file: "foo1.txt", + } + ` + provider_bp_b := `java_api_contribution { + name: "foo2", + api_file: "foo2.txt", + } + ` + ctx, _ := testJavaWithFS(t, ` + java_api_library { + name: "bar1", + api_surface: "public", + api_providers: ["foo1"], + } + + java_api_library { + name: "bar2", + api_surface: "system", + api_providers: ["foo1", "foo2"], + } + `, + map[string][]byte{ + "a/Android.bp": []byte(provider_bp_a), + "b/Android.bp": []byte(provider_bp_b), + }) + + testcases := []struct { + moduleName string + outputJarName string + }{ + { + moduleName: "bar1", + outputJarName: "bar1/android.jar", + }, + { + moduleName: "bar2", + outputJarName: "bar2/android.jar", + }, + } + for _, c := range testcases { + m := ctx.ModuleForTests(c.moduleName, "android_common") + outputs := fmt.Sprint(m.AllOutputs()) + if !strings.Contains(outputs, c.outputJarName) { + t.Errorf("Module output does not contain expected jar %s", c.outputJarName) + } + } +}