• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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