1// Copyright (C) 2020 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package gki 16 17import ( 18 "path/filepath" 19 "strings" 20 21 "android/soong/android" 22 "android/soong/apex" 23 "android/soong/etc" 24 "android/soong/genrule" 25 26 "github.com/google/blueprint/proptools" 27) 28 29type gkiApexProperties struct { 30 // Path relative to $(PRODUCT_OUT) that points to the boot image. This is 31 // passed to the generated makefile_goal. 32 // Exactly one of [factory, product_out_path] must be set. 33 Product_out_path *string 34 35 // Declared KMI version of the boot image. Example: "5.4-android12-0" 36 Kmi_version *string 37 38 // The certificate to sign the OTA payload. 39 // The name of a certificate in the default certificate directory, blank to 40 // use the default product certificate, 41 // or an android_app_certificate module name in the form ":module". 42 Ota_payload_certificate *string 43 44 // Whether test APEXes are generated. Test APEXes are named with 45 // ${name}_test_high and ${name}_test_low, respectively. 46 Gen_test *bool 47 48 // Whether this APEX is installable to one of the partitions. Default: 49 // see apex.installable. 50 Installable *bool 51 52 // Whether modules should be enabled according to board variables. 53 ModulesEnabled bool `blueprint:"mutated"` 54 // APEX package name that will be declared in the APEX manifest. 55 // e.g. com.android.gki.kmi_5_4_android12_0 56 ApexName *string `blueprint:"mutated"` 57} 58 59type gkiApex struct { 60 android.ModuleBase 61 properties gkiApexProperties 62} 63 64func init() { 65 android.RegisterModuleType("gki_apex", gkiApexFactory) 66} 67 68// Declare a GKI APEX. Generate a set of modules to define an apex with name 69// "com.android.gki" + sanitized(kmi_version). 70func gkiApexFactory() android.Module { 71 g := &gkiApex{} 72 g.AddProperties(&g.properties) 73 android.InitAndroidModule(g) 74 android.AddLoadHook(g, func(ctx android.LoadHookContext) { gkiApexMutator(ctx, g) }) 75 return g 76} 77 78func gkiApexMutator(mctx android.LoadHookContext, g *gkiApex) { 79 g.validateAndSetMutableProperties(mctx) 80 g.createModulesRealApexes(mctx) 81} 82 83func (g *gkiApex) validateAndSetMutableProperties(mctx android.LoadHookContext) { 84 // Parse kmi_version property to find APEX name. 85 apexName, err := kmiVersionToApexName(proptools.String(g.properties.Kmi_version)) 86 if err != nil { 87 mctx.PropertyErrorf("kmi_version", err.Error()) 88 return 89 } 90 91 // Set mutable properties. 92 g.properties.ModulesEnabled = g.bootImgHasRules(mctx) && g.boardDefinesKmiVersion(mctx) 93 g.properties.ApexName = proptools.StringPtr(apexName) 94} 95 96func testApexBundleFactory() android.Module { 97 return apex.ApexBundleFactory(true /* testApex */, false /* art */) 98} 99 100// Create modules for a real APEX package that contains an OTA payload. 101func (g *gkiApex) createModulesRealApexes(mctx android.LoadHookContext) { 102 // Import $(PRODUCT_OUT)/boot.img to Soong 103 bootImage := g.moduleName() + "_bootimage" 104 mctx.CreateModule(android.MakefileGoalFactory, &moduleCommonProperties{ 105 Name: proptools.StringPtr(bootImage), 106 Enabled: proptools.BoolPtr(g.properties.ModulesEnabled), 107 }, &makefileGoalProperties{ 108 Product_out_path: g.properties.Product_out_path, 109 }) 110 // boot.img -> kernel_release.txt 111 mctx.CreateModule(genrule.GenRuleFactory, &moduleCommonProperties{ 112 Name: proptools.StringPtr(g.kernelReleaseFileName()), 113 Enabled: proptools.BoolPtr(g.properties.ModulesEnabled), 114 }, &genRuleProperties{ 115 Defaults: []string{"extract_kernel_release_defaults"}, 116 Srcs: []string{":" + bootImage}, 117 }) 118 // boot.img -> payload.bin and payload_properties.txt 119 otaPayloadGen := g.moduleName() + "_ota_payload_gen" 120 mctx.CreateModule(rawImageOtaFactory, &moduleCommonProperties{ 121 Name: proptools.StringPtr(otaPayloadGen), 122 Enabled: proptools.BoolPtr(g.properties.ModulesEnabled), 123 }, &rawImageOtaProperties{ 124 Certificate: g.properties.Ota_payload_certificate, 125 Image_goals: []string{"boot:" + bootImage}, 126 }) 127 // copy payload.bin to <apex>/etc/ota 128 mctx.CreateModule(etc.PrebuiltEtcFactory, &moduleCommonProperties{ 129 Name: proptools.StringPtr(g.otaPayloadName()), 130 Enabled: proptools.BoolPtr(g.properties.ModulesEnabled), 131 }, &prebuiltEtcProperties{ 132 Src: proptools.StringPtr(":" + otaPayloadGen + "{" + payloadTag + "}"), 133 Filename_from_src: proptools.BoolPtr(true), 134 Relative_install_path: proptools.StringPtr("ota"), 135 Installable: proptools.BoolPtr(false), 136 }) 137 // copy payload_properties.txt to <apex>/etc/ota 138 mctx.CreateModule(etc.PrebuiltEtcFactory, &moduleCommonProperties{ 139 Name: proptools.StringPtr(g.otaPropertiesName()), 140 Enabled: proptools.BoolPtr(g.properties.ModulesEnabled), 141 }, &prebuiltEtcProperties{ 142 Src: proptools.StringPtr(":" + otaPayloadGen + "{" + payloadPropertiesTag + "}"), 143 Filename_from_src: proptools.BoolPtr(true), 144 Relative_install_path: proptools.StringPtr("ota"), 145 Installable: proptools.BoolPtr(false), 146 }) 147 // Create the APEX module with name g.moduleName(). Use factory APEX version. 148 g.createModulesRealApex(mctx, g.moduleName(), false, "") 149 150 // Create test APEX modules if gen_test. Test packages are not installable. 151 // Use hard-coded APEX version. 152 if proptools.Bool(g.properties.Gen_test) { 153 g.createModulesRealApex(mctx, g.moduleName()+"_test_high", true, "1000000000") 154 g.createModulesRealApex(mctx, g.moduleName()+"_test_low", true, "1") 155 } 156} 157 158func (g *gkiApex) createModulesRealApex(mctx android.LoadHookContext, 159 moduleName string, 160 isTestApex bool, 161 overrideApexVersion string) { 162 // Check kmi_version property against kernel_release.txt, then 163 // kernel_release.txt -> apex_manifest.json. 164 apexManifest := moduleName + "_apex_manifest" 165 mctx.CreateModule(genrule.GenRuleFactory, &moduleCommonProperties{ 166 Name: proptools.StringPtr(apexManifest), 167 Enabled: proptools.BoolPtr(g.properties.ModulesEnabled), 168 }, &genRuleProperties{ 169 Tools: []string{"build_gki_apex_manifest"}, 170 Out: []string{"apex_manifest.json"}, 171 Srcs: []string{":" + g.kernelReleaseFileName()}, 172 Cmd: proptools.StringPtr(g.createApexManifestCmd(overrideApexVersion)), 173 }) 174 175 // The APEX module. 176 177 // For test APEXes, if module is not enabled because KMI version is not 178 // compatible with the device, create a stub module that produces an empty 179 // file. This is so that the module name can be used in tests. 180 if isTestApex && !g.properties.ModulesEnabled { 181 mctx.CreateModule(genrule.GenRuleFactory, &moduleCommonProperties{ 182 Name: proptools.StringPtr(moduleName), 183 }, &genRuleProperties{ 184 Out: []string{moduleName + ".apex"}, 185 Cmd: proptools.StringPtr(`touch $(out)`), 186 }) 187 return 188 } 189 190 // For test APEXes, if module is enabled, build an apex_test with installable: false. 191 // For installed APEXes, build apex, respecting installable and enabled. 192 apexFactory := apex.BundleFactory 193 overrideInstallable := g.properties.Installable 194 if isTestApex { 195 apexFactory = testApexBundleFactory 196 overrideInstallable = proptools.BoolPtr(false) 197 } 198 199 mctx.CreateModule(apexFactory, &moduleCommonProperties{ 200 Name: proptools.StringPtr(moduleName), 201 Enabled: proptools.BoolPtr(g.properties.ModulesEnabled), 202 }, &apexProperties{ 203 Apex_name: g.properties.ApexName, 204 Manifest: proptools.StringPtr(":" + apexManifest), 205 Defaults: []string{"com.android.gki_defaults"}, 206 // A real GKI APEX cannot be preinstalled to the device. 207 // It can only be provided as an update. 208 Installable: overrideInstallable, 209 Prebuilts: []string{ 210 g.otaPayloadName(), 211 g.otaPropertiesName(), 212 }, 213 }) 214} 215 216// Original module name as specified by the "name" property. 217// This is also the APEX module name, i.e. the file name of the APEX file. 218// This is also the prefix of names of all generated modules that the phony module depends on. 219// e.g. com.android.gki.kmi_5_4_android12_0_boot 220func (g *gkiApex) moduleName() string { 221 return g.BaseModuleName() 222} 223 224// The appeared name of this gkiApex object. Exposed to Soong to avoid conflicting with 225// the generated APEX module with name moduleName(). 226// e.g. com.android.gki.kmi_5_4_android12_0_boot_all 227func (g *gkiApex) Name() string { 228 return g.moduleName() + "_all" 229} 230 231// Names for intermediate modules. 232func (g *gkiApex) kernelReleaseFileName() string { 233 return g.moduleName() + "_bootimage_kernel_release_file" 234} 235 236func (g *gkiApex) otaPayloadName() string { 237 return g.moduleName() + "_ota_payload" 238} 239 240func (g *gkiApex) otaPropertiesName() string { 241 return g.moduleName() + "_ota_payload_properties" 242} 243 244// If the boot image pointed at product_out_path has no rule to be generated, do not generate any 245// build rules for this gki_apex module. For example, if this gki_apex module is: 246// { name: "foo", product_out_path: "boot-bar.img" } 247// But there is no rule to generate boot-bar.img, then 248// - `m foo` fails with `unknown target 'foo'` 249// - checkbuild is still successful. The module foo doesn't even exist, so there 250// is no dependency on boot-bar.img 251// 252// There is a rule to generate "boot-foo.img" if "kernel-foo" is in BOARD_KERNEL_BINARIES. 253// As a special case, there is a rule to generate "boot.img" if BOARD_KERNEL_BINARIES is empty, 254// or "kernel" is in BOARD_KERNEL_BINARIES. 255func (g *gkiApex) bootImgHasRules(mctx android.EarlyModuleContext) bool { 256 kernelNames := mctx.DeviceConfig().BoardKernelBinaries() 257 if len(kernelNames) == 0 { 258 return proptools.String(g.properties.Product_out_path) == "boot.img" 259 } 260 for _, kernelName := range kernelNames { 261 validBootImagePath := strings.Replace(kernelName, "kernel", "boot", -1) + ".img" 262 if proptools.String(g.properties.Product_out_path) == validBootImagePath { 263 return true 264 } 265 } 266 return false 267} 268 269// Only generate if this module's kmi_version property is in BOARD_KERNEL_MODULE_INTERFACE_VERSIONS. 270// Otherwise, this board does not support GKI APEXes, so no modules are generated at all. 271// This function also avoids building invalid modules in checkbuild. For example, if these 272// gki_apex modules are defined: 273// gki_apex { name: "boot-kmi-1", kmi_version: "1", product_out_path: "boot.img" } 274// gki_apex { name: "boot-kmi-2", kmi_version: "2", product_out_path: "boot.img" } 275// But a given device's $PRODUCT_OUT/boot.img can only support at most one KMI version. 276// Disable some modules accordingly to make sure checkbuild still works. 277func boardDefinesKmiVersion(mctx android.EarlyModuleContext, kmiVersion string) bool { 278 kmiVersions := mctx.DeviceConfig().BoardKernelModuleInterfaceVersions() 279 return android.InList(kmiVersion, kmiVersions) 280} 281 282func (g *gkiApex) boardDefinesKmiVersion(mctx android.EarlyModuleContext) bool { 283 return boardDefinesKmiVersion(mctx, proptools.String(g.properties.Kmi_version)) 284} 285 286// Transform kernel release file in $(in) to KMI version + sublevel. 287// e.g. 5.4.42-android12-0 => name: "com.android.gki.kmi_5_4_android12_0", version: "300000000" 288// Finally, write APEX manifest JSON to $(out). 289func (g *gkiApex) createApexManifestCmd(apexVersion string) string { 290 ret := `$(location build_gki_apex_manifest) ` + 291 `--kmi_version "` + proptools.String(g.properties.Kmi_version) + `" ` + 292 `--apex_manifest $(out) --kernel_release_file $(in)` 293 // Override version field if set. 294 if apexVersion != "" { 295 ret += ` --apex_version ` + apexVersion 296 } 297 return ret 298} 299 300func (g *gkiApex) DepsMutator(ctx android.BottomUpMutatorContext) { 301} 302 303func (g *gkiApex) GenerateAndroidBuildActions(ctx android.ModuleContext) { 304} 305 306// OTA payload binary is signed with default_system_dev_certificate, which is equivalent to 307// DefaultAppCertificate(). 308func getDefaultCertificate(ctx android.EarlyModuleContext) string { 309 pem, _ := ctx.Config().DefaultAppCertificate(ctx) 310 return strings.TrimSuffix(pem.String(), filepath.Ext(pem.String())) 311} 312