Build GKI APEX from $(PRODUCT_OUT)/boot*.img
Test: on CF build boot_5.4-android12-0 and inspect output
Test: on aosp_arm64 build boot-5.4_5.4-android12-0 and inspect output
Bug: 161563386
Change-Id: I74988d9d78f7074e331ef46cc0aee52c3201ab8a
diff --git a/Android.bp b/Android.bp
index 69c241d..d42fc0d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,3 +104,21 @@
"-Werror",
],
}
+
+// Build GKI APEX 5.4-android12-0 from $(PRODUCT_OUT)/boot.img.
+// Also generate test packages.
+gki_apex {
+ name: "com.android.gki.kmi_5_4_android12_0",
+ installable: true,
+ kmi_version: "5.4-android12-0",
+ product_out_path: "boot.img",
+ gen_test: true,
+}
+
+// Build GKI APEX 5.4-android12-0 from $(PRODUCT_OUT)/boot-5.4.img
+gki_apex {
+ name: "com.android.gki.kmi_5_4_android12_0_boot-5.4",
+ installable: false,
+ kmi_version: "5.4-android12-0",
+ product_out_path: "boot-5.4.img",
+}
diff --git a/build/Android.bp b/build/Android.bp
new file mode 100644
index 0000000..31947b2
--- /dev/null
+++ b/build/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+bootstrap_go_package {
+ name: "gki-soong-rules",
+ pkgPath: "android/soong/gki",
+ pluginFor: ["soong_build"],
+ deps: [
+ "blueprint",
+ "blueprint-proptools",
+ "soong",
+ "soong-android",
+ "soong-apex",
+ "soong-phony",
+ ],
+ srcs: [
+ "gki.go",
+ "kmi.go",
+ "properties.go",
+ "raw_img_ota.go",
+ ],
+ testSrcs: [
+ "kmi_test.go",
+ ],
+}
diff --git a/build/gki.go b/build/gki.go
new file mode 100644
index 0000000..f48693b
--- /dev/null
+++ b/build/gki.go
@@ -0,0 +1,283 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gki
+
+import (
+ "path/filepath"
+ "strings"
+
+ "android/soong/android"
+ "android/soong/apex"
+ "android/soong/etc"
+ "android/soong/genrule"
+
+ "github.com/google/blueprint/proptools"
+)
+
+type gkiApexProperties struct {
+ // Path relative to $(PRODUCT_OUT) that points to the boot image. This is
+ // passed to the generated makefile_goal.
+ // Exactly one of [factory, product_out_path] must be set.
+ Product_out_path *string
+
+ // Declared KMI version of the boot image. Example: "5.4-android12-0"
+ Kmi_version *string
+
+ // The certificate to sign the OTA payload.
+ // 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".
+ Ota_payload_certificate *string
+
+ // Whether test APEXes are generated. Test APEXes are named with
+ // ${name}_test_high and ${name}_test_low, respectively.
+ Gen_test *bool
+
+ // Whether this APEX is installable to one of the partitions. Default:
+ // see apex.installable.
+ Installable *bool
+
+ // Whether modules should be enabled according to board variables.
+ ModulesEnabled bool `blueprint:"mutated"`
+ // APEX package name that will be declared in the APEX manifest.
+ // e.g. com.android.gki.kmi_5_4_android12_0
+ ApexName *string `blueprint:"mutated"`
+}
+
+type gkiApex struct {
+ android.ModuleBase
+ properties gkiApexProperties
+}
+
+func init() {
+ android.RegisterModuleType("gki_apex", gkiApexFactory)
+}
+
+// Declare a GKI APEX. Generate a set of modules to define an apex with name
+// "com.android.gki" + sanitized(kmi_version).
+func gkiApexFactory() android.Module {
+ g := &gkiApex{}
+ g.AddProperties(&g.properties)
+ android.InitAndroidModule(g)
+ android.AddLoadHook(g, func(ctx android.LoadHookContext) { gkiApexMutator(ctx, g) })
+ return g
+}
+
+func gkiApexMutator(mctx android.LoadHookContext, g *gkiApex) {
+ g.validateAndSetMutableProperties(mctx)
+ g.createModulesRealApexes(mctx)
+}
+
+func (g *gkiApex) validateAndSetMutableProperties(mctx android.LoadHookContext) {
+ // Parse kmi_version property to find APEX name.
+ apexName, err := kmiVersionToApexName(proptools.String(g.properties.Kmi_version))
+ if err != nil {
+ mctx.PropertyErrorf("kmi_version", err.Error())
+ return
+ }
+
+ // Set mutable properties.
+ g.properties.ModulesEnabled = g.bootImgHasRules(mctx) && g.boardDefinesKmiVersion(mctx)
+ g.properties.ApexName = proptools.StringPtr(apexName)
+}
+
+// Create modules for a real APEX package that contains an OTA payload.
+func (g *gkiApex) createModulesRealApexes(mctx android.LoadHookContext) {
+ // Import $(PRODUCT_OUT)/boot.img to Soong
+ bootImage := g.moduleName() + "_bootimage"
+ mctx.CreateModule(android.MakefileGoalFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(bootImage),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &makefileGoalProperties{
+ Product_out_path: g.properties.Product_out_path,
+ })
+ // boot.img -> kernel_release.txt
+ mctx.CreateModule(genrule.GenRuleFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(g.kernelReleaseFileName()),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &genRuleProperties{
+ Defaults: []string{"extract_kernel_release_defaults"},
+ Srcs: []string{":" + bootImage},
+ })
+ // boot.img -> payload.bin and payload_properties.txt
+ otaPayloadGen := g.moduleName() + "_ota_payload_gen"
+ mctx.CreateModule(rawImageOtaFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(otaPayloadGen),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &rawImageOtaProperties{
+ Certificate: g.properties.Ota_payload_certificate,
+ Image_goals: []string{"boot:" + bootImage},
+ })
+ // copy payload.bin to <apex>/etc/ota
+ mctx.CreateModule(etc.PrebuiltEtcFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(g.otaPayloadName()),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &prebuiltEtcProperties{
+ Src: proptools.StringPtr(":" + otaPayloadGen + "{" + payloadTag + "}"),
+ Filename_from_src: proptools.BoolPtr(true),
+ Relative_install_path: proptools.StringPtr("ota"),
+ Installable: proptools.BoolPtr(false),
+ })
+ // copy payload_properties.txt to <apex>/etc/ota
+ mctx.CreateModule(etc.PrebuiltEtcFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(g.otaPropertiesName()),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &prebuiltEtcProperties{
+ Src: proptools.StringPtr(":" + otaPayloadGen + "{" + payloadPropertiesTag + "}"),
+ Filename_from_src: proptools.BoolPtr(true),
+ Relative_install_path: proptools.StringPtr("ota"),
+ Installable: proptools.BoolPtr(false),
+ })
+ // Create the APEX module with name g.moduleName(). Use factory APEX version.
+ g.createModulesRealApex(mctx, g.moduleName(), g.properties.Installable, "")
+
+ // Create test APEX modules if gen_test. Test packages are not installable.
+ // Use hard-coded APEX version.
+ if proptools.Bool(g.properties.Gen_test) {
+ g.createModulesRealApex(mctx, g.moduleName()+"_test_high", proptools.BoolPtr(false), "1000000000")
+ g.createModulesRealApex(mctx, g.moduleName()+"_test_low", proptools.BoolPtr(false), "1")
+ }
+}
+
+func (g *gkiApex) createModulesRealApex(mctx android.LoadHookContext,
+ moduleName string,
+ overrideInstallable *bool,
+ overrideApexVersion string) {
+ // Check kmi_version property against kernel_release.txt, then
+ // kernel_release.txt -> apex_manifest.json.
+ apexManifest := moduleName + "_apex_manifest"
+ mctx.CreateModule(genrule.GenRuleFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(apexManifest),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &genRuleProperties{
+ Tools: []string{"build_gki_apex_manifest"},
+ Out: []string{"apex_manifest.json"},
+ Srcs: []string{":" + g.kernelReleaseFileName()},
+ Cmd: proptools.StringPtr(g.createApexManifestCmd(overrideApexVersion)),
+ })
+ // The APEX module.
+ mctx.CreateModule(apex.BundleFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(moduleName),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &apexProperties{
+ Apex_name: g.properties.ApexName,
+ Manifest: proptools.StringPtr(":" + apexManifest),
+ Defaults: []string{"com.android.gki_defaults"},
+ // A real GKI APEX cannot be preinstalled to the device.
+ // It can only be provided as an update.
+ Installable: overrideInstallable,
+ Prebuilts: []string{
+ g.otaPayloadName(),
+ g.otaPropertiesName(),
+ },
+ })
+}
+
+// Original module name as specified by the "name" property.
+// This is also the APEX module name, i.e. the file name of the APEX file.
+// This is also the prefix of names of all generated modules that the phony module depends on.
+// e.g. com.android.gki.kmi_5_4_android12_0_boot
+func (g *gkiApex) moduleName() string {
+ return g.BaseModuleName()
+}
+
+// The appeared name of this gkiApex object. Exposed to Soong to avoid conflicting with
+// the generated APEX module with name moduleName().
+// e.g. com.android.gki.kmi_5_4_android12_0_boot_all
+func (g *gkiApex) Name() string {
+ return g.moduleName() + "_all"
+}
+
+// Names for intermediate modules.
+func (g *gkiApex) kernelReleaseFileName() string {
+ return g.moduleName() + "_bootimage_kernel_release_file"
+}
+
+func (g *gkiApex) otaPayloadName() string {
+ return g.moduleName() + "_ota_payload"
+}
+
+func (g *gkiApex) otaPropertiesName() string {
+ return g.moduleName() + "_ota_payload_properties"
+}
+
+// If the boot image pointed at product_out_path has no rule to be generated, do not generate any
+// build rules for this gki_apex module. For example, if this gki_apex module is:
+// { name: "foo", product_out_path: "boot-bar.img" }
+// But there is no rule to generate boot-bar.img, then
+// - `m foo` fails with `unknown target 'foo'`
+// - checkbuild is still successful. The module foo doesn't even exist, so there
+// is no dependency on boot-bar.img
+//
+// There is a rule to generate "boot-foo.img" if "kernel-foo" is in BOARD_KERNEL_BINARIES.
+// As a special case, there is a rule to generate "boot.img" if BOARD_KERNEL_BINARIES is empty,
+// or "kernel" is in BOARD_KERNEL_BINARIES.
+func (g *gkiApex) bootImgHasRules(mctx android.EarlyModuleContext) bool {
+ kernelNames := mctx.DeviceConfig().BoardKernelBinaries()
+ if len(kernelNames) == 0 {
+ return proptools.String(g.properties.Product_out_path) == "boot.img"
+ }
+ for _, kernelName := range kernelNames {
+ validBootImagePath := strings.Replace(kernelName, "kernel", "boot", -1) + ".img"
+ if proptools.String(g.properties.Product_out_path) == validBootImagePath {
+ return true
+ }
+ }
+ return false
+}
+
+// Only generate if this module's kmi_version property is in BOARD_KERNEL_MODULE_INTERFACE_VERSIONS.
+// Otherwise, this board does not support GKI APEXes, so no modules are generated at all.
+// This function also avoids building invalid modules in checkbuild. For example, if these
+// gki_apex modules are defined:
+// gki_apex { name: "boot-kmi-1", kmi_version: "1", product_out_path: "boot.img" }
+// gki_apex { name: "boot-kmi-2", kmi_version: "2", product_out_path: "boot.img" }
+// But a given device's $PRODUCT_OUT/boot.img can only support at most one KMI version.
+// Disable some modules accordingly to make sure checkbuild still works.
+func (g *gkiApex) boardDefinesKmiVersion(mctx android.EarlyModuleContext) bool {
+ kmiVersions := mctx.DeviceConfig().BoardKernelModuleInterfaceVersions()
+ return android.InList(proptools.String(g.properties.Kmi_version), kmiVersions)
+}
+
+// - For factory GKI APEX, write APEX manifest JSON to $(out) for factory APEX.
+// e.g. 5.4-android12-0 => name: "com.android.gki.kmi_5_4_android12_0", version: factory version.
+//
+// - For real GKI APEX, transform kernel release file in $(in) to KMI version + sublevel.
+// e.g. 5.4.42-android12-0 => name: "com.android.gki.kmi_5_4_android12_0", version: "42"
+//
+// Finally, write APEX manifest JSON to $(out).
+func (g *gkiApex) createApexManifestCmd(apexVersion string) string {
+ ret := `$(location build_gki_apex_manifest) ` +
+ `--kmi_version "` + proptools.String(g.properties.Kmi_version) + `" ` +
+ `--apex_manifest $(out) --kernel_release_file $(in)`
+ // Override version field if set.
+ if apexVersion != "" {
+ ret += ` --apex_version ` + apexVersion
+ }
+ return ret
+}
+
+func (g *gkiApex) DepsMutator(ctx android.BottomUpMutatorContext) {
+}
+
+func (g *gkiApex) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+}
+
+// OTA payload binary is signed with default_system_dev_certificate, which is equivalent to
+// DefaultAppCertificate().
+func getDefaultCertificate(ctx android.EarlyModuleContext) string {
+ pem, _ := ctx.Config().DefaultAppCertificate(ctx)
+ return strings.TrimSuffix(pem.String(), filepath.Ext(pem.String()))
+}
diff --git a/build/kmi.go b/build/kmi.go
new file mode 100644
index 0000000..27b00bf
--- /dev/null
+++ b/build/kmi.go
@@ -0,0 +1,45 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Minimum support of KMI version in Go. Keep in sync with libkver.
+
+package gki
+
+import (
+ "fmt"
+ "regexp"
+)
+
+var digits = "([0-9]+)"
+var reKmi = regexp.MustCompile("^([0-9]+)[.]([0-9]+)-(android[0-9]+)-([0-9]+)$")
+
+// Input is a valid KMI version, e.g. 5.4-android12-0.
+// Return a sanitized string to be used as a suffix of APEX package name
+// com.android.gki.kmi_5_4_android12_0
+// Keep in sync with libkver.
+func kmiVersionToApexName(s string) (string, error) {
+ matches := reKmi.FindAllStringSubmatch(s, 4)
+
+ if matches == nil {
+ return "", fmt.Errorf("Poorly formed KMI version: %q must match regex %q", s, reKmi)
+ }
+
+ version := matches[0][1]
+ patchLevel := matches[0][2]
+ androidRelease := matches[0][3]
+ kmiGeneration := matches[0][4]
+
+ return fmt.Sprintf("com.android.gki.kmi_%s_%s_%s_%s",
+ version, patchLevel, androidRelease, kmiGeneration), nil
+}
diff --git a/build/kmi_test.go b/build/kmi_test.go
new file mode 100644
index 0000000..de7642b
--- /dev/null
+++ b/build/kmi_test.go
@@ -0,0 +1,48 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Minimum support of KMI version in Go. Keep in sync with libkver.
+
+package gki
+
+import (
+ "testing"
+)
+
+func expectValid(t *testing.T, kmi string, expectedApexName string) {
+ t.Helper()
+ got, e := kmiVersionToApexName(kmi)
+ if e != nil {
+ t.Errorf("Expected no error when parsing %q, got %q", kmi, e)
+ }
+ if got != expectedApexName {
+ t.Errorf("Expected kmiVersionToApexName(%q) == %q, got %q", kmi, expectedApexName, got)
+ }
+}
+
+func expectInvalid(t *testing.T, kmi string) {
+ t.Helper()
+ got, e := kmiVersionToApexName(kmi)
+ if e == nil {
+ t.Errorf("Expected error when parsing %q, got no error with result %q", kmi, got)
+ }
+}
+
+func TestParse(t *testing.T) {
+ expectInvalid(t, "")
+ expectInvalid(t, "foobar")
+ expectInvalid(t, "1")
+ expectValid(t, "5.4-android12-0", "com.android.gki.kmi_5_4_android12_0")
+ expectValid(t, "5.4-android12-42", "com.android.gki.kmi_5_4_android12_42")
+}
diff --git a/build/properties.go b/build/properties.go
new file mode 100644
index 0000000..c6b819d
--- /dev/null
+++ b/build/properties.go
@@ -0,0 +1,49 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gki
+
+type moduleCommonProperties struct {
+ Name *string
+ Enabled *bool
+ Required []string
+}
+
+type makefileGoalProperties struct {
+ Product_out_path *string
+}
+
+type genRuleProperties struct {
+ Cmd *string
+ Defaults []string
+ Out []string
+ Srcs []string
+ Tools []string
+}
+
+type prebuiltEtcProperties struct {
+ Src *string
+ Filename_from_src *bool
+ Relative_install_path *string
+ Installable *bool
+}
+
+type apexProperties struct {
+ Apex_name *string
+ Manifest *string
+ Defaults []string
+ Installable *bool
+ Prebuilts []string
+ Overrides []string
+}
diff --git a/build/raw_img_ota.go b/build/raw_img_ota.go
new file mode 100644
index 0000000..2e528d4
--- /dev/null
+++ b/build/raw_img_ota.go
@@ -0,0 +1,244 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// A special genrule that creates OTA payload and payload_properties from a raw
+// image. This rule is created so that the two outputs, payload and
+// payload_properties, can be distinguished with tags.
+
+package gki
+
+import (
+ "fmt"
+ "strings"
+
+ "android/soong/android"
+ "android/soong/java"
+
+ "github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
+)
+
+type dependencyTag struct {
+ blueprint.BaseDependencyTag
+ name string
+}
+
+// ["foo", "bar"] -> ["${foo}", "${bar}"]
+func toVars(deps []string) []string {
+ ret := []string{}
+ for _, dep := range deps {
+ ret = append(ret, fmt.Sprintf("${%s}", dep))
+ }
+ return ret
+}
+
+var (
+ certificateTag = dependencyTag{name: "certificate"}
+ rawImageTag = dependencyTag{name: "raw_image"}
+
+ pctx = android.NewPackageContext("android/gki")
+
+ otaFromRawImageDeps = []string{
+ "ota_from_raw_image",
+
+ // Needed by ota_from_target_files
+ "brillo_update_payload",
+
+ // Needed by brillo_update_payload
+ "delta_generator",
+
+ // Needed by GetBootImageTimestamp
+ "lz4",
+ "toybox",
+ "unpack_bootimg",
+ }
+
+ otaFromRawImageVarDeps = toVars(otaFromRawImageDeps)
+
+ otaFromRawImageRule = pctx.AndroidStaticRule("ota_from_raw_image", blueprint.RuleParams{
+ Command: `${ota_from_raw_image} --tools ` + strings.Join(otaFromRawImageVarDeps, " ") +
+ ` ${kwargs} --out ${outDir} -- ${inputArg}`,
+ CommandDeps: otaFromRawImageVarDeps,
+ Description: "ota_from_raw_image ${outDir}",
+ }, "kwargs", "outDir", "inputArg")
+
+ // Tags to OutputFiles
+ payloadTag = "payload"
+ payloadPropertiesTag = "properties"
+)
+
+func init() {
+ for _, dep := range otaFromRawImageDeps {
+ pctx.HostBinToolVariable(dep, dep)
+ }
+ // Intentionally not register this module so that it can only be constructed by gki_apex.
+}
+
+type rawImageOtaProperties struct {
+ // 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
+
+ // A set of images and their related modules. Must be in this form
+ // IMAGE_NAME:MODULE, where IMAGE_NAME is an image name like "boot", and
+ // MODULE is the name of a makefile_goal.
+ Image_goals []string
+}
+
+type rawImageOta struct {
+ android.ModuleBase
+ properties rawImageOtaProperties
+
+ pem android.Path
+ key android.Path
+
+ outPayload android.WritablePath
+ outProperties android.WritablePath
+}
+
+// Declare a rule that generates a signed OTA payload from a raw image. This
+// includes payload.bin and payload_properties.txt.
+func rawImageOtaFactory() android.Module {
+ r := &rawImageOta{}
+ r.AddProperties(&r.properties)
+ android.InitAndroidModule(r)
+ return r
+}
+
+func (r *rawImageOta) OutputFiles(tag string) (android.Paths, error) {
+ switch tag {
+ case "":
+ return android.Paths{r.outPayload, r.outProperties}, nil
+ case payloadTag:
+ return android.Paths{r.outPayload}, nil
+ case payloadPropertiesTag:
+ return android.Paths{r.outProperties}, nil
+ default:
+ return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+ }
+}
+
+var _ android.OutputFileProducer = (*rawImageOta)(nil)
+
+func (r *rawImageOta) getCertString(ctx android.BaseModuleContext) string {
+ moduleName := ctx.ModuleName()
+ certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(moduleName)
+ if overridden {
+ return ":" + certificate
+ }
+ return proptools.String(r.properties.Certificate)
+}
+
+// Returns module->image_name mapping, e.g. "bootimage_soong"->"boot"
+func (r *rawImageOta) goalToImage(ctx android.EarlyModuleContext) map[string]string {
+ ret := map[string]string{}
+ for _, imageGoal := range r.properties.Image_goals {
+ lst := strings.Split(imageGoal, ":")
+ if len(lst) != 2 {
+ ctx.PropertyErrorf("image_goals", "Must be in the form IMAGE_NAME:MODULE")
+ return map[string]string{}
+ }
+ ret[lst[1]] = lst[0]
+ }
+ return ret
+}
+
+func (r *rawImageOta) DepsMutator(ctx android.BottomUpMutatorContext) {
+ // Add dependency to modules in image_goals
+ for module, _ := range r.goalToImage(ctx) {
+ ctx.AddVariationDependencies(nil, rawImageTag, module)
+ }
+ // Add dependency to certificate module, if any.
+ cert := android.SrcIsModule(r.getCertString(ctx))
+ if cert != "" {
+ ctx.AddVariationDependencies(nil, certificateTag, cert)
+ }
+}
+
+func (r *rawImageOta) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ inputArg := []string{}
+ kwargs := []string{}
+ implicits := android.Paths{}
+
+ // Handle image_goals
+ goalToImage := r.goalToImage(ctx)
+ ctx.VisitDirectDepsWithTag(rawImageTag, func(module android.Module) {
+ depName := ctx.OtherModuleName(module)
+ imgPath := android.OutputFileForModule(ctx, module, "")
+ if imgPath != nil {
+ implicits = append(implicits, imgPath)
+ inputArg = append(inputArg, goalToImage[depName]+":"+imgPath.String())
+ } else {
+ ctx.ModuleErrorf("image dependency %q does not generate any output", depName)
+ }
+ })
+
+ // Handle certificate
+ ctx.VisitDirectDepsWithTag(certificateTag, func(module android.Module) {
+ depName := ctx.OtherModuleName(module)
+ if cert, ok := module.(*java.AndroidAppCertificate); ok {
+ r.pem = cert.Certificate.Pem
+ r.key = cert.Certificate.Key
+ } else {
+ ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", depName)
+ }
+ })
+ r.setCertificateAndPrivateKey(ctx)
+ keyName, keyError := removeCertExt(r.pem)
+ if keyError != nil {
+ ctx.ModuleErrorf("Cannot get certificate to sign the OTA payload binary: " + keyError.Error())
+ }
+ implicits = append(implicits, r.pem, r.key)
+ kwargs = append(kwargs, "--key "+proptools.String(keyName))
+
+ // Set outputs
+ outDir := android.PathForModuleGen(ctx, "payload_files")
+ r.outPayload = outDir.Join(ctx, "payload.bin")
+ r.outProperties = outDir.Join(ctx, "payload_properties.txt")
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: otaFromRawImageRule,
+ Description: "Generate OTA from raw image",
+ Implicits: implicits,
+ Outputs: android.WritablePaths{r.outPayload, r.outProperties},
+ Args: map[string]string{
+ "kwargs": strings.Join(kwargs, " "),
+ "outDir": outDir.String(),
+ "inputArg": strings.Join(inputArg, " "),
+ },
+ })
+}
+
+func (r *rawImageOta) setCertificateAndPrivateKey(ctx android.ModuleContext) {
+ if r.pem == nil {
+ cert := proptools.String(r.properties.Certificate)
+ if cert == "" {
+ pem, key := ctx.Config().DefaultAppCertificate(ctx)
+ r.pem = pem
+ r.key = key
+ } else {
+ defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
+ r.pem = defaultDir.Join(ctx, cert+".x509.pem")
+ r.key = defaultDir.Join(ctx, cert+".pk8")
+ }
+ }
+}
+
+func removeCertExt(path android.Path) (*string, error) {
+ s := path.String()
+ if strings.HasSuffix(s, ".x509.pem") {
+ return proptools.StringPtr(strings.TrimSuffix(s, ".x509.pem")), nil
+ }
+ return nil, fmt.Errorf("Path %q does not end with .x509.pem", s)
+}