blob: cf7e125a099ab79beea247358da9b2eeac23ee13 [file] [log] [blame] [edit]
// 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
}