You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

312 lines
12 KiB

// 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)
}
func testApexBundleFactory() android.Module {
return apex.ApexBundleFactory(true /* testApex */, false /* art */)
}
// 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(), false, "")
// 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", true, "1000000000")
g.createModulesRealApex(mctx, g.moduleName()+"_test_low", true, "1")
}
}
func (g *gkiApex) createModulesRealApex(mctx android.LoadHookContext,
moduleName string,
isTestApex 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.
// For test APEXes, if module is not enabled because KMI version is not
// compatible with the device, create a stub module that produces an empty
// file. This is so that the module name can be used in tests.
if isTestApex && !g.properties.ModulesEnabled {
mctx.CreateModule(genrule.GenRuleFactory, &moduleCommonProperties{
Name: proptools.StringPtr(moduleName),
}, &genRuleProperties{
Out: []string{moduleName + ".apex"},
Cmd: proptools.StringPtr(`touch $(out)`),
})
return
}
// For test APEXes, if module is enabled, build an apex_test with installable: false.
// For installed APEXes, build apex, respecting installable and enabled.
apexFactory := apex.BundleFactory
overrideInstallable := g.properties.Installable
if isTestApex {
apexFactory = testApexBundleFactory
overrideInstallable = proptools.BoolPtr(false)
}
mctx.CreateModule(apexFactory, &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 boardDefinesKmiVersion(mctx android.EarlyModuleContext, kmiVersion string) bool {
kmiVersions := mctx.DeviceConfig().BoardKernelModuleInterfaceVersions()
return android.InList(kmiVersion, kmiVersions)
}
func (g *gkiApex) boardDefinesKmiVersion(mctx android.EarlyModuleContext) bool {
return boardDefinesKmiVersion(mctx, proptools.String(g.properties.Kmi_version))
}
// 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: "300000000"
// 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()))
}