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
This commit is contained in:
Colin Cross 2021-11-12 12:19:42 -08:00
parent 6cfb37af56
commit ffbcd1d8a0
8 changed files with 121 additions and 57 deletions

View file

@ -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:

View file

@ -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)
}

View file

@ -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 <output-file> -sdk-version value -abis value `+
fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> [-zip <output-zip-file>] `+
`-sdk-version value -abis value `+
`-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+
`[-apkcerts <apkcerts output file> -partition <partition>] <APK set>`)
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
}

View file

@ -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())
}
})
}
}

View file

@ -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...)
},

View file

@ -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(),
},
})
}

View file

@ -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)

View file

@ -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{