// Copyright 2021 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" "path/filepath" "regexp" "strings" "testing" "android/soong/android" "github.com/google/blueprint/proptools" ) func TestJavaSdkLibrary(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithPrebuiltApis(map[string][]string{ "28": {"foo"}, "29": {"foo"}, "30": {"bar", "barney", "baz", "betty", "foo", "fred", "quuz", "wilma"}, }), android.FixtureModifyConfig(func(config android.Config) { config.SetApiLibraries([]string{"foo"}) }), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.BuildFlags = map[string]string{ "RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true", } }), ).RunTestWithBp(t, ` droiddoc_exported_dir { name: "droiddoc-templates-sdk", path: ".", } java_sdk_library { name: "foo", srcs: ["a.java", "b.java"], api_packages: ["foo"], } java_sdk_library { name: "bar", srcs: ["a.java", "b.java"], api_packages: ["bar"], exclude_kotlinc_generated_files: true, } java_library { name: "baz", srcs: ["c.java"], libs: ["foo", "bar.stubs"], sdk_version: "system_current", } java_sdk_library { name: "barney", srcs: ["c.java"], api_only: true, } java_sdk_library { name: "betty", srcs: ["c.java"], shared_library: false, } java_sdk_library_import { name: "quuz", public: { jars: ["c.jar"], current_api: "api/current.txt", removed_api: "api/removed.txt", }, } java_sdk_library_import { name: "fred", public: { jars: ["b.jar"], }, } java_sdk_library_import { name: "wilma", public: { jars: ["b.jar"], }, shared_library: false, } java_library { name: "qux", srcs: ["c.java"], libs: ["baz", "fred", "quuz.stubs", "wilma", "barney", "betty"], sdk_version: "system_current", } java_library { name: "baz-test", srcs: ["c.java"], libs: ["foo"], sdk_version: "test_current", } java_library { name: "baz-29", srcs: ["c.java"], libs: ["foo"], sdk_version: "system_29", } java_library { name: "baz-module-30", srcs: ["c.java"], libs: ["foo"], sdk_version: "module_30", } `) // check the existence of the internal modules foo := result.ModuleForTests("foo", "android_common") result.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common") result.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common") result.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common") result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common") result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common") result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common") result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo")+".api.contribution", "") result.ModuleForTests(apiScopePublic.apiLibraryModuleName("foo"), "android_common") result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common") result.ModuleForTests("foo.api.public.28", "") result.ModuleForTests("foo.api.system.28", "") result.ModuleForTests("foo.api.test.28", "") exportedComponentsInfo, _ := android.SingletonModuleProvider(result, foo.Module(), android.ExportedComponentsInfoProvider) expectedFooExportedComponents := []string{ "foo-removed.api.combined.public.latest", "foo-removed.api.combined.system.latest", "foo.api.combined.public.latest", "foo.api.combined.system.latest", "foo.stubs", "foo.stubs.exportable", "foo.stubs.exportable.system", "foo.stubs.exportable.test", "foo.stubs.source", "foo.stubs.source.system", "foo.stubs.source.test", "foo.stubs.system", "foo.stubs.test", } android.AssertArrayString(t, "foo exported components", expectedFooExportedComponents, exportedComponentsInfo.Components) bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac") // tests if baz is actually linked to the stubs lib android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.system.jar") // ... and not to the impl lib android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.jar") // test if baz is not linked to the system variant of foo android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.jar") bazTestJavac := result.ModuleForTests("baz-test", "android_common").Rule("javac") // tests if baz-test is actually linked to the test stubs lib android.AssertStringDoesContain(t, "baz-test javac classpath", bazTestJavac.Args["classpath"], "foo.stubs.test.jar") baz29Javac := result.ModuleForTests("baz-29", "android_common").Rule("javac") // tests if baz-29 is actually linked to the system 29 stubs lib android.AssertStringDoesContain(t, "baz-29 javac classpath", baz29Javac.Args["classpath"], "prebuilts/sdk/29/system/foo.jar") bazModule30Javac := result.ModuleForTests("baz-module-30", "android_common").Rule("javac") // tests if "baz-module-30" is actually linked to the module 30 stubs lib android.AssertStringDoesContain(t, "baz-module-30 javac classpath", bazModule30Javac.Args["classpath"], "prebuilts/sdk/30/module-lib/foo.jar") // test if baz has exported SDK lib names foo and bar to qux qux := result.ModuleForTests("qux", "android_common") if quxLib, ok := qux.Module().(*Library); ok { requiredSdkLibs, optionalSdkLibs := quxLib.ClassLoaderContexts().UsesLibs() android.AssertDeepEquals(t, "qux exports (required)", []string{"fred", "quuz", "foo", "bar"}, requiredSdkLibs) android.AssertDeepEquals(t, "qux exports (optional)", []string{}, optionalSdkLibs) } // test if quuz have created the api_contribution module result.ModuleForTests(apiScopePublic.stubsSourceModuleName("quuz")+".api.contribution", "") fooImplDexJar := result.ModuleForTests("foo.impl", "android_common").Rule("d8") // tests if kotlinc generated files are NOT excluded from output of foo.impl. android.AssertStringDoesNotContain(t, "foo.impl dex", fooImplDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module") barImplDexJar := result.ModuleForTests("bar.impl", "android_common").Rule("d8") // tests if kotlinc generated files are excluded from output of bar.impl. android.AssertStringDoesContain(t, "bar.impl dex", barImplDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module") } func TestJavaSdkLibrary_UpdatableLibrary(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithPrebuiltApis(map[string][]string{ "28": {"foo"}, "29": {"foo"}, "30": {"foo", "fooUpdatable", "fooUpdatableErr"}, }), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V", "W", "X"} }), ).RunTestWithBp(t, ` java_sdk_library { name: "fooUpdatable", srcs: ["a.java", "b.java"], api_packages: ["foo"], on_bootclasspath_since: "U", on_bootclasspath_before: "V", min_device_sdk: "W", max_device_sdk: "X", min_sdk_version: "S", } java_sdk_library { name: "foo", srcs: ["a.java", "b.java"], api_packages: ["foo"], } `) // test that updatability attributes are passed on correctly fooUpdatable := result.ModuleForTests("fooUpdatable.xml", "android_common").Output("fooUpdatable.xml") fooUpdatableContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooUpdatable) android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `on-bootclasspath-since="U"`) android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `on-bootclasspath-before="V"`) android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `min-device-sdk="W"`) android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `max-device-sdk="X"`) // double check that updatability attributes are not written if they don't exist in the bp file // the permissions file for the foo library defined above fooPermissions := result.ModuleForTests("foo.xml", "android_common").Output("foo.xml") fooPermissionsContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooPermissions) android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `on-bootclasspath-since`) android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `on-bootclasspath-before`) android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `min-device-sdk`) android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `max-device-sdk`) } func TestJavaSdkLibrary_UpdatableLibrary_Validation_ValidVersion(t *testing.T) { android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithPrebuiltApis(map[string][]string{ "30": {"fooUpdatable", "fooUpdatableErr"}, }), ).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern( []string{ `on_bootclasspath_since: "aaa" could not be parsed as an integer and is not a recognized codename`, `on_bootclasspath_before: "bbc" could not be parsed as an integer and is not a recognized codename`, `min_device_sdk: "ccc" could not be parsed as an integer and is not a recognized codename`, `max_device_sdk: "current" is not an allowed value for this attribute`, })).RunTestWithBp(t, ` java_sdk_library { name: "fooUpdatableErr", srcs: ["a.java", "b.java"], api_packages: ["foo"], on_bootclasspath_since: "aaa", on_bootclasspath_before: "bbc", min_device_sdk: "ccc", max_device_sdk: "current", } `) } func TestJavaSdkLibrary_UpdatableLibrary_Validation_AtLeastTAttributes(t *testing.T) { android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithPrebuiltApis(map[string][]string{ "28": {"foo"}, }), ).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern( []string{ "on_bootclasspath_since: Attribute value needs to be at least T", "on_bootclasspath_before: Attribute value needs to be at least T", "min_device_sdk: Attribute value needs to be at least T", "max_device_sdk: Attribute value needs to be at least T", }, )).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java", "b.java"], api_packages: ["foo"], on_bootclasspath_since: "S", on_bootclasspath_before: "S", min_device_sdk: "S", max_device_sdk: "S", min_sdk_version: "S", } `) } func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdk(t *testing.T) { android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithPrebuiltApis(map[string][]string{ "28": {"foo"}, }), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V"} }), ).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern( []string{ "min_device_sdk can't be greater than max_device_sdk", }, )).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java", "b.java"], api_packages: ["foo"], min_device_sdk: "V", max_device_sdk: "U", min_sdk_version: "S", } `) } func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdkAndModuleMinSdk(t *testing.T) { android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithPrebuiltApis(map[string][]string{ "28": {"foo"}, }), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V"} }), ).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern( []string{ regexp.QuoteMeta("min_device_sdk: Can't be less than module's min sdk (V)"), regexp.QuoteMeta("max_device_sdk: Can't be less than module's min sdk (V)"), }, )).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java", "b.java"], api_packages: ["foo"], min_device_sdk: "U", max_device_sdk: "U", min_sdk_version: "V", } `) } func TestJavaSdkLibrary_UpdatableLibrary_usesNewTag(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithPrebuiltApis(map[string][]string{ "30": {"foo"}, }), ).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java", "b.java"], min_device_sdk: "Tiramisu", min_sdk_version: "S", } `) // test that updatability attributes are passed on correctly fooUpdatable := result.ModuleForTests("foo.xml", "android_common").Output("foo.xml") fooUpdatableContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooUpdatable) android.AssertStringDoesContain(t, "foo.xml contents", fooUpdatableContents, ` module is . barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac") stubLibraries := []string{ stubsPath("foo-public", apiScopePublic), stubsPath("foo-system", apiScopeSystem), stubsPath("foo-module-lib", apiScopeModuleLib), stubsPath("foo-system-server", apiScopeSystemServer), } expectedPattern := fmt.Sprintf(`^-classpath .*:\Q%s\E$`, strings.Join(stubLibraries, ":")) if expected, actual := expectedPattern, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) { t.Errorf("expected pattern %q to match %#q", expected, actual) } } func TestJavaSdkLibrary_MissingScope(t *testing.T) { prepareForJavaTest. ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`requires api scope module-lib from foo but it only has \[\] available`)). RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java"], public: { enabled: false, }, } java_library { name: "baz", srcs: ["a.java"], libs: ["foo"], sdk_version: "module_current", } `) } func TestJavaSdkLibrary_FallbackScope(t *testing.T) { android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("foo"), ).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java"], system: { enabled: true, }, } java_library { name: "baz", srcs: ["a.java"], libs: ["foo"], // foo does not have module-lib scope so it should fallback to system sdk_version: "module_current", } `) } func TestJavaSdkLibrary_DefaultToStubs(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("foo"), ).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java"], system: { enabled: true, }, default_to_stubs: true, } java_library { name: "baz", srcs: ["a.java"], libs: ["foo"], // does not have sdk_version set, should fallback to module, // which will then fallback to system because the module scope // is not enabled. } `) // The baz library should depend on the system stubs jar. bazLibrary := result.ModuleForTests("baz", "android_common").Rule("javac") if expected, actual := `^-classpath .*:out/soong/[^:]*/turbine-combined/foo\.stubs.system\.jar$`, bazLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) { t.Errorf("expected %q, found %#q", expected, actual) } } func TestJavaSdkLibraryImport(t *testing.T) { result := prepareForJavaTest.RunTestWithBp(t, ` java_library { name: "foo", srcs: ["a.java"], libs: ["sdklib"], sdk_version: "current", } java_library { name: "foo.system", srcs: ["a.java"], libs: ["sdklib"], sdk_version: "system_current", } java_library { name: "foo.test", srcs: ["a.java"], libs: ["sdklib"], sdk_version: "test_current", } java_sdk_library_import { name: "sdklib", public: { jars: ["a.jar"], }, system: { jars: ["b.jar"], }, test: { jars: ["c.jar"], stub_srcs: ["c.java"], }, } `) for _, scope := range []string{"", ".system", ".test"} { fooModule := result.ModuleForTests("foo"+scope, "android_common") javac := fooModule.Rule("javac") sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Rule("combineJar").Output android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], sdklibStubsJar.String()) } CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{ `dex2oatd`, `prebuilt_sdklib.stubs`, `prebuilt_sdklib.stubs.source.test`, `prebuilt_sdklib.stubs.system`, `prebuilt_sdklib.stubs.test`, }) } func TestJavaSdkLibraryImport_WithSource(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("sdklib"), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.BuildFlags = map[string]string{ "RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true", } }), ).RunTestWithBp(t, ` java_sdk_library { name: "sdklib", srcs: ["a.java"], sdk_version: "none", system_modules: "none", public: { enabled: true, }, } java_sdk_library_import { name: "sdklib", public: { jars: ["a.jar"], }, } `) CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{ `dex2oatd`, `prebuilt_sdklib`, `sdklib-removed.api.combined.public.latest`, `sdklib.api.combined.public.latest`, `sdklib.impl`, `sdklib.stubs`, `sdklib.stubs.exportable`, `sdklib.stubs.source`, `sdklib.xml`, }) CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{ `all_apex_contributions`, `prebuilt_sdklib.stubs`, `sdklib.impl`, // This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the // dependency is added after prebuilts may have been renamed and so has to use // the renamed name. `sdklib.xml`, }) } func testJavaSdkLibraryImport_Preferred(t *testing.T, prefer string, preparer android.FixturePreparer) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("sdklib"), preparer, android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.BuildFlags = map[string]string{ "RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true", } }), ).RunTestWithBp(t, ` java_sdk_library { name: "sdklib", srcs: ["a.java"], sdk_version: "none", system_modules: "none", public: { enabled: true, }, } java_sdk_library_import { name: "sdklib", `+prefer+` public: { jars: ["a.jar"], stub_srcs: ["a.java"], current_api: "current.txt", removed_api: "removed.txt", annotations: "annotations.zip", }, } java_library { name: "combined", static_libs: [ "sdklib.stubs", ], java_resources: [ ":sdklib.stubs.source", ":sdklib{.public.api.txt}", ":sdklib{.public.removed-api.txt}", ":sdklib{.public.annotations.zip}", ], sdk_version: "none", system_modules: "none", } java_library { name: "public", srcs: ["a.java"], libs: ["sdklib"], sdk_version: "current", } `) CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{ `prebuilt_sdklib`, `sdklib-removed.api.combined.public.latest`, `sdklib.api.combined.public.latest`, `sdklib.impl`, `sdklib.stubs`, `sdklib.stubs.exportable`, `sdklib.stubs.source`, `sdklib.xml`, }) CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{ `all_apex_contributions`, `dex2oatd`, `prebuilt_sdklib.stubs`, `prebuilt_sdklib.stubs.source`, `sdklib.impl`, `sdklib.xml`, }) // Make sure that dependencies on child modules use the prebuilt when preferred. CheckModuleDependencies(t, result.TestContext, "combined", "android_common", []string{ // Each use of :sdklib{...} adds a dependency onto prebuilt_sdklib. `prebuilt_sdklib`, `prebuilt_sdklib`, `prebuilt_sdklib`, `prebuilt_sdklib.stubs`, `prebuilt_sdklib.stubs.source`, }) // Make sure that dependencies on sdklib that resolve to one of the child libraries use the // prebuilt library. public := result.ModuleForTests("public", "android_common") rule := public.Output("javac/public.jar") inputs := rule.Implicits.Strings() expected := "out/soong/.intermediates/prebuilt_sdklib.stubs/android_common/combined/sdklib.stubs.jar" if !android.InList(expected, inputs) { t.Errorf("expected %q to contain %q", inputs, expected) } } func TestJavaSdkLibraryImport_Preferred(t *testing.T) { t.Run("prefer", func(t *testing.T) { testJavaSdkLibraryImport_Preferred(t, "prefer: true,", android.NullFixturePreparer) }) } // If a module is listed in `mainline_module_contributions, it should be used // It will supersede any other source vs prebuilt selection mechanism like `prefer` attribute func TestSdkLibraryImport_MetadataModuleSupersedesPreferred(t *testing.T) { bp := ` apex_contributions { name: "my_mainline_module_contributions", api_domain: "my_mainline_module", contents: [ // legacy mechanism prefers the prebuilt // mainline_module_contributions supersedes this since source is listed explicitly "sdklib.prebuilt_preferred_using_legacy_flags", // legacy mechanism prefers the source // mainline_module_contributions supersedes this since prebuilt is listed explicitly "prebuilt_sdklib.source_preferred_using_legacy_flags", ], } java_sdk_library { name: "sdklib.prebuilt_preferred_using_legacy_flags", srcs: ["a.java"], sdk_version: "none", system_modules: "none", public: { enabled: true, }, system: { enabled: true, } } java_sdk_library_import { name: "sdklib.prebuilt_preferred_using_legacy_flags", prefer: true, // prebuilt is preferred using legacy mechanism public: { jars: ["a.jar"], stub_srcs: ["a.java"], current_api: "current.txt", removed_api: "removed.txt", annotations: "annotations.zip", }, system: { jars: ["a.jar"], stub_srcs: ["a.java"], current_api: "current.txt", removed_api: "removed.txt", annotations: "annotations.zip", }, } java_sdk_library { name: "sdklib.source_preferred_using_legacy_flags", srcs: ["a.java"], sdk_version: "none", system_modules: "none", public: { enabled: true, }, system: { enabled: true, } } java_sdk_library_import { name: "sdklib.source_preferred_using_legacy_flags", prefer: false, // source is preferred using legacy mechanism public: { jars: ["a.jar"], stub_srcs: ["a.java"], current_api: "current.txt", removed_api: "removed.txt", annotations: "annotations.zip", }, system: { jars: ["a.jar"], stub_srcs: ["a.java"], current_api: "current.txt", removed_api: "removed.txt", annotations: "annotations.zip", }, } // rdeps java_library { name: "public", srcs: ["a.java"], libs: [ // this should get source since source is listed in my_mainline_module_contributions "sdklib.prebuilt_preferred_using_legacy_flags.stubs", "sdklib.prebuilt_preferred_using_legacy_flags.stubs.system", // this should get prebuilt since source is listed in my_mainline_module_contributions "sdklib.source_preferred_using_legacy_flags.stubs", "sdklib.source_preferred_using_legacy_flags.stubs.system", ], } ` result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("sdklib.source_preferred_using_legacy_flags", "sdklib.prebuilt_preferred_using_legacy_flags"), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.BuildFlags = map[string]string{ "RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "my_mainline_module_contributions", } }), ).RunTestWithBp(t, bp) // Make sure that rdeps get the correct source vs prebuilt based on mainline_module_contributions public := result.ModuleForTests("public", "android_common") rule := public.Output("javac/public.jar") inputs := rule.Implicits.Strings() expectedInputs := []string{ // source "out/soong/.intermediates/sdklib.prebuilt_preferred_using_legacy_flags.stubs/android_common/turbine-combined/sdklib.prebuilt_preferred_using_legacy_flags.stubs.jar", "out/soong/.intermediates/sdklib.prebuilt_preferred_using_legacy_flags.stubs.system/android_common/turbine-combined/sdklib.prebuilt_preferred_using_legacy_flags.stubs.system.jar", // prebuilt "out/soong/.intermediates/prebuilt_sdklib.source_preferred_using_legacy_flags.stubs/android_common/combined/sdklib.source_preferred_using_legacy_flags.stubs.jar", "out/soong/.intermediates/prebuilt_sdklib.source_preferred_using_legacy_flags.stubs.system/android_common/combined/sdklib.source_preferred_using_legacy_flags.stubs.system.jar", } for _, expected := range expectedInputs { if !android.InList(expected, inputs) { t.Errorf("expected %q to contain %q", inputs, expected) } } } func TestJavaSdkLibraryEnforce(t *testing.T) { partitionToBpOption := func(partition string) string { switch partition { case "system": return "" case "vendor": return "soc_specific: true," case "product": return "product_specific: true," default: panic("Invalid partition group name: " + partition) } } type testConfigInfo struct { libraryType string fromPartition string toPartition string enforceProductInterface bool enforceJavaSdkLibraryCheck bool allowList []string } createPreparer := func(info testConfigInfo) android.FixturePreparer { bpFileTemplate := ` java_library { name: "foo", srcs: ["foo.java"], libs: ["bar"], sdk_version: "current", %s } %s { name: "bar", srcs: ["bar.java"], sdk_version: "current", %s } ` bpFile := fmt.Sprintf(bpFileTemplate, partitionToBpOption(info.fromPartition), info.libraryType, partitionToBpOption(info.toPartition)) return android.GroupFixturePreparers( PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("bar"), android.FixtureWithRootAndroidBp(bpFile), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface) variables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck) variables.InterPartitionJavaLibraryAllowList = info.allowList }), ) } runTest := func(t *testing.T, info testConfigInfo, expectedErrorPattern string) { t.Run(fmt.Sprintf("%v", info), func(t *testing.T) { errorHandler := android.FixtureExpectsNoErrors if expectedErrorPattern != "" { errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorPattern) } android.GroupFixturePreparers( prepareForJavaTest, createPreparer(info), ). ExtendWithErrorHandler(errorHandler). RunTest(t) }) } errorMessage := "is not allowed across the partitions" runTest(t, testConfigInfo{ libraryType: "java_library", fromPartition: "product", toPartition: "system", enforceProductInterface: true, enforceJavaSdkLibraryCheck: false, }, "") runTest(t, testConfigInfo{ libraryType: "java_library", fromPartition: "product", toPartition: "system", enforceProductInterface: false, enforceJavaSdkLibraryCheck: true, }, "") runTest(t, testConfigInfo{ libraryType: "java_library", fromPartition: "product", toPartition: "system", enforceProductInterface: true, enforceJavaSdkLibraryCheck: true, }, errorMessage) runTest(t, testConfigInfo{ libraryType: "java_library", fromPartition: "vendor", toPartition: "system", enforceProductInterface: true, enforceJavaSdkLibraryCheck: true, }, errorMessage) runTest(t, testConfigInfo{ libraryType: "java_library", fromPartition: "vendor", toPartition: "system", enforceProductInterface: true, enforceJavaSdkLibraryCheck: true, allowList: []string{"bar"}, }, "") runTest(t, testConfigInfo{ libraryType: "java_library", fromPartition: "vendor", toPartition: "product", enforceProductInterface: true, enforceJavaSdkLibraryCheck: true, }, errorMessage) runTest(t, testConfigInfo{ libraryType: "java_sdk_library", fromPartition: "product", toPartition: "system", enforceProductInterface: true, enforceJavaSdkLibraryCheck: true, }, "") runTest(t, testConfigInfo{ libraryType: "java_sdk_library", fromPartition: "vendor", toPartition: "system", enforceProductInterface: true, enforceJavaSdkLibraryCheck: true, }, "") runTest(t, testConfigInfo{ libraryType: "java_sdk_library", fromPartition: "vendor", toPartition: "product", enforceProductInterface: true, enforceJavaSdkLibraryCheck: true, }, "") } func TestJavaSdkLibraryDist(t *testing.T) { result := android.GroupFixturePreparers( PrepareForTestWithJavaBuildComponents, PrepareForTestWithJavaDefaultModules, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis( "sdklib_no_group", "sdklib_group_foo", "sdklib_owner_foo", "foo"), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.BuildFlags = map[string]string{ "RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true", } }), ).RunTestWithBp(t, ` java_sdk_library { name: "sdklib_no_group", srcs: ["foo.java"], } java_sdk_library { name: "sdklib_group_foo", srcs: ["foo.java"], dist_group: "foo", } java_sdk_library { name: "sdklib_owner_foo", srcs: ["foo.java"], owner: "foo", } java_sdk_library { name: "sdklib_stem_foo", srcs: ["foo.java"], dist_stem: "foo", } `) type testCase struct { module string distDir string distStem string } testCases := []testCase{ { module: "sdklib_no_group", distDir: "apistubs/unknown/public", distStem: "sdklib_no_group.jar", }, { module: "sdklib_group_foo", distDir: "apistubs/foo/public", distStem: "sdklib_group_foo.jar", }, { // Owner doesn't affect distDir after b/186723288. module: "sdklib_owner_foo", distDir: "apistubs/unknown/public", distStem: "sdklib_owner_foo.jar", }, { module: "sdklib_stem_foo", distDir: "apistubs/unknown/public", distStem: "foo.jar", }, } for _, tt := range testCases { t.Run(tt.module, func(t *testing.T) { m := result.ModuleForTests(apiScopePublic.exportableStubsLibraryModuleName(tt.module), "android_common").Module().(*Library) dists := m.Dists() if len(dists) != 1 { t.Fatalf("expected exactly 1 dist entry, got %d", len(dists)) } if g, w := String(dists[0].Dir), tt.distDir; g != w { t.Errorf("expected dist dir %q, got %q", w, g) } if g, w := String(dists[0].Dest), tt.distStem; g != w { t.Errorf("expected dist stem %q, got %q", w, g) } }) } } func TestSdkLibrary_CheckMinSdkVersion(t *testing.T) { preparer := android.GroupFixturePreparers( PrepareForTestWithJavaBuildComponents, PrepareForTestWithJavaDefaultModules, PrepareForTestWithJavaSdkLibraryFiles, ) preparer.RunTestWithBp(t, ` java_sdk_library { name: "sdklib", srcs: ["a.java"], static_libs: ["util"], min_sdk_version: "30", unsafe_ignore_missing_latest_api: true, } java_library { name: "util", srcs: ["a.java"], min_sdk_version: "30", } `) preparer. RunTestWithBp(t, ` java_sdk_library { name: "sdklib", srcs: ["a.java"], libs: ["util"], impl_only_libs: ["util"], stub_only_libs: ["util"], stub_only_static_libs: ["util"], min_sdk_version: "30", unsafe_ignore_missing_latest_api: true, } java_library { name: "util", srcs: ["a.java"], } `) preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "util".*should support min_sdk_version\(30\)`)). RunTestWithBp(t, ` java_sdk_library { name: "sdklib", srcs: ["a.java"], static_libs: ["util"], min_sdk_version: "30", unsafe_ignore_missing_latest_api: true, } java_library { name: "util", srcs: ["a.java"], min_sdk_version: "31", } `) preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "another_util".*should support min_sdk_version\(30\)`)). RunTestWithBp(t, ` java_sdk_library { name: "sdklib", srcs: ["a.java"], static_libs: ["util"], min_sdk_version: "30", unsafe_ignore_missing_latest_api: true, } java_library { name: "util", srcs: ["a.java"], static_libs: ["another_util"], min_sdk_version: "30", } java_library { name: "another_util", srcs: ["a.java"], min_sdk_version: "31", } `) } func TestJavaSdkLibrary_StubOnlyLibs_PassedToDroidstubs(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("foo"), ).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java"], public: { enabled: true, }, stub_only_libs: ["bar-lib"], } java_library { name: "bar-lib", srcs: ["b.java"], } `) // The foo.stubs.source should depend on bar-lib fooStubsSources := result.ModuleForTests("foo.stubs.source", "android_common").Module().(*Droidstubs) android.AssertStringListContains(t, "foo stubs should depend on bar-lib", fooStubsSources.Javadoc.properties.Libs, "bar-lib") } func TestJavaSdkLibrary_Scope_Libs_PassedToDroidstubs(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("foo"), ).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java"], public: { enabled: true, libs: ["bar-lib"], }, } java_library { name: "bar-lib", srcs: ["b.java"], } `) // The foo.stubs.source should depend on bar-lib fooStubsSources := result.ModuleForTests("foo.stubs.source", "android_common").Module().(*Droidstubs) android.AssertStringListContains(t, "foo stubs should depend on bar-lib", fooStubsSources.Javadoc.properties.Libs, "bar-lib") } func TestJavaSdkLibrary_ApiLibrary(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("foo"), android.FixtureModifyConfig(func(config android.Config) { config.SetApiLibraries([]string{"foo"}) }), ).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java", "b.java"], api_packages: ["foo"], system: { enabled: true, }, module_lib: { enabled: true, }, test: { enabled: true, }, } `) testCases := []struct { scope *apiScope apiContributions []string fullApiSurfaceStub string }{ { scope: apiScopePublic, apiContributions: []string{"foo.stubs.source.api.contribution"}, fullApiSurfaceStub: "android_stubs_current", }, { scope: apiScopeSystem, apiContributions: []string{"foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"}, fullApiSurfaceStub: "android_system_stubs_current", }, { scope: apiScopeTest, apiContributions: []string{"foo.stubs.source.test.api.contribution", "foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"}, fullApiSurfaceStub: "android_test_stubs_current", }, { scope: apiScopeModuleLib, apiContributions: []string{"foo.stubs.source.module_lib.api.contribution", "foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"}, fullApiSurfaceStub: "android_module_lib_stubs_current_full.from-text", }, } for _, c := range testCases { m := result.ModuleForTests(c.scope.apiLibraryModuleName("foo"), "android_common").Module().(*ApiLibrary) android.AssertArrayString(t, "Module expected to contain api contributions", c.apiContributions, m.properties.Api_contributions) android.AssertStringEquals(t, "Module expected to contain full api surface api library", c.fullApiSurfaceStub, *m.properties.Full_api_surface_stub) } } func TestStaticDepStubLibrariesVisibility(t *testing.T) { android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("foo"), android.FixtureMergeMockFs( map[string][]byte{ "A.java": nil, "dir/Android.bp": []byte( ` java_library { name: "bar", srcs: ["A.java"], libs: ["foo.stubs.from-source"], } `), "dir/A.java": nil, }, ).ExtendWithErrorHandler( android.FixtureExpectsAtLeastOneErrorMatchingPattern( `module "bar" variant "android_common": depends on //.:foo.stubs.from-source which is not visible to this module`)), ).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["A.java"], } `) } func TestSdkLibraryDependency(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithPrebuiltApis(map[string][]string{ "30": {"bar", "foo"}, }), ).RunTestWithBp(t, ` java_sdk_library { name: "foo", srcs: ["a.java", "b.java"], api_packages: ["foo"], } java_sdk_library { name: "bar", srcs: ["c.java", "b.java"], libs: [ "foo", ], uses_libs: [ "foo", ], } `) barPermissions := result.ModuleForTests("bar.xml", "android_common").Output("bar.xml") barContents := android.ContentFromFileRuleForTests(t, result.TestContext, barPermissions) android.AssertStringDoesContain(t, "bar.xml java_sdk_xml command", barContents, `dependency="foo"`) } func TestSdkLibraryExportableStubsLibrary(t *testing.T) { result := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("foo"), android.FixtureModifyConfig(func(config android.Config) { config.SetApiLibraries([]string{"foo"}) }), ).RunTestWithBp(t, ` aconfig_declarations { name: "bar", package: "com.example.package", container: "com.android.foo", srcs: [ "bar.aconfig", ], } java_sdk_library { name: "foo", srcs: ["a.java", "b.java"], api_packages: ["foo"], system: { enabled: true, }, module_lib: { enabled: true, }, test: { enabled: true, }, aconfig_declarations: [ "bar", ], } `) exportableStubsLibraryModuleName := apiScopePublic.exportableStubsLibraryModuleName("foo") exportableSourceStubsLibraryModuleName := apiScopePublic.exportableSourceStubsLibraryModuleName("foo") // Check modules generation topLevelModule := result.ModuleForTests(exportableStubsLibraryModuleName, "android_common") result.ModuleForTests(exportableSourceStubsLibraryModuleName, "android_common") // Check static lib dependency android.AssertBoolEquals(t, "exportable top level stubs library module depends on the"+ "exportable source stubs library module", true, CheckModuleHasDependency(t, result.TestContext, exportableStubsLibraryModuleName, "android_common", exportableSourceStubsLibraryModuleName), ) android.AssertArrayString(t, "exportable source stub library is a static lib of the"+ "top level exportable stubs library", []string{exportableSourceStubsLibraryModuleName}, topLevelModule.Module().(*Library).properties.Static_libs) } // For java libraries depending on java_sdk_library(_import) via libs, assert that // rdep gets stubs of source if source is listed in apex_contributions and prebuilt has prefer (legacy mechanism) func TestStubResolutionOfJavaSdkLibraryInLibs(t *testing.T) { bp := ` apex_contributions { name: "my_mainline_module_contributions", api_domain: "my_mainline_module", contents: ["sdklib"], // source is selected using apex_contributions, but prebuilt is selected using prefer } java_sdk_library { name: "sdklib", srcs: ["a.java"], sdk_version: "none", system_modules: "none", public: { enabled: true, }, } java_sdk_library_import { name: "sdklib", public: { jars: ["a.jar"], stub_srcs: ["a.java"], current_api: "current.txt", removed_api: "removed.txt", annotations: "annotations.zip", }, prefer: true, // Set prefer explicitly on the prebuilt. We will assert that rdep gets source in a test case. } // rdeps java_library { name: "mymodule", srcs: ["a.java"], sdk_version: "current", libs: ["sdklib",], // this should be dynamically resolved to sdklib.stubs (source) or prebuilt_sdklib.stubs (prebuilt) } ` fixture := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("sdklib"), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.BuildFlags = map[string]string{ // We can use any of the apex contribution build flags from build/soong/android/config.go#mainlineApexContributionBuildFlags here "RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "my_mainline_module_contributions", } }), ) result := fixture.RunTestWithBp(t, bp) // Make sure that rdeps get the correct source vs prebuilt based on mainline_module_contributions public := result.ModuleForTests("mymodule", "android_common") rule := public.Output("javac/mymodule.jar") inputs := rule.Implicits.Strings() android.AssertStringListContains(t, "Could not find the expected stub on classpath", inputs, "out/soong/.intermediates/sdklib.stubs/android_common/turbine-combined/sdklib.stubs.jar") } // test that rdep gets resolved to the correct version of a java_sdk_library (source or a specific prebuilt) func TestMultipleSdkLibraryPrebuilts(t *testing.T) { bp := ` apex_contributions { name: "my_mainline_module_contributions", api_domain: "my_mainline_module", contents: ["%s"], } java_sdk_library { name: "sdklib", srcs: ["a.java"], sdk_version: "none", system_modules: "none", public: { enabled: true, }, } java_sdk_library_import { name: "sdklib.v1", //prebuilt source_module_name: "sdklib", public: { jars: ["a.jar"], stub_srcs: ["a.java"], current_api: "current.txt", removed_api: "removed.txt", annotations: "annotations.zip", }, } java_sdk_library_import { name: "sdklib.v2", //prebuilt source_module_name: "sdklib", public: { jars: ["a.jar"], stub_srcs: ["a.java"], current_api: "current.txt", removed_api: "removed.txt", annotations: "annotations.zip", }, } // rdeps java_library { name: "mymodule", srcs: ["a.java"], libs: ["sdklib.stubs",], } ` testCases := []struct { desc string selectedDependencyName string expectedStubPath string }{ { desc: "Source library is selected using apex_contributions", selectedDependencyName: "sdklib", expectedStubPath: "out/soong/.intermediates/sdklib.stubs/android_common/turbine-combined/sdklib.stubs.jar", }, { desc: "Prebuilt library v1 is selected using apex_contributions", selectedDependencyName: "prebuilt_sdklib.v1", expectedStubPath: "out/soong/.intermediates/prebuilt_sdklib.v1.stubs/android_common/combined/sdklib.stubs.jar", }, { desc: "Prebuilt library v2 is selected using apex_contributions", selectedDependencyName: "prebuilt_sdklib.v2", expectedStubPath: "out/soong/.intermediates/prebuilt_sdklib.v2.stubs/android_common/combined/sdklib.stubs.jar", }, } fixture := android.GroupFixturePreparers( prepareForJavaTest, PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("sdklib", "sdklib.v1", "sdklib.v2"), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { variables.BuildFlags = map[string]string{ "RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "my_mainline_module_contributions", } }), ) for _, tc := range testCases { result := fixture.RunTestWithBp(t, fmt.Sprintf(bp, tc.selectedDependencyName)) // Make sure that rdeps get the correct source vs prebuilt based on mainline_module_contributions public := result.ModuleForTests("mymodule", "android_common") rule := public.Output("javac/mymodule.jar") inputs := rule.Implicits.Strings() android.AssertStringListContains(t, "Could not find the expected stub on classpath", inputs, tc.expectedStubPath) } }