From ffbcd1d8a086c581a14a42e54fa0019c637f56e6 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Fri, 12 Nov 2021 12:19:42 -0800 Subject: [PATCH] Extract primary apk from apk set zip Extract and install the primary apk normally, and then unzip the rest of them as a post install command. Bug: 204136549 Test: app_set_test.go Change-Id: I17437ff27f49df6bc91bdbbea6173b46c7d3ec4e --- apex/androidmk.go | 2 +- apex/builder.go | 3 +- cmd/extract_apks/main.go | 48 ++++++++++++++++++------- cmd/extract_apks/main_test.go | 68 +++++++++++++++++++++++------------ java/androidmk.go | 4 +-- java/app_set.go | 27 +++++++------- java/app_set_test.go | 22 +++++++++--- java/builder.go | 4 +-- 8 files changed, 121 insertions(+), 57 deletions(-) diff --git a/apex/androidmk.go b/apex/androidmk.go index a7a010754..204842992 100644 --- a/apex/androidmk.go +++ b/apex/androidmk.go @@ -263,7 +263,7 @@ func (a *apexBundle) androidMkForFiles(w io.Writer, apexBundleName, apexName, mo if !ok { panic(fmt.Sprintf("Expected %s to be AndroidAppSet", fi.module)) } - fmt.Fprintln(w, "LOCAL_APK_SET_INSTALL_FILE :=", as.InstallFile()) + fmt.Fprintln(w, "LOCAL_APK_SET_INSTALL_FILE :=", as.PackedAdditionalOutputs().String()) fmt.Fprintln(w, "LOCAL_APKCERTS_FILE :=", as.APKCertsFile().String()) fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_android_app_set.mk") case nativeSharedLib, nativeExecutable, nativeTest: diff --git a/apex/builder.go b/apex/builder.go index 2e21ddfa1..3599c5d01 100644 --- a/apex/builder.go +++ b/apex/builder.go @@ -442,7 +442,8 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { } else { if fi.class == appSet { copyCommands = append(copyCommands, - fmt.Sprintf("unzip -qDD -d %s %s", destPathDir, fi.builtFile.String())) + fmt.Sprintf("unzip -qDD -d %s %s", destPathDir, + fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs().String())) } else { copyCommands = append(copyCommands, "cp -f "+fi.builtFile.String()+" "+destPath) } diff --git a/cmd/extract_apks/main.go b/cmd/extract_apks/main.go index 6e51a28f4..1cf64de8b 100644 --- a/cmd/extract_apks/main.go +++ b/cmd/extract_apks/main.go @@ -356,7 +356,7 @@ type Zip2ZipWriter interface { // Writes out selected entries, renaming them as needed func (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig, - writer Zip2ZipWriter, partition string) ([]string, error) { + outFile io.Writer, zipWriter Zip2ZipWriter, partition string) ([]string, error) { // Renaming rules: // splits/MODULE-master.apk to STEM.apk // else @@ -406,8 +406,14 @@ func (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig, origin, inName, outName) } entryOrigin[outName] = inName - if err := writer.CopyFrom(apkFile, outName); err != nil { - return nil, err + if outName == config.stem+".apk" { + if err := writeZipEntryToFile(outFile, apkFile); err != nil { + return nil, err + } + } else { + if err := zipWriter.CopyFrom(apkFile, outName); err != nil { + return nil, err + } } if partition != "" { apkcerts = append(apkcerts, fmt.Sprintf( @@ -426,14 +432,13 @@ func (apkSet *ApkSet) extractAndCopySingle(selected SelectionResult, outFile *os if !ok { return fmt.Errorf("Couldn't find apk path %s", selected.entries[0]) } - inputReader, _ := apk.Open() - _, err := io.Copy(outFile, inputReader) - return err + return writeZipEntryToFile(outFile, apk) } // Arguments parsing var ( - outputFile = flag.String("o", "", "output file containing extracted entries") + outputFile = flag.String("o", "", "output file for primary entry") + zipFile = flag.String("zip", "", "output file containing additional extracted entries") targetConfig = TargetConfig{ screenDpi: map[android_bundle_proto.ScreenDensity_DensityAlias]bool{}, abis: map[android_bundle_proto.Abi_AbiAlias]int{}, @@ -494,7 +499,8 @@ func (s screenDensityFlagValue) Set(densityList string) error { func processArgs() { flag.Usage = func() { - fmt.Fprintln(os.Stderr, `usage: extract_apks -o -sdk-version value -abis value `+ + fmt.Fprintln(os.Stderr, `usage: extract_apks -o [-zip ] `+ + `-sdk-version value -abis value `+ `-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+ `[-apkcerts -partition ] `) flag.PrintDefaults() @@ -510,7 +516,8 @@ func processArgs() { flag.StringVar(&targetConfig.stem, "stem", "", "output entries base name in the output zip file") flag.Parse() if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 || - (targetConfig.stem == "" && !*extractSingle) || (*apkcertsOutput != "" && *partition == "") { + ((targetConfig.stem == "" || *zipFile == "") && !*extractSingle) || + (*apkcertsOutput != "" && *partition == "") { flag.Usage() } targetConfig.sdkVersion = int32(*version) @@ -542,13 +549,20 @@ func main() { if *extractSingle { err = apkSet.extractAndCopySingle(sel, outFile) } else { - writer := zip.NewWriter(outFile) + zipOutputFile, err := os.Create(*zipFile) + if err != nil { + log.Fatal(err) + } + defer zipOutputFile.Close() + + zipWriter := zip.NewWriter(zipOutputFile) defer func() { - if err := writer.Close(); err != nil { + if err := zipWriter.Close(); err != nil { log.Fatal(err) } }() - apkcerts, err := apkSet.writeApks(sel, targetConfig, writer, *partition) + + apkcerts, err := apkSet.writeApks(sel, targetConfig, outFile, zipWriter, *partition) if err == nil && *apkcertsOutput != "" { apkcertsFile, err := os.Create(*apkcertsOutput) if err != nil { @@ -567,3 +581,13 @@ func main() { log.Fatal(err) } } + +func writeZipEntryToFile(outFile io.Writer, zipEntry *zip.File) error { + reader, err := zipEntry.Open() + if err != nil { + return err + } + defer reader.Close() + _, err = io.Copy(outFile, reader) + return err +} diff --git a/cmd/extract_apks/main_test.go b/cmd/extract_apks/main_test.go index 9fcf324a2..f5e40466a 100644 --- a/cmd/extract_apks/main_test.go +++ b/cmd/extract_apks/main_test.go @@ -15,6 +15,7 @@ package main import ( + "bytes" "fmt" "reflect" "testing" @@ -437,8 +438,8 @@ type testCaseWriteApks struct { stem string partition string // what we write from what - expectedZipEntries map[string]string - expectedApkcerts []string + zipEntries map[string]string + expectedApkcerts []string } func TestWriteApks(t *testing.T) { @@ -448,7 +449,7 @@ func TestWriteApks(t *testing.T) { moduleName: "mybase", stem: "Foo", partition: "system", - expectedZipEntries: map[string]string{ + zipEntries: map[string]string{ "Foo.apk": "splits/mybase-master.apk", "Foo-xhdpi.apk": "splits/mybase-xhdpi.apk", }, @@ -462,7 +463,7 @@ func TestWriteApks(t *testing.T) { moduleName: "base", stem: "Bar", partition: "product", - expectedZipEntries: map[string]string{ + zipEntries: map[string]string{ "Bar.apk": "universal.apk", }, expectedApkcerts: []string{ @@ -471,23 +472,46 @@ func TestWriteApks(t *testing.T) { }, } for _, testCase := range testCases { - apkSet := ApkSet{entries: make(map[string]*zip.File)} - sel := SelectionResult{moduleName: testCase.moduleName} - for _, in := range testCase.expectedZipEntries { - apkSet.entries[in] = &zip.File{FileHeader: zip.FileHeader{Name: in}} - sel.entries = append(sel.entries, in) - } - writer := testZip2ZipWriter{make(map[string]string)} - config := TargetConfig{stem: testCase.stem} - apkcerts, err := apkSet.writeApks(sel, config, writer, testCase.partition) - if err != nil { - t.Error(err) - } - if !reflect.DeepEqual(testCase.expectedZipEntries, writer.entries) { - t.Errorf("expected zip entries %v, got %v", testCase.expectedZipEntries, writer.entries) - } - if !reflect.DeepEqual(testCase.expectedApkcerts, apkcerts) { - t.Errorf("expected apkcerts %v, got %v", testCase.expectedApkcerts, apkcerts) - } + t.Run(testCase.name, func(t *testing.T) { + testZipBuf := &bytes.Buffer{} + testZip := zip.NewWriter(testZipBuf) + for _, in := range testCase.zipEntries { + f, _ := testZip.Create(in) + f.Write([]byte(in)) + } + testZip.Close() + + zipReader, _ := zip.NewReader(bytes.NewReader(testZipBuf.Bytes()), int64(testZipBuf.Len())) + + apkSet := ApkSet{entries: make(map[string]*zip.File)} + sel := SelectionResult{moduleName: testCase.moduleName} + for _, f := range zipReader.File { + apkSet.entries[f.Name] = f + sel.entries = append(sel.entries, f.Name) + } + + zipWriter := testZip2ZipWriter{make(map[string]string)} + outWriter := &bytes.Buffer{} + config := TargetConfig{stem: testCase.stem} + apkcerts, err := apkSet.writeApks(sel, config, outWriter, zipWriter, testCase.partition) + if err != nil { + t.Error(err) + } + expectedZipEntries := make(map[string]string) + for k, v := range testCase.zipEntries { + if k != testCase.stem+".apk" { + expectedZipEntries[k] = v + } + } + if !reflect.DeepEqual(expectedZipEntries, zipWriter.entries) { + t.Errorf("expected zip entries %v, got %v", testCase.zipEntries, zipWriter.entries) + } + if !reflect.DeepEqual(testCase.expectedApkcerts, apkcerts) { + t.Errorf("expected apkcerts %v, got %v", testCase.expectedApkcerts, apkcerts) + } + if g, w := outWriter.String(), testCase.zipEntries[testCase.stem+".apk"]; !reflect.DeepEqual(g, w) { + t.Errorf("expected output file contents %q, got %q", testCase.stem+".apk", outWriter.String()) + } + }) } } diff --git a/java/androidmk.go b/java/androidmk.go index 4c115d58c..272a4fd36 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -696,12 +696,12 @@ func (apkSet *AndroidAppSet) AndroidMkEntries() []android.AndroidMkEntries { return []android.AndroidMkEntries{ android.AndroidMkEntries{ Class: "APPS", - OutputFile: android.OptionalPathForPath(apkSet.packedOutput), + OutputFile: android.OptionalPathForPath(apkSet.primaryOutput), Include: "$(BUILD_SYSTEM)/soong_android_app_set.mk", ExtraEntries: []android.AndroidMkExtraEntriesFunc{ func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", apkSet.Privileged()) - entries.SetString("LOCAL_APK_SET_INSTALL_FILE", apkSet.InstallFile()) + entries.SetPath("LOCAL_APK_SET_INSTALL_FILE", apkSet.PackedAdditionalOutputs()) entries.SetPath("LOCAL_APKCERTS_FILE", apkSet.apkcertsFile) entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", apkSet.properties.Overrides...) }, diff --git a/java/app_set.go b/java/app_set.go index 6b25638da..2e9a4abdc 100644 --- a/java/app_set.go +++ b/java/app_set.go @@ -55,10 +55,10 @@ type AndroidAppSet struct { android.DefaultableModuleBase prebuilt android.Prebuilt - properties AndroidAppSetProperties - packedOutput android.WritablePath - installFile string - apkcertsFile android.ModuleOutPath + properties AndroidAppSetProperties + packedOutput android.WritablePath + primaryOutput android.WritablePath + apkcertsFile android.ModuleOutPath } func (as *AndroidAppSet) Name() string { @@ -78,11 +78,11 @@ func (as *AndroidAppSet) Privileged() bool { } func (as *AndroidAppSet) OutputFile() android.Path { - return as.packedOutput + return as.primaryOutput } -func (as *AndroidAppSet) InstallFile() string { - return as.installFile +func (as *AndroidAppSet) PackedAdditionalOutputs() android.Path { + return as.packedOutput } func (as *AndroidAppSet) APKCertsFile() android.Path { @@ -114,11 +114,11 @@ func SupportedAbis(ctx android.ModuleContext) []string { func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) { as.packedOutput = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip") + as.primaryOutput = android.PathForModuleOut(ctx, as.BaseModuleName()+".apk") as.apkcertsFile = android.PathForModuleOut(ctx, "apkcerts.txt") // We are assuming here that the install file in the APK // set has `.apk` suffix. If it doesn't the build will fail. // APK sets containing APEX files are handled elsewhere. - as.installFile = as.BaseModuleName() + ".apk" screenDensities := "all" if dpis := ctx.Config().ProductAAPTPrebuiltDPI(); len(dpis) > 0 { screenDensities = strings.ToUpper(strings.Join(dpis, ",")) @@ -127,11 +127,11 @@ func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) // TODO(asmundak): do we support device features ctx.Build(pctx, android.BuildParams{ - Rule: extractMatchingApks, - Description: "Extract APKs from APK set", - Output: as.packedOutput, - ImplicitOutput: as.apkcertsFile, - Inputs: android.Paths{as.prebuilt.SingleSourcePath(ctx)}, + Rule: extractMatchingApks, + Description: "Extract APKs from APK set", + Output: as.primaryOutput, + ImplicitOutputs: android.WritablePaths{as.packedOutput, as.apkcertsFile}, + Inputs: android.Paths{as.prebuilt.SingleSourcePath(ctx)}, Args: map[string]string{ "abis": strings.Join(SupportedAbis(ctx), ","), "allow-prereleased": strconv.FormatBool(proptools.Bool(as.properties.Prerelease)), @@ -140,6 +140,7 @@ func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) "stem": as.BaseModuleName(), "apkcerts": as.apkcertsFile.String(), "partition": as.PartitionTag(ctx.DeviceConfig()), + "zip": as.packedOutput.String(), }, }) } diff --git a/java/app_set_test.go b/java/app_set_test.go index adaf71bab..03eb66786 100644 --- a/java/app_set_test.go +++ b/java/app_set_test.go @@ -17,19 +17,20 @@ package java import ( "fmt" "reflect" + "strings" "testing" "android/soong/android" ) func TestAndroidAppSet(t *testing.T) { - ctx, _ := testJava(t, ` + result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, ` android_app_set { name: "foo", set: "prebuilts/apks/app.apks", prerelease: true, }`) - module := ctx.ModuleForTests("foo", "android_common") + module := result.ModuleForTests("foo", "android_common") const packedSplitApks = "foo.zip" params := module.Output(packedSplitApks) if params.Rule == nil { @@ -41,9 +42,22 @@ func TestAndroidAppSet(t *testing.T) { if s := params.Args["partition"]; s != "system" { t.Errorf("wrong partition value: '%s', expected 'system'", s) } - mkEntries := android.AndroidMkEntriesForTest(t, ctx, module.Module())[0] + + android.AssertPathRelativeToTopEquals(t, "incorrect output path", + "out/soong/.intermediates/foo/android_common/foo.apk", params.Output) + + android.AssertPathsRelativeToTopEquals(t, "incorrect implicit output paths", + []string{ + "out/soong/.intermediates/foo/android_common/foo.zip", + "out/soong/.intermediates/foo/android_common/apkcerts.txt", + }, + params.ImplicitOutputs.Paths()) + + mkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, module.Module())[0] actualInstallFile := mkEntries.EntryMap["LOCAL_APK_SET_INSTALL_FILE"] - expectedInstallFile := []string{"foo.apk"} + expectedInstallFile := []string{ + strings.Replace(params.ImplicitOutputs[0].String(), android.OutSoongDir, result.Config.SoongOutDir(), 1), + } if !reflect.DeepEqual(actualInstallFile, expectedInstallFile) { t.Errorf("Unexpected LOCAL_APK_SET_INSTALL_FILE value: '%s', expected: '%s',", actualInstallFile, expectedInstallFile) diff --git a/java/builder.go b/java/builder.go index ae124a3e7..e64a61f5c 100644 --- a/java/builder.go +++ b/java/builder.go @@ -120,14 +120,14 @@ var ( "extractMatchingApks", blueprint.RuleParams{ Command: `rm -rf "$out" && ` + - `${config.ExtractApksCmd} -o "${out}" -allow-prereleased=${allow-prereleased} ` + + `${config.ExtractApksCmd} -o "${out}" -zip "${zip}" -allow-prereleased=${allow-prereleased} ` + `-sdk-version=${sdk-version} -abis=${abis} ` + `--screen-densities=${screen-densities} --stem=${stem} ` + `-apkcerts=${apkcerts} -partition=${partition} ` + `${in}`, CommandDeps: []string{"${config.ExtractApksCmd}"}, }, - "abis", "allow-prereleased", "screen-densities", "sdk-version", "stem", "apkcerts", "partition") + "abis", "allow-prereleased", "screen-densities", "sdk-version", "stem", "apkcerts", "partition", "zip") turbine, turbineRE = pctx.RemoteStaticRules("turbine", blueprint.RuleParams{