• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2024 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 filesystem
16
17import (
18	"fmt"
19	"path/filepath"
20	"regexp"
21	"slices"
22	"strconv"
23	"strings"
24
25	"android/soong/android"
26
27	"github.com/google/blueprint"
28	"github.com/google/blueprint/proptools"
29)
30
31func init() {
32	android.RegisterModuleType("super_image", SuperImageFactory)
33}
34
35type superImage struct {
36	android.ModuleBase
37
38	properties     SuperImageProperties
39	partitionProps SuperImagePartitionNameProperties
40
41	installDir android.InstallPath
42}
43
44type SuperImageProperties struct {
45	// the size of the super partition
46	Size *int64
47	// the block device where metadata for dynamic partitions is stored
48	Metadata_device *string
49	// the super partition block device list
50	Block_devices []string
51	// whether A/B updater is used
52	Ab_update *bool
53	// whether dynamic partitions is enabled on devices that were launched without this support
54	Retrofit *bool
55	// whether the output is a sparse image
56	Sparse *bool
57	// information about how partitions within the super partition are grouped together
58	Partition_groups []PartitionGroupsInfo
59	// Name of the system_other partition filesystem module. This module will be installed to
60	// the "b" slot of the system partition in a/b partition builds.
61	System_other_partition *string
62	// whether dynamic partitions is used
63	Use_dynamic_partitions *bool
64	Virtual_ab             struct {
65		// whether virtual A/B seamless update is enabled
66		Enable *bool
67		// whether retrofitting virtual A/B seamless update is enabled
68		Retrofit *bool
69		// If set, device uses virtual A/B Compression
70		Compression *bool
71		// This value controls the compression algorithm used for VABC.
72		// Valid options are defined in system/core/fs_mgr/libsnapshot/cow_writer.cpp
73		// e.g. "none", "gz", "brotli"
74		Compression_method *string
75		// Specifies maximum bytes to be compressed at once during ota. Options: 4096, 8192, 16384, 32768, 65536, 131072, 262144.
76		Compression_factor *int64
77		// Specifies COW version to be used by update_engine and libsnapshot. If this value is not
78		// specified we default to COW version 2 in update_engine for backwards compatibility
79		Cow_version *int64
80	}
81	// Whether the super image will be disted in the update package
82	Super_image_in_update_package *bool
83	// Whether a super_empty.img should be created
84	Create_super_empty *bool
85}
86
87type PartitionGroupsInfo struct {
88	Name          string
89	GroupSize     string
90	PartitionList []string
91}
92
93type SuperImagePartitionNameProperties struct {
94	// Name of the System partition filesystem module
95	System_partition *string
96	// Name of the System_ext partition filesystem module
97	System_ext_partition *string
98	// Name of the System_dlkm partition filesystem module
99	System_dlkm_partition *string
100	// Name of the System_other partition filesystem module
101	System_other_partition *string
102	// Name of the Product partition filesystem module
103	Product_partition *string
104	// Name of the Vendor partition filesystem module
105	Vendor_partition *string
106	// Name of the Vendor_dlkm partition filesystem module
107	Vendor_dlkm_partition *string
108	// Name of the Odm partition filesystem module
109	Odm_partition *string
110	// Name of the Odm_dlkm partition filesystem module
111	Odm_dlkm_partition *string
112}
113
114type SuperImageInfo struct {
115	// The built super.img file, which contains the sub-partitions
116	SuperImage android.Path
117
118	// Mapping from the sub-partition type to its re-exported FileSystemInfo providers from the
119	// sub-partitions.
120	SubImageInfo map[string]FilesystemInfo
121
122	DynamicPartitionsInfo android.Path
123
124	SuperEmptyImage android.Path
125
126	AbUpdate bool
127}
128
129var SuperImageProvider = blueprint.NewProvider[SuperImageInfo]()
130
131func SuperImageFactory() android.Module {
132	module := &superImage{}
133	module.AddProperties(&module.properties, &module.partitionProps)
134	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
135	return module
136}
137
138type superImageDepTagType struct {
139	blueprint.BaseDependencyTag
140}
141
142var subImageDepTag superImageDepTagType
143
144type systemOtherDepTagType struct {
145	blueprint.BaseDependencyTag
146}
147
148var systemOtherDepTag systemOtherDepTagType
149
150func (s *superImage) DepsMutator(ctx android.BottomUpMutatorContext) {
151	addDependencyIfDefined := func(dep *string) {
152		if dep != nil {
153			ctx.AddDependency(ctx.Module(), subImageDepTag, proptools.String(dep))
154		}
155	}
156
157	addDependencyIfDefined(s.partitionProps.System_partition)
158	addDependencyIfDefined(s.partitionProps.System_ext_partition)
159	addDependencyIfDefined(s.partitionProps.System_dlkm_partition)
160	addDependencyIfDefined(s.partitionProps.System_other_partition)
161	addDependencyIfDefined(s.partitionProps.Product_partition)
162	addDependencyIfDefined(s.partitionProps.Vendor_partition)
163	addDependencyIfDefined(s.partitionProps.Vendor_dlkm_partition)
164	addDependencyIfDefined(s.partitionProps.Odm_partition)
165	addDependencyIfDefined(s.partitionProps.Odm_dlkm_partition)
166	if s.properties.System_other_partition != nil {
167		ctx.AddDependency(ctx.Module(), systemOtherDepTag, *s.properties.System_other_partition)
168	}
169}
170
171func (s *superImage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
172	miscInfo, deps, subImageInfos := s.buildMiscInfo(ctx, false)
173	builder := android.NewRuleBuilder(pctx, ctx)
174	output := android.PathForModuleOut(ctx, s.installFileName())
175	lpMake := ctx.Config().HostToolPath(ctx, "lpmake")
176	lpMakeDir := filepath.Dir(lpMake.String())
177	deps = append(deps, lpMake)
178	builder.Command().Textf("PATH=%s:\\$PATH", lpMakeDir).
179		BuiltTool("build_super_image").
180		Text("-v").
181		Input(miscInfo).
182		Implicits(deps).
183		Output(output)
184	builder.Build("build_super_image", fmt.Sprintf("Creating super image %s", s.BaseModuleName()))
185	var superEmptyImage android.WritablePath
186	if proptools.Bool(s.properties.Create_super_empty) {
187		superEmptyImageBuilder := android.NewRuleBuilder(pctx, ctx)
188		superEmptyImage = android.PathForModuleOut(ctx, "super_empty.img")
189		superEmptyMiscInfo, superEmptyDeps, _ := s.buildMiscInfo(ctx, true)
190		if superEmptyDeps != nil {
191			ctx.ModuleErrorf("TODO: Handle additional deps when building super_empty.img")
192		}
193		superEmptyImageBuilder.Command().Textf("PATH=%s:\\$PATH", lpMakeDir).
194			BuiltTool("build_super_image").
195			Text("-v").
196			Input(superEmptyMiscInfo).
197			Implicit(lpMake).
198			Output(superEmptyImage)
199		superEmptyImageBuilder.Build("build_super_empty_image", fmt.Sprintf("Creating super empty image %s", s.BaseModuleName()))
200	}
201	android.SetProvider(ctx, SuperImageProvider, SuperImageInfo{
202		SuperImage:            output,
203		SubImageInfo:          subImageInfos,
204		DynamicPartitionsInfo: s.generateDynamicPartitionsInfo(ctx),
205		SuperEmptyImage:       superEmptyImage,
206		AbUpdate:              proptools.Bool(s.properties.Ab_update),
207	})
208	ctx.SetOutputFiles([]android.Path{output}, "")
209	ctx.CheckbuildFile(output)
210
211	buildComplianceMetadata(ctx, subImageDepTag)
212}
213
214func (s *superImage) installFileName() string {
215	return "super.img"
216}
217
218func (s *superImage) buildMiscInfo(ctx android.ModuleContext, superEmpty bool) (android.Path, android.Paths, map[string]FilesystemInfo) {
219	var miscInfoString strings.Builder
220	partitionList := s.dumpDynamicPartitionInfo(ctx, &miscInfoString)
221	addStr := func(name string, value string) {
222		miscInfoString.WriteString(name)
223		miscInfoString.WriteRune('=')
224		miscInfoString.WriteString(value)
225		miscInfoString.WriteRune('\n')
226	}
227	addStr("ab_update", strconv.FormatBool(proptools.Bool(s.properties.Ab_update)))
228	if superEmpty {
229		miscInfo := android.PathForModuleOut(ctx, "misc_info_super_empty.txt")
230		android.WriteFileRule(ctx, miscInfo, miscInfoString.String())
231		return miscInfo, nil, nil
232	}
233
234	subImageInfo := make(map[string]FilesystemInfo)
235	var deps android.Paths
236
237	missingPartitionErrorMessage := ""
238	handleSubPartition := func(partitionType string, name *string) {
239		if proptools.String(name) == "" {
240			missingPartitionErrorMessage += fmt.Sprintf("%s image listed in partition groups, but its module was not specified. ", partitionType)
241			return
242		}
243		mod := ctx.GetDirectDepWithTag(*name, subImageDepTag)
244		if mod == nil {
245			ctx.ModuleErrorf("Could not get dep %q", *name)
246			return
247		}
248		info, ok := android.OtherModuleProvider(ctx, mod, FilesystemProvider)
249		if !ok {
250			ctx.ModuleErrorf("Expected dep %q to provide FilesystemInfo", *name)
251			return
252		}
253		addStr(partitionType+"_image", info.Output.String())
254		deps = append(deps, info.Output)
255		if _, ok := subImageInfo[partitionType]; ok {
256			ctx.ModuleErrorf("Already set subimageInfo for %q", partitionType)
257		}
258		subImageInfo[partitionType] = info
259	}
260
261	// Build partitionToImagePath, because system partition may need system_other
262	// partition image path
263	for _, p := range partitionList {
264		switch p {
265		case "system":
266			handleSubPartition("system", s.partitionProps.System_partition)
267		case "system_dlkm":
268			handleSubPartition("system_dlkm", s.partitionProps.System_dlkm_partition)
269		case "system_ext":
270			handleSubPartition("system_ext", s.partitionProps.System_ext_partition)
271		case "product":
272			handleSubPartition("product", s.partitionProps.Product_partition)
273		case "vendor":
274			handleSubPartition("vendor", s.partitionProps.Vendor_partition)
275		case "vendor_dlkm":
276			handleSubPartition("vendor_dlkm", s.partitionProps.Vendor_dlkm_partition)
277		case "odm":
278			handleSubPartition("odm", s.partitionProps.Odm_partition)
279		case "odm_dlkm":
280			handleSubPartition("odm_dlkm", s.partitionProps.Odm_dlkm_partition)
281		default:
282			ctx.ModuleErrorf("partition %q is not a super image supported partition", p)
283		}
284	}
285
286	if s.properties.System_other_partition != nil {
287		if !slices.Contains(partitionList, "system") {
288			ctx.PropertyErrorf("system_other_partition", "Must have a system partition to use a system_other partition")
289		}
290		systemOther := ctx.GetDirectDepProxyWithTag(*s.properties.System_other_partition, systemOtherDepTag)
291		systemOtherFiles := android.OutputFilesForModule(ctx, systemOther, "")
292		if len(systemOtherFiles) != 1 {
293			ctx.PropertyErrorf("system_other_partition", "Expected 1 output file from module %q", *&s.properties.System_other_partition)
294		} else {
295			handleSubPartition("system_other", s.partitionProps.System_other_partition)
296		}
297	}
298
299	// Delay the error message until execution time because on aosp-main-future-without-vendor,
300	// BUILDING_VENDOR_IMAGE is false so we don't get the vendor image, but it's still listed in
301	// BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST.
302	missingPartitionErrorMessageFile := android.PathForModuleOut(ctx, "missing_partition_error.txt")
303	if missingPartitionErrorMessage != "" {
304		ctx.Build(pctx, android.BuildParams{
305			Rule:   android.ErrorRule,
306			Output: missingPartitionErrorMessageFile,
307			Args: map[string]string{
308				"error": missingPartitionErrorMessage,
309			},
310		})
311	} else {
312		ctx.Build(pctx, android.BuildParams{
313			Rule:   android.Touch,
314			Output: missingPartitionErrorMessageFile,
315		})
316	}
317
318	miscInfo := android.PathForModuleOut(ctx, "misc_info.txt")
319	android.WriteFileRule(ctx, miscInfo, miscInfoString.String(), missingPartitionErrorMessageFile)
320	return miscInfo, deps, subImageInfo
321}
322
323func (s *superImage) dumpDynamicPartitionInfo(ctx android.ModuleContext, sb *strings.Builder) []string {
324	addStr := func(name string, value string) {
325		sb.WriteString(name)
326		sb.WriteRune('=')
327		sb.WriteString(value)
328		sb.WriteRune('\n')
329	}
330
331	addStr("use_dynamic_partitions", strconv.FormatBool(proptools.Bool(s.properties.Use_dynamic_partitions)))
332	if proptools.Bool(s.properties.Retrofit) {
333		addStr("dynamic_partition_retrofit", "true")
334	}
335	addStr("lpmake", "lpmake")
336	addStr("build_super_partition", "true")
337	if proptools.Bool(s.properties.Create_super_empty) {
338		addStr("build_super_empty_partition", "true")
339	}
340	addStr("super_metadata_device", proptools.String(s.properties.Metadata_device))
341	if len(s.properties.Block_devices) > 0 {
342		addStr("super_block_devices", strings.Join(s.properties.Block_devices, " "))
343	}
344	// TODO: In make, there's more complicated logic than just this surrounding super_*_device_size
345	addStr("super_super_device_size", strconv.Itoa(proptools.Int(s.properties.Size)))
346	var groups, partitionList []string
347	for _, groupInfo := range s.properties.Partition_groups {
348		groups = append(groups, groupInfo.Name)
349		partitionList = append(partitionList, groupInfo.PartitionList...)
350	}
351	addStr("dynamic_partition_list", strings.Join(android.SortedUniqueStrings(partitionList), " "))
352	addStr("super_partition_groups", strings.Join(groups, " "))
353	initialPartitionListLen := len(partitionList)
354	partitionList = android.SortedUniqueStrings(partitionList)
355	if len(partitionList) != initialPartitionListLen {
356		ctx.ModuleErrorf("Duplicate partitions found in the partition_groups property")
357	}
358	// Add Partition group info after adding `super_partition_groups` and `dynamic_partition_list`
359	for _, groupInfo := range s.properties.Partition_groups {
360		addStr("super_"+groupInfo.Name+"_group_size", groupInfo.GroupSize)
361		addStr("super_"+groupInfo.Name+"_partition_list", strings.Join(groupInfo.PartitionList, " "))
362	}
363
364	if proptools.Bool(s.properties.Super_image_in_update_package) {
365		addStr("super_image_in_update_package", "true")
366	}
367	addStr("super_partition_size", strconv.Itoa(proptools.Int(s.properties.Size)))
368
369	if proptools.Bool(s.properties.Virtual_ab.Enable) {
370		addStr("virtual_ab", "true")
371		if proptools.Bool(s.properties.Virtual_ab.Retrofit) {
372			addStr("virtual_ab_retrofit", "true")
373		}
374		addStr("virtual_ab_compression", strconv.FormatBool(proptools.Bool(s.properties.Virtual_ab.Compression)))
375		if s.properties.Virtual_ab.Compression_method != nil {
376			matched, _ := regexp.MatchString("^[a-zA-Z0-9_-]+$", *s.properties.Virtual_ab.Compression_method)
377			if !matched {
378				ctx.PropertyErrorf("virtual_ab.compression_method", "compression_method cannot have special characters")
379			}
380			addStr("virtual_ab_compression_method", *s.properties.Virtual_ab.Compression_method)
381		}
382		if s.properties.Virtual_ab.Cow_version != nil {
383			addStr("virtual_ab_cow_version", strconv.FormatInt(*s.properties.Virtual_ab.Cow_version, 10))
384		}
385		if s.properties.Virtual_ab.Compression_factor != nil {
386			addStr("virtual_ab_compression_factor", strconv.FormatInt(*s.properties.Virtual_ab.Compression_factor, 10))
387		}
388
389	} else {
390		if s.properties.Virtual_ab.Retrofit != nil {
391			ctx.PropertyErrorf("virtual_ab.retrofit", "This property cannot be set when virtual_ab is disabled")
392		}
393		if s.properties.Virtual_ab.Compression != nil {
394			ctx.PropertyErrorf("virtual_ab.compression", "This property cannot be set when virtual_ab is disabled")
395		}
396		if s.properties.Virtual_ab.Compression_method != nil {
397			ctx.PropertyErrorf("virtual_ab.compression_method", "This property cannot be set when virtual_ab is disabled")
398		}
399		if s.properties.Virtual_ab.Compression_factor != nil {
400			ctx.PropertyErrorf("virtual_ab.compression_factor", "This property cannot be set when virtual_ab is disabled")
401		}
402	}
403
404	return partitionList
405}
406
407func (s *superImage) generateDynamicPartitionsInfo(ctx android.ModuleContext) android.Path {
408	var contents strings.Builder
409	s.dumpDynamicPartitionInfo(ctx, &contents)
410	dynamicPartitionsInfo := android.PathForModuleOut(ctx, "dynamic_partitions_info.txt")
411	android.WriteFileRuleVerbatim(ctx, dynamicPartitionsInfo, contents.String())
412	return dynamicPartitionsInfo
413}
414