diff --git a/android/deapexer.go b/android/deapexer.go index 265f5312b..6a93f6087 100644 --- a/android/deapexer.go +++ b/android/deapexer.go @@ -15,6 +15,8 @@ package android import ( + "strings" + "github.com/google/blueprint" ) @@ -148,12 +150,19 @@ type RequiresFilesFromPrebuiltApexTag interface { func FindDeapexerProviderForModule(ctx ModuleContext) *DeapexerInfo { var di *DeapexerInfo ctx.VisitDirectDepsWithTag(DeapexerTag, func(m Module) { - p := ctx.OtherModuleProvider(m, DeapexerProvider).(DeapexerInfo) + c := ctx.OtherModuleProvider(m, DeapexerProvider).(DeapexerInfo) + p := &c if di != nil { + // If two DeapexerInfo providers have been found then check if they are + // equivalent. If they are then use the selected one, otherwise fail. + if selected := equivalentDeapexerInfoProviders(di, p); selected != nil { + di = selected + return + } ctx.ModuleErrorf("Multiple installable prebuilt APEXes provide ambiguous deapexers: %s and %s", di.ApexModuleName(), p.ApexModuleName()) } - di = &p + di = p }) if di != nil { return di @@ -162,3 +171,33 @@ func FindDeapexerProviderForModule(ctx ModuleContext) *DeapexerInfo { ctx.ModuleErrorf("No prebuilt APEX provides a deapexer module for APEX variant %s", ai.ApexVariationName) return nil } + +// removeCompressedApexSuffix removes the _compressed suffix from the name if present. +func removeCompressedApexSuffix(name string) string { + return strings.TrimSuffix(name, "_compressed") +} + +// equivalentDeapexerInfoProviders checks to make sure that the two DeapexerInfo structures are +// equivalent. +// +// At the moment and _compressed APEXes are treated as being equivalent. +// +// If they are not equivalent then this returns nil, otherwise, this returns the DeapexerInfo that +// should be used by the build, which is always the uncompressed one. That ensures that the behavior +// of the build is not dependent on which prebuilt APEX is visited first. +func equivalentDeapexerInfoProviders(p1 *DeapexerInfo, p2 *DeapexerInfo) *DeapexerInfo { + n1 := removeCompressedApexSuffix(p1.ApexModuleName()) + n2 := removeCompressedApexSuffix(p2.ApexModuleName()) + + // If the names don't match then they are not equivalent. + if n1 != n2 { + return nil + } + + // Select the uncompressed APEX. + if n1 == removeCompressedApexSuffix(n1) { + return p1 + } else { + return p2 + } +} diff --git a/apex/apex_test.go b/apex/apex_test.go index 7905710f2..dbe918010 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -7440,7 +7440,7 @@ func testDexpreoptWithApexes(t *testing.T, bp, errmsg string, preparer android.F return result.TestContext } -func TestDuplicateDeapexeresFromPrebuiltApexes(t *testing.T) { +func TestDuplicateDeapexersFromPrebuiltApexes(t *testing.T) { preparers := android.GroupFixturePreparers( java.PrepareForTestWithJavaDefaultModules, PrepareForTestWithApexBuildComponents, @@ -7509,6 +7509,107 @@ func TestDuplicateDeapexeresFromPrebuiltApexes(t *testing.T) { }) } +func TestDuplicateButEquivalentDeapexersFromPrebuiltApexes(t *testing.T) { + preparers := android.GroupFixturePreparers( + java.PrepareForTestWithJavaDefaultModules, + PrepareForTestWithApexBuildComponents, + ) + + bpBase := ` + apex_set { + name: "com.android.myapex", + installable: true, + exported_bootclasspath_fragments: ["my-bootclasspath-fragment"], + set: "myapex.apks", + } + + apex_set { + name: "com.android.myapex_compressed", + apex_name: "com.android.myapex", + installable: true, + exported_bootclasspath_fragments: ["my-bootclasspath-fragment"], + set: "myapex_compressed.apks", + } + + prebuilt_bootclasspath_fragment { + name: "my-bootclasspath-fragment", + apex_available: [ + "com.android.myapex", + "com.android.myapex_compressed", + ], + hidden_api: { + annotation_flags: "annotation-flags.csv", + metadata: "metadata.csv", + index: "index.csv", + signature_patterns: "signature_patterns.csv", + }, + %s + } + ` + + t.Run("java_import", func(t *testing.T) { + result := preparers.RunTestWithBp(t, + fmt.Sprintf(bpBase, `contents: ["libfoo"]`)+` + java_import { + name: "libfoo", + jars: ["libfoo.jar"], + apex_available: [ + "com.android.myapex", + "com.android.myapex_compressed", + ], + } + `) + + module := result.Module("libfoo", "android_common_com.android.myapex") + usesLibraryDep := module.(java.UsesLibraryDependency) + android.AssertPathRelativeToTopEquals(t, "dex jar path", + "out/soong/.intermediates/com.android.myapex.deapexer/android_common/deapexer/javalib/libfoo.jar", + usesLibraryDep.DexJarBuildPath().Path()) + }) + + t.Run("java_sdk_library_import", func(t *testing.T) { + result := preparers.RunTestWithBp(t, + fmt.Sprintf(bpBase, `contents: ["libfoo"]`)+` + java_sdk_library_import { + name: "libfoo", + public: { + jars: ["libbar.jar"], + }, + apex_available: [ + "com.android.myapex", + "com.android.myapex_compressed", + ], + compile_dex: true, + } + `) + + module := result.Module("libfoo", "android_common_com.android.myapex") + usesLibraryDep := module.(java.UsesLibraryDependency) + android.AssertPathRelativeToTopEquals(t, "dex jar path", + "out/soong/.intermediates/com.android.myapex.deapexer/android_common/deapexer/javalib/libfoo.jar", + usesLibraryDep.DexJarBuildPath().Path()) + }) + + t.Run("prebuilt_bootclasspath_fragment", func(t *testing.T) { + _ = preparers.RunTestWithBp(t, fmt.Sprintf(bpBase, ` + image_name: "art", + contents: ["libfoo"], + `)+` + java_sdk_library_import { + name: "libfoo", + public: { + jars: ["libbar.jar"], + }, + apex_available: [ + "com.android.myapex", + "com.android.myapex_compressed", + ], + compile_dex: true, + } + `) + }) +} + func TestUpdatable_should_set_min_sdk_version(t *testing.T) { testApexError(t, `"myapex" .*: updatable: updatable APEXes should set min_sdk_version`, ` apex {