Merge "Add zip-apex"
diff --git a/apex/apex.go b/apex/apex.go
index 52fa561..0445c20 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -55,6 +55,7 @@
 			`${apexer} --force --manifest ${manifest} ` +
 			`--file_contexts ${file_contexts} ` +
 			`--canned_fs_config ${canned_fs_config} ` +
+			`--payload_type image ` +
 			`--key ${key} ${image_dir} ${out} `,
 		CommandDeps: []string{"${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}",
 			"${mke2fs}", "${resize2fs}", "${sefcontext_compile}",
@@ -62,6 +63,17 @@
 		Description: "APEX ${image_dir} => ${out}",
 	}, "tool_path", "image_dir", "copy_commands", "manifest", "file_contexts", "canned_fs_config", "key")
 
+	zipApexRule = pctx.StaticRule("zipApexRule", blueprint.RuleParams{
+		Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` +
+			`(${copy_commands}) && ` +
+			`APEXER_TOOL_PATH=${tool_path} ` +
+			`${apexer} --force --manifest ${manifest} ` +
+			`--payload_type zip ` +
+			`${image_dir} ${out} `,
+		CommandDeps: []string{"${apexer}", "${merge_zips}", "${soong_zip}", "${zipalign}", "${aapt2}"},
+		Description: "ZipAPEX ${image_dir} => ${out}",
+	}, "tool_path", "image_dir", "copy_commands", "manifest")
+
 	apexProtoConvertRule = pctx.AndroidStaticRule("apexProtoConvertRule",
 		blueprint.RuleParams{
 			Command:     `${aapt2} convert --output-format proto $in -o $out`,
@@ -78,7 +90,11 @@
 	}, "abi")
 )
 
-var apexSuffix = ".apex"
+var imageApexSuffix = ".apex"
+var zipApexSuffix = ".zipapex"
+
+var imageApexType = "image"
+var zipApexType = "zip"
 
 type dependencyTag struct {
 	blueprint.BaseDependencyTag
@@ -199,6 +215,10 @@
 	// Name of the apex_key module that provides the private key to sign APEX
 	Key *string
 
+	// The type of APEX to build. Controls what the APEX payload is. Either
+	// 'image', 'zip' or 'both'. Default: 'image'.
+	Payload_type *string
+
 	// The name of a certificate in the default certificate directory, blank to use the default product certificate,
 	// or an android_app_certificate module name in the form ":module".
 	Certificate *string
@@ -246,6 +266,56 @@
 	javaSharedLib
 )
 
+type apexPackaging int
+
+const (
+	imageApex apexPackaging = iota
+	zipApex
+	both
+)
+
+func (a apexPackaging) image() bool {
+	switch a {
+	case imageApex, both:
+		return true
+	}
+	return false
+}
+
+func (a apexPackaging) zip() bool {
+	switch a {
+	case zipApex, both:
+		return true
+	}
+	return false
+}
+
+func (a apexPackaging) suffix() string {
+	switch a {
+	case imageApex:
+		return imageApexSuffix
+	case zipApex:
+		return zipApexSuffix
+	case both:
+		panic(fmt.Errorf("must be either zip or image"))
+	default:
+		panic(fmt.Errorf("unkonwn APEX type %d", a))
+	}
+}
+
+func (a apexPackaging) name() string {
+	switch a {
+	case imageApex:
+		return imageApexType
+	case zipApex:
+		return zipApexType
+	case both:
+		panic(fmt.Errorf("must be either zip or image"))
+	default:
+		panic(fmt.Errorf("unkonwn APEX type %d", a))
+	}
+}
+
 func (class apexFileClass) NameInMake() string {
 	switch class {
 	case etc:
@@ -275,8 +345,10 @@
 
 	properties apexBundleProperties
 
+	apexTypes apexPackaging
+
 	bundleModuleFile android.WritablePath
-	outputFile       android.WritablePath
+	outputFiles      map[apexPackaging]android.WritablePath
 	installDir       android.OutputPath
 
 	// list of files to be included in this apex
@@ -425,6 +497,17 @@
 	var keyFile android.Path
 	var certificate java.Certificate
 
+	if a.properties.Payload_type == nil || *a.properties.Payload_type == "image" {
+		a.apexTypes = imageApex
+	} else if *a.properties.Payload_type == "zip" {
+		a.apexTypes = zipApex
+	} else if *a.properties.Payload_type == "both" {
+		a.apexTypes = both
+	} else {
+		ctx.PropertyErrorf("type", "%q is not one of \"image\", \"zip\", or \"both\".", *a.properties.Payload_type)
+		return
+	}
+
 	ctx.WalkDeps(func(child, parent android.Module) bool {
 		if _, ok := parent.(*apexBundle); ok {
 			// direct dependencies
@@ -532,14 +615,20 @@
 	a.flattened = ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuild()
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	a.filesInfo = filesInfo
-	if ctx.Config().FlattenApex() {
-		a.buildFlattenedApex(ctx)
-	} else {
-		a.buildUnflattenedApex(ctx, keyFile, certificate)
+
+	if a.apexTypes.zip() {
+		a.buildUnflattenedApex(ctx, keyFile, certificate, zipApex)
+	}
+	if a.apexTypes.image() {
+		if ctx.Config().FlattenApex() {
+			a.buildFlattenedApex(ctx)
+		} else {
+			a.buildUnflattenedApex(ctx, keyFile, certificate, imageApex)
+		}
 	}
 }
 
-func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext, keyFile android.Path, certificate java.Certificate) {
+func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext, keyFile android.Path, certificate java.Certificate, apexType apexPackaging) {
 	cert := String(a.properties.Certificate)
 	if cert != "" && android.SrcIsModule(cert) == "" {
 		defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
@@ -552,45 +641,19 @@
 		certificate = java.Certificate{pem, key}
 	}
 
-	// files and dirs that will be created in apex
-	var readOnlyPaths []string
-	var executablePaths []string // this also includes dirs
-	for _, f := range a.filesInfo {
-		pathInApex := filepath.Join(f.installDir, f.builtFile.Base())
-		if f.installDir == "bin" {
-			executablePaths = append(executablePaths, pathInApex)
-		} else {
-			readOnlyPaths = append(readOnlyPaths, pathInApex)
-		}
-		if !android.InList(f.installDir, executablePaths) {
-			executablePaths = append(executablePaths, f.installDir)
-		}
-	}
-	sort.Strings(readOnlyPaths)
-	sort.Strings(executablePaths)
-	cannedFsConfig := android.PathForModuleOut(ctx, "canned_fs_config")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        generateFsConfig,
-		Output:      cannedFsConfig,
-		Description: "generate fs config",
-		Args: map[string]string{
-			"ro_paths":   strings.Join(readOnlyPaths, " "),
-			"exec_paths": strings.Join(executablePaths, " "),
-		},
-	})
-
 	manifest := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
 
-	fcName := proptools.StringDefault(a.properties.File_contexts, ctx.ModuleName())
-	fileContextsPath := "system/sepolicy/apex/" + fcName + "-file_contexts"
-	fileContextsOptionalPath := android.ExistentPathForSource(ctx, fileContextsPath)
-	if !fileContextsOptionalPath.Valid() {
-		ctx.ModuleErrorf("Cannot find file_contexts file: %q", fileContextsPath)
-		return
+	var abis []string
+	for _, target := range ctx.MultiTargets() {
+		if len(target.Arch.Abi) > 0 {
+			abis = append(abis, target.Arch.Abi[0])
+		}
 	}
-	fileContexts := fileContextsOptionalPath.Path()
 
-	unsignedOutputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+apexSuffix+".unsigned")
+	abis = android.FirstUniqueStrings(abis)
+
+	suffix := apexType.suffix()
+	unsignedOutputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+suffix+".unsigned")
 
 	filesToCopy := []android.Path{}
 	for _, f := range a.filesInfo {
@@ -600,68 +663,121 @@
 	copyCommands := []string{}
 	for i, src := range filesToCopy {
 		dest := filepath.Join(a.filesInfo[i].installDir, src.Base())
-		dest_path := filepath.Join(android.PathForModuleOut(ctx, "image").String(), dest)
+		dest_path := filepath.Join(android.PathForModuleOut(ctx, "image"+suffix).String(), dest)
 		copyCommands = append(copyCommands, "mkdir -p "+filepath.Dir(dest_path))
 		copyCommands = append(copyCommands, "cp "+src.String()+" "+dest_path)
 	}
 	implicitInputs := append(android.Paths(nil), filesToCopy...)
-	implicitInputs = append(implicitInputs, cannedFsConfig, manifest, fileContexts, keyFile)
+	implicitInputs = append(implicitInputs, manifest)
+
 	outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String()
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        apexRule,
-		Implicits:   implicitInputs,
-		Output:      unsignedOutputFile,
-		Description: "apex",
-		Args: map[string]string{
-			"tool_path":        outHostBinDir + ":" + prebuiltSdkToolsBinDir,
-			"image_dir":        android.PathForModuleOut(ctx, "image").String(),
-			"copy_commands":    strings.Join(copyCommands, " && "),
-			"manifest":         manifest.String(),
-			"file_contexts":    fileContexts.String(),
-			"canned_fs_config": cannedFsConfig.String(),
-			"key":              keyFile.String(),
-		},
-	})
 
-	var abis []string
-	for _, target := range ctx.MultiTargets() {
-		abis = append(abis, target.Arch.Abi[0])
+	if apexType.image() {
+		// files and dirs that will be created in APEX
+		var readOnlyPaths []string
+		var executablePaths []string // this also includes dirs
+		for _, f := range a.filesInfo {
+			pathInApex := filepath.Join(f.installDir, f.builtFile.Base())
+			if f.installDir == "bin" {
+				executablePaths = append(executablePaths, pathInApex)
+			} else {
+				readOnlyPaths = append(readOnlyPaths, pathInApex)
+			}
+			if !android.InList(f.installDir, executablePaths) {
+				executablePaths = append(executablePaths, f.installDir)
+			}
+		}
+		sort.Strings(readOnlyPaths)
+		sort.Strings(executablePaths)
+		cannedFsConfig := android.PathForModuleOut(ctx, "canned_fs_config")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        generateFsConfig,
+			Output:      cannedFsConfig,
+			Description: "generate fs config",
+			Args: map[string]string{
+				"ro_paths":   strings.Join(readOnlyPaths, " "),
+				"exec_paths": strings.Join(executablePaths, " "),
+			},
+		})
+
+		fcName := proptools.StringDefault(a.properties.File_contexts, ctx.ModuleName())
+		fileContextsPath := "system/sepolicy/apex/" + fcName + "-file_contexts"
+		fileContextsOptionalPath := android.ExistentPathForSource(ctx, fileContextsPath)
+		if !fileContextsOptionalPath.Valid() {
+			ctx.ModuleErrorf("Cannot find file_contexts file: %q", fileContextsPath)
+			return
+		}
+		fileContexts := fileContextsOptionalPath.Path()
+
+		// Additional implicit inputs.
+		implicitInputs = append(implicitInputs, cannedFsConfig, fileContexts, keyFile)
+
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        apexRule,
+			Implicits:   implicitInputs,
+			Output:      unsignedOutputFile,
+			Description: "apex (" + apexType.name() + ")",
+			Args: map[string]string{
+				"tool_path":        outHostBinDir + ":" + prebuiltSdkToolsBinDir,
+				"image_dir":        android.PathForModuleOut(ctx, "image"+suffix).String(),
+				"copy_commands":    strings.Join(copyCommands, " && "),
+				"manifest":         manifest.String(),
+				"file_contexts":    fileContexts.String(),
+				"canned_fs_config": cannedFsConfig.String(),
+				"key":              keyFile.String(),
+			},
+		})
+
+		apexProtoFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".pb"+suffix)
+		bundleModuleFile := android.PathForModuleOut(ctx, ctx.ModuleName()+suffix+"-base.zip")
+		a.bundleModuleFile = bundleModuleFile
+
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        apexProtoConvertRule,
+			Input:       unsignedOutputFile,
+			Output:      apexProtoFile,
+			Description: "apex proto convert",
+		})
+
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        apexBundleRule,
+			Input:       apexProtoFile,
+			Output:      a.bundleModuleFile,
+			Description: "apex bundle module",
+			Args: map[string]string{
+				"abi": strings.Join(abis, "."),
+			},
+		})
+	} else {
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        zipApexRule,
+			Implicits:   implicitInputs,
+			Output:      unsignedOutputFile,
+			Description: "apex (" + apexType.name() + ")",
+			Args: map[string]string{
+				"tool_path":     outHostBinDir + ":" + prebuiltSdkToolsBinDir,
+				"image_dir":     android.PathForModuleOut(ctx, "image"+suffix).String(),
+				"copy_commands": strings.Join(copyCommands, " && "),
+				"manifest":      manifest.String(),
+			},
+		})
 	}
-	abis = android.FirstUniqueStrings(abis)
 
-	apexProtoFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".pb"+apexSuffix)
-	bundleModuleFile := android.PathForModuleOut(ctx, ctx.ModuleName()+"-base.zip")
-	a.bundleModuleFile = bundleModuleFile
-
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        apexProtoConvertRule,
-		Input:       unsignedOutputFile,
-		Output:      apexProtoFile,
-		Description: "apex proto convert",
-	})
-
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        apexBundleRule,
-		Input:       apexProtoFile,
-		Output:      bundleModuleFile,
-		Description: "apex bundle module",
-		Args: map[string]string{
-			"abi": strings.Join(abis, "."),
-		},
-	})
-
-	a.outputFile = android.PathForModuleOut(ctx, ctx.ModuleName()+apexSuffix)
+	a.outputFiles[apexType] = android.PathForModuleOut(ctx, ctx.ModuleName()+suffix)
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        java.Signapk,
 		Description: "signapk",
-		Output:      a.outputFile,
+		Output:      a.outputFiles[apexType],
 		Input:       unsignedOutputFile,
 		Args: map[string]string{
 			"certificates": strings.Join([]string{certificate.Pem.String(), certificate.Key.String()}, " "),
 			"flags":        "-a 4096", //alignment
 		},
 	})
+
+	// Install to $OUT/soong/{target,host}/.../apex
+	ctx.InstallFile(android.PathForModuleInstall(ctx, "apex"), ctx.ModuleName()+suffix, a.outputFiles[apexType])
 }
 
 func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) {
@@ -677,7 +793,24 @@
 }
 
 func (a *apexBundle) AndroidMk() android.AndroidMkData {
-	if a.flattened {
+	writers := []android.AndroidMkData{}
+	if a.apexTypes.image() {
+		writers = append(writers, a.androidMkForType(imageApex))
+	}
+	if a.apexTypes.zip() {
+		writers = append(writers, a.androidMkForType(zipApex))
+	}
+	return android.AndroidMkData{
+		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+			for _, data := range writers {
+				data.Custom(w, name, prefix, moduleDir, data)
+			}
+		}}
+}
+
+func (a *apexBundle) androidMkForType(apexType apexPackaging) android.AndroidMkData {
+	// Only image APEXes can be flattened.
+	if a.flattened && apexType.image() {
 		return android.AndroidMkData{
 			Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
 				moduleNames := []string{}
@@ -716,29 +849,37 @@
 	} else {
 		return android.AndroidMkData{
 			Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+				// zip-apex is the less common type so have the name refer to the image-apex
+				// only and use {name}.zip if you want the zip-apex
+				if apexType == zipApex && a.apexTypes == both {
+					name = name + ".zip"
+				}
 				fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)")
 				fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 				fmt.Fprintln(w, "LOCAL_MODULE :=", name)
 				fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC") // do we need a new class?
-				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", a.outputFile.String())
+				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", a.outputFiles[apexType].String())
 				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", filepath.Join("$(OUT_DIR)", a.installDir.RelPathString()))
-				fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", name+apexSuffix)
+				fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", name+apexType.suffix())
 				fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", String(a.properties.Key))
 				fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
 
-				fmt.Fprintln(w, "ALL_MODULES.$(LOCAL_MODULE).BUNDLE :=", a.bundleModuleFile.String())
+				if apexType == imageApex {
+					fmt.Fprintln(w, "ALL_MODULES.$(LOCAL_MODULE).BUNDLE :=", a.bundleModuleFile.String())
+				}
 			}}
 	}
 }
 
 func apexBundleFactory() android.Module {
-	module := &apexBundle{}
+	module := &apexBundle{
+		outputFiles: map[apexPackaging]android.WritablePath{},
+	}
 	module.AddProperties(&module.properties)
-	module.Prefer32(func(ctx android.BaseModuleContext, base *android.ModuleBase,
-		class android.OsClass) bool {
+	module.Prefer32(func(ctx android.BaseModuleContext, base *android.ModuleBase, class android.OsClass) bool {
 		return class == android.Device && ctx.Config().DevicePrefer32BitExecutables()
 	})
-	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitAndroidMultiTargetsArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
 	return module
 }
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 41d8455..d1794ee 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -188,8 +188,56 @@
 	ensureListContains(t, ctx.ModuleVariantsForTests("mylib2"), "android_arm64_armv8-a_shared_myapex")
 
 	// Ensure that both direct and indirect deps are copied into apex
-	ensureContains(t, copyCmds, "image/lib64/mylib.so")
-	ensureContains(t, copyCmds, "image/lib64/mylib2.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib2.so")
+}
+
+func TestBasicZipApex(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			payload_type: "zip",
+			native_shared_libs: ["mylib"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["mylib2"],
+			system_shared_libs: [],
+			stl: "none",
+		}
+
+		cc_library {
+			name: "mylib2",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+		}
+	`)
+
+	zipApexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("zipApexRule")
+	copyCmds := zipApexRule.Args["copy_commands"]
+
+	// Ensure that main rule creates an output
+	ensureContains(t, zipApexRule.Output.String(), "myapex.zipapex.unsigned")
+
+	// Ensure that APEX variant is created for the direct dep
+	ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_myapex")
+
+	// Ensure that APEX variant is created for the indirect dep
+	ensureListContains(t, ctx.ModuleVariantsForTests("mylib2"), "android_arm64_armv8-a_shared_myapex")
+
+	// Ensure that both direct and indirect deps are copied into apex
+	ensureContains(t, copyCmds, "image.zipapex/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image.zipapex/lib64/mylib2.so")
 }
 
 func TestApexWithStubs(t *testing.T) {
@@ -239,13 +287,13 @@
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that direct non-stubs dep is always included
-	ensureContains(t, copyCmds, "image/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
 
 	// Ensure that indirect stubs dep is not included
-	ensureNotContains(t, copyCmds, "image/lib64/mylib2.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
 
 	// Ensure that direct stubs dep is included
-	ensureContains(t, copyCmds, "image/lib64/mylib3.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib3.so")
 
 	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_myapex").Rule("ld").Args["libFlags"]
 
@@ -326,12 +374,12 @@
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that mylib, libm, libdl are included.
-	ensureContains(t, copyCmds, "image/lib64/mylib.so")
-	ensureContains(t, copyCmds, "image/lib64/libm.so")
-	ensureContains(t, copyCmds, "image/lib64/libdl.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libm.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libdl.so")
 
 	// Ensure that libc is not included (since it has stubs and not listed in native_shared_libs)
-	ensureNotContains(t, copyCmds, "image/lib64/libc.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libc.so")
 
 	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_myapex").Rule("ld").Args["libFlags"]
 	mylibCFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_myapex").Rule("cc").Args["cFlags"]