Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5098a612 authored by Alex Light's avatar Alex Light
Browse files

Add zip-apex

This adds support for an apex-like file where the payload is a zip
file instead of a FS image. This makes it easier to examine and use
without root. These can also be built for the host.

This can be controlled using the 'type' modifier in the apex build
rule. 'type' may be "image" to build an apex file called {name}.apex
with an FS image payload, "zip" to build an apex with called
{name}.zipapex with a zip-file payload or "both" to build both. By
default we will use "image".

This also adds support for setting apex rules to
"host_supported: true".

Test: cd art/build/apex; mma; examine the com.android.runtime.host.zipapex
      artifact.
Test: go test android/soong/apex -v
Bug: 120436895

Change-Id: I534d330672211ac5ccc3bd5a0c89b9fc507bf51e
parent ce16f3ba
Loading
Loading
Loading
Loading
+233 −92
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ var (
			`${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 @@ var (
		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 @@ var (
	}, "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 @@ type apexBundleProperties struct {
	// 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 @@ const (
	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 @@ type apexBundle struct {

	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 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	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 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	a.flattened = ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuild()
	a.installDir = android.PathForModuleInstall(ctx, "apex")
	a.filesInfo = filesInfo

	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)
			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,7 +641,40 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext, keyFile and
		certificate = java.Certificate{pem, key}
	}

	// files and dirs that will be created in apex
	manifest := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))

	var abis []string
	for _, target := range ctx.MultiTargets() {
		if len(target.Arch.Abi) > 0 {
			abis = append(abis, target.Arch.Abi[0])
		}
	}

	abis = android.FirstUniqueStrings(abis)

	suffix := apexType.suffix()
	unsignedOutputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+suffix+".unsigned")

	filesToCopy := []android.Path{}
	for _, f := range a.filesInfo {
		filesToCopy = append(filesToCopy, f.builtFile)
	}

	copyCommands := []string{}
	for i, src := range filesToCopy {
		dest := filepath.Join(a.filesInfo[i].installDir, src.Base())
		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, manifest)

	outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String()
	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")

	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 {
@@ -579,8 +701,6 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext, keyFile and
			},
		})

	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)
@@ -590,32 +710,17 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext, keyFile and
		}
		fileContexts := fileContextsOptionalPath.Path()

	unsignedOutputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+apexSuffix+".unsigned")
		// Additional implicit inputs.
		implicitInputs = append(implicitInputs, cannedFsConfig, fileContexts, keyFile)

	filesToCopy := []android.Path{}
	for _, f := range a.filesInfo {
		filesToCopy = append(filesToCopy, f.builtFile)
	}

	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)
		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)
	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",
			Description: "apex (" + apexType.name() + ")",
			Args: map[string]string{
				"tool_path":        outHostBinDir + ":" + prebuiltSdkToolsBinDir,
			"image_dir":        android.PathForModuleOut(ctx, "image").String(),
				"image_dir":        android.PathForModuleOut(ctx, "image"+suffix).String(),
				"copy_commands":    strings.Join(copyCommands, " && "),
				"manifest":         manifest.String(),
				"file_contexts":    fileContexts.String(),
@@ -624,14 +729,8 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext, keyFile and
			},
		})

	var abis []string
	for _, target := range ctx.MultiTargets() {
		abis = append(abis, target.Arch.Abi[0])
	}
	abis = android.FirstUniqueStrings(abis)

	apexProtoFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".pb"+apexSuffix)
	bundleModuleFile := android.PathForModuleOut(ctx, ctx.ModuleName()+"-base.zip")
		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{
@@ -644,24 +743,41 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext, keyFile and
		ctx.Build(pctx, android.BuildParams{
			Rule:        apexBundleRule,
			Input:       apexProtoFile,
		Output:      bundleModuleFile,
			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(),
			},
		})
	}

	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) buildFlattenedApex(ctx android.ModuleContext) {
}

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 @@ func (a *apexBundle) AndroidMk() android.AndroidMkData {
	} 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)")

				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
}
+57 −9
Original line number Diff line number Diff line
@@ -188,8 +188,56 @@ func TestBasicApex(t *testing.T) {
	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 @@ func TestApexWithStubs(t *testing.T) {
	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 @@ func TestApexWithSystemLibsStubs(t *testing.T) {
	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"]