diff --git a/android/util.go b/android/util.go index 81f481d5f..e985fc1f5 100644 --- a/android/util.go +++ b/android/util.go @@ -121,8 +121,19 @@ func InList(s string, list []string) bool { return IndexList(s, list) != -1 } -func PrefixInList(s string, list []string) bool { - for _, prefix := range list { +// Returns true if the given string s is prefixed with any string in the given prefix list. +func PrefixInList(s string, prefixList []string) bool { + for _, prefix := range prefixList { + if strings.HasPrefix(s, prefix) { + return true + } + } + return false +} + +// Returns true if any string in the given list has the given prefix. +func PrefixedStringInList(list []string, prefix string) bool { + for _, s := range list { if strings.HasPrefix(s, prefix) { return true } diff --git a/java/aapt2.go b/java/aapt2.go index cfe0deab1..04e4de52c 100644 --- a/java/aapt2.go +++ b/java/aapt2.go @@ -147,10 +147,16 @@ var fileListToFileRule = pctx.AndroidStaticRule("fileListToFile", RspfileContent: "$in", }) +var mergeAssetsRule = pctx.AndroidStaticRule("mergeAssets", + blueprint.RuleParams{ + Command: `${config.MergeZipsCmd} ${out} ${in}`, + CommandDeps: []string{"${config.MergeZipsCmd}"}, + }) + func aapt2Link(ctx android.ModuleContext, packageRes, genJar, proguardOptions, rTxt, extraPackages android.WritablePath, flags []string, deps android.Paths, - compiledRes, compiledOverlay android.Paths, splitPackages android.WritablePaths) { + compiledRes, compiledOverlay, assetPackages android.Paths, splitPackages android.WritablePaths) { genDir := android.PathForModuleGen(ctx, "aapt2", "R") @@ -186,12 +192,25 @@ func aapt2Link(ctx android.ModuleContext, } implicitOutputs := append(splitPackages, proguardOptions, genJar, rTxt, extraPackages) + linkOutput := packageRes + + // AAPT2 ignores assets in overlays. Merge them after linking. + if len(assetPackages) > 0 { + linkOutput = android.PathForModuleOut(ctx, "aapt2", "package-res.apk") + inputZips := append(android.Paths{linkOutput}, assetPackages...) + ctx.Build(pctx, android.BuildParams{ + Rule: mergeAssetsRule, + Inputs: inputZips, + Output: packageRes, + Description: "merge assets from dependencies", + }) + } ctx.Build(pctx, android.BuildParams{ Rule: aapt2LinkRule, Description: "aapt2 link", Implicits: deps, - Output: packageRes, + Output: linkOutput, ImplicitOutputs: implicitOutputs, Args: map[string]string{ "flags": strings.Join(flags, " "), diff --git a/java/aar.go b/java/aar.go index d707e36d7..24c5e7d04 100644 --- a/java/aar.go +++ b/java/aar.go @@ -32,6 +32,7 @@ type AndroidLibraryDependency interface { ExportedRRODirs() []rroDir ExportedStaticPackages() android.Paths ExportedManifests() android.Paths + ExportedAssets() android.OptionalPath } func init() { @@ -93,6 +94,7 @@ type aapt struct { extraAaptPackagesFile android.Path mergedManifestFile android.Path noticeFile android.OptionalPath + assetPackage android.OptionalPath isLibrary bool useEmbeddedNativeLibs bool useEmbeddedDex bool @@ -124,6 +126,10 @@ func (a *aapt) ExportedManifests() android.Paths { return a.transitiveManifestPaths } +func (a *aapt) ExportedAssets() android.OptionalPath { + return a.assetPackage +} + func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext, manifestPath android.Path) (compileFlags, linkFlags []string, linkDeps android.Paths, resDirs, overlayDirs []globbedResourceDir, rroDirs []rroDir, resZips android.Paths) { @@ -219,9 +225,15 @@ func (a *aapt) deps(ctx android.BottomUpMutatorContext, sdkDep sdkDep) { } } +var extractAssetsRule = pctx.AndroidStaticRule("extractAssets", + blueprint.RuleParams{ + Command: `${config.Zip2ZipCmd} -i ${in} -o ${out} "assets/**/*"`, + CommandDeps: []string{"${config.Zip2ZipCmd}"}, + }) + func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, extraLinkFlags ...string) { - transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, libDeps, libFlags, sdkLibraries := + transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags, sdkLibraries := aaptLibs(ctx, sdkContext) // App manifest file @@ -321,7 +333,20 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex } aapt2Link(ctx, packageRes, srcJar, proguardOptionsFile, rTxt, extraPackages, - linkFlags, linkDeps, compiledRes, compiledOverlay, splitPackages) + linkFlags, linkDeps, compiledRes, compiledOverlay, assetPackages, splitPackages) + + // Extract assets from the resource package output so that they can be used later in aapt2link + // for modules that depend on this one. + if android.PrefixedStringInList(linkFlags, "-A ") || len(assetPackages) > 0 { + assets := android.PathForModuleOut(ctx, "assets.zip") + ctx.Build(pctx, android.BuildParams{ + Rule: extractAssetsRule, + Input: packageRes, + Output: assets, + Description: "extract assets from built resource file", + }) + a.assetPackage = android.OptionalPathForPath(assets) + } a.aaptSrcJar = srcJar a.exportPackage = packageRes @@ -335,7 +360,7 @@ func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext, ex // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStaticLibs, transitiveStaticLibManifests android.Paths, - staticRRODirs []rroDir, deps android.Paths, flags []string, sdkLibraries []string) { + staticRRODirs []rroDir, assets, deps android.Paths, flags []string, sdkLibraries []string) { var sharedLibs android.Paths @@ -373,6 +398,9 @@ func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStati transitiveStaticLibs = append(transitiveStaticLibs, exportPackage) transitiveStaticLibManifests = append(transitiveStaticLibManifests, aarDep.ExportedManifests()...) sdkLibraries = append(sdkLibraries, aarDep.ExportedSdkLibs()...) + if aarDep.ExportedAssets().Valid() { + assets = append(assets, aarDep.ExportedAssets().Path()) + } outer: for _, d := range aarDep.ExportedRRODirs() { @@ -402,7 +430,7 @@ func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext) (transitiveStati transitiveStaticLibManifests = android.FirstUniquePaths(transitiveStaticLibManifests) sdkLibraries = android.FirstUniqueStrings(sdkLibraries) - return transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, deps, flags, sdkLibraries + return transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assets, deps, flags, sdkLibraries } type AndroidLibrary struct { @@ -572,6 +600,11 @@ func (a *AARImport) ExportedManifests() android.Paths { return android.Paths{a.manifest} } +// TODO(jungjw): Decide whether we want to implement this. +func (a *AARImport) ExportedAssets() android.OptionalPath { + return android.OptionalPath{} +} + func (a *AARImport) Prebuilt() *android.Prebuilt { return &a.prebuilt } @@ -660,7 +693,7 @@ func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { linkFlags = append(linkFlags, "--manifest "+a.manifest.String()) linkDeps = append(linkDeps, a.manifest) - transitiveStaticLibs, staticLibManifests, staticRRODirs, libDeps, libFlags, sdkLibraries := + transitiveStaticLibs, staticLibManifests, staticRRODirs, transitiveAssets, libDeps, libFlags, sdkLibraries := aaptLibs(ctx, sdkContext(a)) _ = staticLibManifests @@ -673,7 +706,7 @@ func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { overlayRes := append(android.Paths{flata}, transitiveStaticLibs...) aapt2Link(ctx, a.exportPackage, srcJar, proguardOptionsFile, rTxt, a.extraAaptPackagesFile, - linkFlags, linkDeps, nil, overlayRes, nil) + linkFlags, linkDeps, nil, overlayRes, transitiveAssets, nil) } var _ Dependency = (*AARImport)(nil) diff --git a/java/app_test.go b/java/app_test.go index c20a8e7a9..c86b038af 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -323,6 +323,107 @@ func TestResourceDirs(t *testing.T) { } } +func TestLibraryAssets(t *testing.T) { + bp := ` + android_app { + name: "foo", + sdk_version: "current", + static_libs: ["lib1", "lib2", "lib3"], + } + + android_library { + name: "lib1", + sdk_version: "current", + asset_dirs: ["assets_a"], + } + + android_library { + name: "lib2", + sdk_version: "current", + } + + android_library { + name: "lib3", + sdk_version: "current", + static_libs: ["lib4"], + } + + android_library { + name: "lib4", + sdk_version: "current", + asset_dirs: ["assets_b"], + } + ` + + testCases := []struct { + name string + assetFlag string + assetPackages []string + }{ + { + name: "foo", + // lib1 has its own asset. lib3 doesn't have any, but provides lib4's transitively. + assetPackages: []string{ + buildDir + "/.intermediates/foo/android_common/aapt2/package-res.apk", + buildDir + "/.intermediates/lib1/android_common/assets.zip", + buildDir + "/.intermediates/lib3/android_common/assets.zip", + }, + }, + { + name: "lib1", + assetFlag: "-A assets_a", + }, + { + name: "lib2", + }, + { + name: "lib3", + assetPackages: []string{ + buildDir + "/.intermediates/lib3/android_common/aapt2/package-res.apk", + buildDir + "/.intermediates/lib4/android_common/assets.zip", + }, + }, + { + name: "lib4", + assetFlag: "-A assets_b", + }, + } + ctx := testApp(t, bp) + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + m := ctx.ModuleForTests(test.name, "android_common") + + // Check asset flag in aapt2 link flags + var aapt2link android.TestingBuildParams + if len(test.assetPackages) > 0 { + aapt2link = m.Output("aapt2/package-res.apk") + } else { + aapt2link = m.Output("package-res.apk") + } + aapt2Flags := aapt2link.Args["flags"] + if test.assetFlag != "" { + if !strings.Contains(aapt2Flags, test.assetFlag) { + t.Errorf("Can't find asset flag %q in aapt2 link flags %q", test.assetFlag, aapt2Flags) + } + } else { + if strings.Contains(aapt2Flags, " -A ") { + t.Errorf("aapt2 link flags %q contain unexpected asset flag", aapt2Flags) + } + } + + // Check asset merge rule. + if len(test.assetPackages) > 0 { + mergeAssets := m.Output("package-res.apk") + if !reflect.DeepEqual(test.assetPackages, mergeAssets.Inputs.Strings()) { + t.Errorf("Unexpected mergeAssets inputs: %v, expected: %v", + mergeAssets.Inputs.Strings(), test.assetPackages) + } + } + }) + } +} + func TestAndroidResources(t *testing.T) { testCases := []struct { name string diff --git a/java/testing.go b/java/testing.go index ecf0126e8..c409a46eb 100644 --- a/java/testing.go +++ b/java/testing.go @@ -49,6 +49,8 @@ func TestConfig(buildDir string, env map[string]string, bp string, fs map[string "api/test-current.txt": nil, "api/test-removed.txt": nil, "framework/aidl/a.aidl": nil, + "assets_a/a": nil, + "assets_b/b": nil, "prebuilts/ndk/current/sources/cxx-stl/llvm-libc++/libs/arm64-v8a/libc++_shared.so": nil,