// Copyright (C) 2024 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 filesystem import ( "fmt" "path/filepath" "regexp" "slices" "strconv" "strings" "android/soong/android" "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) func init() { android.RegisterModuleType("super_image", SuperImageFactory) } type superImage struct { android.ModuleBase properties SuperImageProperties partitionProps SuperImagePartitionNameProperties installDir android.InstallPath } type SuperImageProperties struct { // the size of the super partition Size *int64 // the block device where metadata for dynamic partitions is stored Metadata_device *string // the super partition block device list Block_devices []string // whether A/B updater is used Ab_update *bool // whether dynamic partitions is enabled on devices that were launched without this support Retrofit *bool // whether the output is a sparse image Sparse *bool // information about how partitions within the super partition are grouped together Partition_groups []PartitionGroupsInfo // Name of the system_other partition filesystem module. This module will be installed to // the "b" slot of the system partition in a/b partition builds. System_other_partition *string // whether dynamic partitions is used Use_dynamic_partitions *bool Virtual_ab struct { // whether virtual A/B seamless update is enabled Enable *bool // whether retrofitting virtual A/B seamless update is enabled Retrofit *bool // If set, device uses virtual A/B Compression Compression *bool // This value controls the compression algorithm used for VABC. // Valid options are defined in system/core/fs_mgr/libsnapshot/cow_writer.cpp // e.g. "none", "gz", "brotli" Compression_method *string // Specifies maximum bytes to be compressed at once during ota. Options: 4096, 8192, 16384, 32768, 65536, 131072, 262144. Compression_factor *int64 // Specifies COW version to be used by update_engine and libsnapshot. If this value is not // specified we default to COW version 2 in update_engine for backwards compatibility Cow_version *int64 } // Whether the super image will be disted in the update package Super_image_in_update_package *bool // Whether a super_empty.img should be created Create_super_empty *bool } type PartitionGroupsInfo struct { Name string GroupSize string PartitionList []string } type SuperImagePartitionNameProperties struct { // Name of the System partition filesystem module System_partition *string // Name of the System_ext partition filesystem module System_ext_partition *string // Name of the System_dlkm partition filesystem module System_dlkm_partition *string // Name of the System_other partition filesystem module System_other_partition *string // Name of the Product partition filesystem module Product_partition *string // Name of the Vendor partition filesystem module Vendor_partition *string // Name of the Vendor_dlkm partition filesystem module Vendor_dlkm_partition *string // Name of the Odm partition filesystem module Odm_partition *string // Name of the Odm_dlkm partition filesystem module Odm_dlkm_partition *string } type SuperImageInfo struct { // The built super.img file, which contains the sub-partitions SuperImage android.Path // Mapping from the sub-partition type to its re-exported FileSystemInfo providers from the // sub-partitions. SubImageInfo map[string]FilesystemInfo DynamicPartitionsInfo android.Path SuperEmptyImage android.Path AbUpdate bool } var SuperImageProvider = blueprint.NewProvider[SuperImageInfo]() func SuperImageFactory() android.Module { module := &superImage{} module.AddProperties(&module.properties, &module.partitionProps) android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon) return module } type superImageDepTagType struct { blueprint.BaseDependencyTag } var subImageDepTag superImageDepTagType type systemOtherDepTagType struct { blueprint.BaseDependencyTag } var systemOtherDepTag systemOtherDepTagType func (s *superImage) DepsMutator(ctx android.BottomUpMutatorContext) { addDependencyIfDefined := func(dep *string) { if dep != nil { ctx.AddDependency(ctx.Module(), subImageDepTag, proptools.String(dep)) } } addDependencyIfDefined(s.partitionProps.System_partition) addDependencyIfDefined(s.partitionProps.System_ext_partition) addDependencyIfDefined(s.partitionProps.System_dlkm_partition) addDependencyIfDefined(s.partitionProps.System_other_partition) addDependencyIfDefined(s.partitionProps.Product_partition) addDependencyIfDefined(s.partitionProps.Vendor_partition) addDependencyIfDefined(s.partitionProps.Vendor_dlkm_partition) addDependencyIfDefined(s.partitionProps.Odm_partition) addDependencyIfDefined(s.partitionProps.Odm_dlkm_partition) if s.properties.System_other_partition != nil { ctx.AddDependency(ctx.Module(), systemOtherDepTag, *s.properties.System_other_partition) } } func (s *superImage) GenerateAndroidBuildActions(ctx android.ModuleContext) { miscInfo, deps, subImageInfos := s.buildMiscInfo(ctx, false) builder := android.NewRuleBuilder(pctx, ctx) output := android.PathForModuleOut(ctx, s.installFileName()) lpMake := ctx.Config().HostToolPath(ctx, "lpmake") lpMakeDir := filepath.Dir(lpMake.String()) deps = append(deps, lpMake) builder.Command().Textf("PATH=%s:\\$PATH", lpMakeDir). BuiltTool("build_super_image"). Text("-v"). Input(miscInfo). Implicits(deps). Output(output) builder.Build("build_super_image", fmt.Sprintf("Creating super image %s", s.BaseModuleName())) var superEmptyImage android.WritablePath if proptools.Bool(s.properties.Create_super_empty) { superEmptyImageBuilder := android.NewRuleBuilder(pctx, ctx) superEmptyImage = android.PathForModuleOut(ctx, "super_empty.img") superEmptyMiscInfo, superEmptyDeps, _ := s.buildMiscInfo(ctx, true) if superEmptyDeps != nil { ctx.ModuleErrorf("TODO: Handle additional deps when building super_empty.img") } superEmptyImageBuilder.Command().Textf("PATH=%s:\\$PATH", lpMakeDir). BuiltTool("build_super_image"). Text("-v"). Input(superEmptyMiscInfo). Implicit(lpMake). Output(superEmptyImage) superEmptyImageBuilder.Build("build_super_empty_image", fmt.Sprintf("Creating super empty image %s", s.BaseModuleName())) } android.SetProvider(ctx, SuperImageProvider, SuperImageInfo{ SuperImage: output, SubImageInfo: subImageInfos, DynamicPartitionsInfo: s.generateDynamicPartitionsInfo(ctx), SuperEmptyImage: superEmptyImage, AbUpdate: proptools.Bool(s.properties.Ab_update), }) ctx.SetOutputFiles([]android.Path{output}, "") ctx.CheckbuildFile(output) buildComplianceMetadata(ctx, subImageDepTag) } func (s *superImage) installFileName() string { return "super.img" } func (s *superImage) buildMiscInfo(ctx android.ModuleContext, superEmpty bool) (android.Path, android.Paths, map[string]FilesystemInfo) { var miscInfoString strings.Builder partitionList := s.dumpDynamicPartitionInfo(ctx, &miscInfoString) addStr := func(name string, value string) { miscInfoString.WriteString(name) miscInfoString.WriteRune('=') miscInfoString.WriteString(value) miscInfoString.WriteRune('\n') } addStr("ab_update", strconv.FormatBool(proptools.Bool(s.properties.Ab_update))) if superEmpty { miscInfo := android.PathForModuleOut(ctx, "misc_info_super_empty.txt") android.WriteFileRule(ctx, miscInfo, miscInfoString.String()) return miscInfo, nil, nil } subImageInfo := make(map[string]FilesystemInfo) var deps android.Paths missingPartitionErrorMessage := "" handleSubPartition := func(partitionType string, name *string) { if proptools.String(name) == "" { missingPartitionErrorMessage += fmt.Sprintf("%s image listed in partition groups, but its module was not specified. ", partitionType) return } mod := ctx.GetDirectDepWithTag(*name, subImageDepTag) if mod == nil { ctx.ModuleErrorf("Could not get dep %q", *name) return } info, ok := android.OtherModuleProvider(ctx, mod, FilesystemProvider) if !ok { ctx.ModuleErrorf("Expected dep %q to provide FilesystemInfo", *name) return } addStr(partitionType+"_image", info.Output.String()) deps = append(deps, info.Output) if _, ok := subImageInfo[partitionType]; ok { ctx.ModuleErrorf("Already set subimageInfo for %q", partitionType) } subImageInfo[partitionType] = info } // Build partitionToImagePath, because system partition may need system_other // partition image path for _, p := range partitionList { switch p { case "system": handleSubPartition("system", s.partitionProps.System_partition) case "system_dlkm": handleSubPartition("system_dlkm", s.partitionProps.System_dlkm_partition) case "system_ext": handleSubPartition("system_ext", s.partitionProps.System_ext_partition) case "product": handleSubPartition("product", s.partitionProps.Product_partition) case "vendor": handleSubPartition("vendor", s.partitionProps.Vendor_partition) case "vendor_dlkm": handleSubPartition("vendor_dlkm", s.partitionProps.Vendor_dlkm_partition) case "odm": handleSubPartition("odm", s.partitionProps.Odm_partition) case "odm_dlkm": handleSubPartition("odm_dlkm", s.partitionProps.Odm_dlkm_partition) default: ctx.ModuleErrorf("partition %q is not a super image supported partition", p) } } if s.properties.System_other_partition != nil { if !slices.Contains(partitionList, "system") { ctx.PropertyErrorf("system_other_partition", "Must have a system partition to use a system_other partition") } systemOther := ctx.GetDirectDepProxyWithTag(*s.properties.System_other_partition, systemOtherDepTag) systemOtherFiles := android.OutputFilesForModule(ctx, systemOther, "") if len(systemOtherFiles) != 1 { ctx.PropertyErrorf("system_other_partition", "Expected 1 output file from module %q", *&s.properties.System_other_partition) } else { handleSubPartition("system_other", s.partitionProps.System_other_partition) } } // Delay the error message until execution time because on aosp-main-future-without-vendor, // BUILDING_VENDOR_IMAGE is false so we don't get the vendor image, but it's still listed in // BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST. missingPartitionErrorMessageFile := android.PathForModuleOut(ctx, "missing_partition_error.txt") if missingPartitionErrorMessage != "" { ctx.Build(pctx, android.BuildParams{ Rule: android.ErrorRule, Output: missingPartitionErrorMessageFile, Args: map[string]string{ "error": missingPartitionErrorMessage, }, }) } else { ctx.Build(pctx, android.BuildParams{ Rule: android.Touch, Output: missingPartitionErrorMessageFile, }) } miscInfo := android.PathForModuleOut(ctx, "misc_info.txt") android.WriteFileRule(ctx, miscInfo, miscInfoString.String(), missingPartitionErrorMessageFile) return miscInfo, deps, subImageInfo } func (s *superImage) dumpDynamicPartitionInfo(ctx android.ModuleContext, sb *strings.Builder) []string { addStr := func(name string, value string) { sb.WriteString(name) sb.WriteRune('=') sb.WriteString(value) sb.WriteRune('\n') } addStr("use_dynamic_partitions", strconv.FormatBool(proptools.Bool(s.properties.Use_dynamic_partitions))) if proptools.Bool(s.properties.Retrofit) { addStr("dynamic_partition_retrofit", "true") } addStr("lpmake", "lpmake") addStr("build_super_partition", "true") if proptools.Bool(s.properties.Create_super_empty) { addStr("build_super_empty_partition", "true") } addStr("super_metadata_device", proptools.String(s.properties.Metadata_device)) if len(s.properties.Block_devices) > 0 { addStr("super_block_devices", strings.Join(s.properties.Block_devices, " ")) } // TODO: In make, there's more complicated logic than just this surrounding super_*_device_size addStr("super_super_device_size", strconv.Itoa(proptools.Int(s.properties.Size))) var groups, partitionList []string for _, groupInfo := range s.properties.Partition_groups { groups = append(groups, groupInfo.Name) partitionList = append(partitionList, groupInfo.PartitionList...) } addStr("dynamic_partition_list", strings.Join(android.SortedUniqueStrings(partitionList), " ")) addStr("super_partition_groups", strings.Join(groups, " ")) initialPartitionListLen := len(partitionList) partitionList = android.SortedUniqueStrings(partitionList) if len(partitionList) != initialPartitionListLen { ctx.ModuleErrorf("Duplicate partitions found in the partition_groups property") } // Add Partition group info after adding `super_partition_groups` and `dynamic_partition_list` for _, groupInfo := range s.properties.Partition_groups { addStr("super_"+groupInfo.Name+"_group_size", groupInfo.GroupSize) addStr("super_"+groupInfo.Name+"_partition_list", strings.Join(groupInfo.PartitionList, " ")) } if proptools.Bool(s.properties.Super_image_in_update_package) { addStr("super_image_in_update_package", "true") } addStr("super_partition_size", strconv.Itoa(proptools.Int(s.properties.Size))) if proptools.Bool(s.properties.Virtual_ab.Enable) { addStr("virtual_ab", "true") if proptools.Bool(s.properties.Virtual_ab.Retrofit) { addStr("virtual_ab_retrofit", "true") } addStr("virtual_ab_compression", strconv.FormatBool(proptools.Bool(s.properties.Virtual_ab.Compression))) if s.properties.Virtual_ab.Compression_method != nil { matched, _ := regexp.MatchString("^[a-zA-Z0-9_-]+$", *s.properties.Virtual_ab.Compression_method) if !matched { ctx.PropertyErrorf("virtual_ab.compression_method", "compression_method cannot have special characters") } addStr("virtual_ab_compression_method", *s.properties.Virtual_ab.Compression_method) } if s.properties.Virtual_ab.Cow_version != nil { addStr("virtual_ab_cow_version", strconv.FormatInt(*s.properties.Virtual_ab.Cow_version, 10)) } if s.properties.Virtual_ab.Compression_factor != nil { addStr("virtual_ab_compression_factor", strconv.FormatInt(*s.properties.Virtual_ab.Compression_factor, 10)) } } else { if s.properties.Virtual_ab.Retrofit != nil { ctx.PropertyErrorf("virtual_ab.retrofit", "This property cannot be set when virtual_ab is disabled") } if s.properties.Virtual_ab.Compression != nil { ctx.PropertyErrorf("virtual_ab.compression", "This property cannot be set when virtual_ab is disabled") } if s.properties.Virtual_ab.Compression_method != nil { ctx.PropertyErrorf("virtual_ab.compression_method", "This property cannot be set when virtual_ab is disabled") } if s.properties.Virtual_ab.Compression_factor != nil { ctx.PropertyErrorf("virtual_ab.compression_factor", "This property cannot be set when virtual_ab is disabled") } } return partitionList } func (s *superImage) generateDynamicPartitionsInfo(ctx android.ModuleContext) android.Path { var contents strings.Builder s.dumpDynamicPartitionInfo(ctx, &contents) dynamicPartitionsInfo := android.PathForModuleOut(ctx, "dynamic_partitions_info.txt") android.WriteFileRuleVerbatim(ctx, dynamicPartitionsInfo, contents.String()) return dynamicPartitionsInfo }