| // 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())) |
| } |