Output apkcerts file for android_app_set.

Soong and Make have no ways to figure out what splits will be outputted
from a given android_app_set, so it's impossible for them to provide
full PACKAGES.$(LOCAL_MODULE).CERTIFICATE entries, which are required to
build a final apkcerts.txt. This change makes extract_apks produce
apkcerts.txt files for each input modules instead. The Make-side
counterpart of this change merges all local apkcerts.txt into a final
one.

Fixes: 160119159
Test: main_test.go
Test: m apkcerts-list
Change-Id: I321e80fd636a955213761f56a3ac64bfe7f7f7c0
diff --git a/cmd/extract_apks/main.go b/cmd/extract_apks/main.go
index e9a850e..db54ffb 100644
--- a/cmd/extract_apks/main.go
+++ b/cmd/extract_apks/main.go
@@ -24,6 +24,7 @@
 	"math"
 	"os"
 	"regexp"
+	"sort"
 	"strings"
 
 	"github.com/golang/protobuf/proto"
@@ -355,7 +356,7 @@
 
 // Writes out selected entries, renaming them as needed
 func (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig,
-	writer Zip2ZipWriter) error {
+	writer Zip2ZipWriter, partition string) ([]string, error) {
 	// Renaming rules:
 	//  splits/MODULE-master.apk to STEM.apk
 	// else
@@ -389,10 +390,11 @@
 	}
 
 	entryOrigin := make(map[string]string) // output entry to input entry
+	var apkcerts []string
 	for _, apk := range selected.entries {
 		apkFile, ok := apkSet.entries[apk]
 		if !ok {
-			return fmt.Errorf("TOC refers to an entry %s which does not exist", apk)
+			return nil, fmt.Errorf("TOC refers to an entry %s which does not exist", apk)
 		}
 		inName := apkFile.Name
 		outName, ok := renamer(inName)
@@ -405,10 +407,15 @@
 		}
 		entryOrigin[outName] = inName
 		if err := writer.CopyFrom(apkFile, outName); err != nil {
-			return err
+			return nil, err
+		}
+		if partition != "" {
+			apkcerts = append(apkcerts, fmt.Sprintf(
+				`name="%s" certificate="PRESIGNED" private_key="" partition="%s"`, outName, partition))
 		}
 	}
-	return nil
+	sort.Strings(apkcerts)
+	return apkcerts, nil
 }
 
 func (apkSet *ApkSet) extractAndCopySingle(selected SelectionResult, outFile *os.File) error {
@@ -433,6 +440,9 @@
 	}
 	extractSingle = flag.Bool("extract-single", false,
 		"extract a single target and output it uncompressed. only available for standalone apks and apexes.")
+	apkcertsOutput = flag.String("apkcerts", "",
+		"optional apkcerts.txt output file containing signing info of all outputted apks")
+	partition = flag.String("partition", "", "partition string. required when -apkcerts is used.")
 )
 
 // Parse abi values
@@ -485,7 +495,8 @@
 func processArgs() {
 	flag.Usage = func() {
 		fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> -sdk-version value -abis value `+
-			`-screen-densities value {-stem value | -extract-single} [-allow-prereleased] <APK set>`)
+			`-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+
+			`[-apkcerts <apkcerts output file> -partition <partition>] <APK set>`)
 		flag.PrintDefaults()
 		os.Exit(2)
 	}
@@ -498,7 +509,8 @@
 		"allow prereleased")
 	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) {
+	if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 ||
+		(targetConfig.stem == "" && !*extractSingle) || (*apkcertsOutput != "" && *partition == "") {
 		flag.Usage()
 	}
 	targetConfig.sdkVersion = int32(*version)
@@ -536,7 +548,20 @@
 				log.Fatal(err)
 			}
 		}()
-		err = apkSet.writeApks(sel, targetConfig, writer)
+		apkcerts, err := apkSet.writeApks(sel, targetConfig, writer, *partition)
+		if err == nil && *apkcertsOutput != "" {
+			apkcertsFile, err := os.Create(*apkcertsOutput)
+			if err != nil {
+				log.Fatal(err)
+			}
+			defer apkcertsFile.Close()
+			for _, a := range apkcerts {
+				_, err = apkcertsFile.WriteString(a + "\n")
+				if err != nil {
+					log.Fatal(err)
+				}
+			}
+		}
 	}
 	if err != nil {
 		log.Fatal(err)
diff --git a/cmd/extract_apks/main_test.go b/cmd/extract_apks/main_test.go
index bdd4bec..c3e6a2d 100644
--- a/cmd/extract_apks/main_test.go
+++ b/cmd/extract_apks/main_test.go
@@ -16,10 +16,11 @@
 
 import (
 	"fmt"
-	"github.com/golang/protobuf/proto"
 	"reflect"
 	"testing"
 
+	"github.com/golang/protobuf/proto"
+
 	bp "android/soong/cmd/extract_apks/bundle_proto"
 	"android/soong/third_party/zip"
 )
@@ -430,48 +431,63 @@
 	return nil
 }
 
-type testCaseWriteZip struct {
+type testCaseWriteApks struct {
 	name       string
 	moduleName string
 	stem       string
+	partition  string
 	// what we write from what
-	expected map[string]string
+	expectedZipEntries map[string]string
+	expectedApkcerts   []string
 }
 
-func TestWriteZip(t *testing.T) {
-	testCases := []testCaseWriteZip{
+func TestWriteApks(t *testing.T) {
+	testCases := []testCaseWriteApks{
 		{
 			name:       "splits",
 			moduleName: "mybase",
 			stem:       "Foo",
-			expected: map[string]string{
+			partition:  "system",
+			expectedZipEntries: map[string]string{
 				"Foo.apk":       "splits/mybase-master.apk",
 				"Foo-xhdpi.apk": "splits/mybase-xhdpi.apk",
 			},
+			expectedApkcerts: []string{
+				`name="Foo-xhdpi.apk" certificate="PRESIGNED" private_key="" partition="system"`,
+				`name="Foo.apk" certificate="PRESIGNED" private_key="" partition="system"`,
+			},
 		},
 		{
 			name:       "universal",
 			moduleName: "base",
 			stem:       "Bar",
-			expected: map[string]string{
+			partition:  "product",
+			expectedZipEntries: map[string]string{
 				"Bar.apk": "universal.apk",
 			},
+			expectedApkcerts: []string{
+				`name="Bar.apk" certificate="PRESIGNED" private_key="" partition="product"`,
+			},
 		},
 	}
 	for _, testCase := range testCases {
 		apkSet := ApkSet{entries: make(map[string]*zip.File)}
 		sel := SelectionResult{moduleName: testCase.moduleName}
-		for _, in := range testCase.expected {
+		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}
-		if err := apkSet.writeApks(sel, config, writer); err != nil {
+		apkcerts, err := apkSet.writeApks(sel, config, writer, testCase.partition)
+		if err != nil {
 			t.Error(err)
 		}
-		if !reflect.DeepEqual(testCase.expected, writer.entries) {
-			t.Errorf("expected %v, got %v", testCase.expected, writer.entries)
+		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)
 		}
 	}
 }