// Copyright (C) 2021 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" "strconv" "github.com/google/blueprint/proptools" "android/soong/android" ) func init() { android.RegisterModuleType("logical_partition", logicalPartitionFactory) } type logicalPartition struct { android.ModuleBase properties logicalPartitionProperties output android.OutputPath installDir android.InstallPath } type logicalPartitionProperties struct { // Set the name of the output. Defaults to .img. Stem *string // Total size of the logical partition. If set to "auto", total size is automatically // calcaulted as minimum. Size *string // List of partitions for default group. Default group has no size limit and automatically // minimized when creating an image. Default_group []partitionProperties // List of groups. A group defines a fixed sized region. It can host one or more logical // partitions and their total size is limited by the size of the group they are in. Groups []groupProperties // Whether the output is a sparse image or not. Default is false. Sparse *bool } type groupProperties struct { // Name of the partition group. Can't be "default"; use default_group instead. Name *string // Size of the partition group Size *string // List of logical partitions in this group Partitions []partitionProperties } type partitionProperties struct { // Name of the partition Name *string // Filesystem that is placed on the partition Filesystem *string `android:"path"` } // logical_partition is a partition image which has one or more logical partitions in it. func logicalPartitionFactory() android.Module { module := &logicalPartition{} module.AddProperties(&module.properties) android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst) return module } func (l *logicalPartition) DepsMutator(ctx android.BottomUpMutatorContext) { // do nothing } func (l *logicalPartition) installFileName() string { return proptools.StringDefault(l.properties.Stem, l.BaseModuleName()+".img") } func (l *logicalPartition) GenerateAndroidBuildActions(ctx android.ModuleContext) { builder := android.NewRuleBuilder(pctx, ctx) // Sparse the filesystem images and calculate their sizes sparseImages := make(map[string]android.OutputPath) sparseImageSizes := make(map[string]android.OutputPath) sparsePartitions := func(partitions []partitionProperties) { for _, part := range partitions { sparseImg, sizeTxt := sparseFilesystem(ctx, part, builder) pName := proptools.String(part.Name) sparseImages[pName] = sparseImg sparseImageSizes[pName] = sizeTxt } } for _, group := range l.properties.Groups { sparsePartitions(group.Partitions) } sparsePartitions(l.properties.Default_group) cmd := builder.Command().BuiltTool("lpmake") size := proptools.String(l.properties.Size) if size == "" { ctx.PropertyErrorf("size", "must be set") } else if _, err := strconv.Atoi(size); err != nil && size != "auto" { ctx.PropertyErrorf("size", `must be a number or "auto"`) } cmd.FlagWithArg("--device-size=", size) // TODO(jiyong): consider supporting A/B devices. Then we need to adjust num of slots. cmd.FlagWithArg("--metadata-slots=", "2") cmd.FlagWithArg("--metadata-size=", "65536") if proptools.Bool(l.properties.Sparse) { cmd.Flag("--sparse") } groupNames := make(map[string]bool) partitionNames := make(map[string]bool) addPartitionsToGroup := func(partitions []partitionProperties, gName string) { for _, part := range partitions { pName := proptools.String(part.Name) if pName == "" { ctx.PropertyErrorf("groups.partitions.name", "must be set") } if _, ok := partitionNames[pName]; ok { ctx.PropertyErrorf("groups.partitions.name", "already exists") } else { partitionNames[pName] = true } // Get size of the partition by reading the -size.txt file pSize := fmt.Sprintf("$(cat %s)", sparseImageSizes[pName]) cmd.FlagWithArg("--partition=", fmt.Sprintf("%s:readonly:%s:%s", pName, pSize, gName)) cmd.FlagWithInput("--image="+pName+"=", sparseImages[pName]) } } addPartitionsToGroup(l.properties.Default_group, "default") for _, group := range l.properties.Groups { gName := proptools.String(group.Name) if gName == "" { ctx.PropertyErrorf("groups.name", "must be set") } else if gName == "default" { ctx.PropertyErrorf("groups.name", `can't use "default" as a group name. Use default_group instead`) } if _, ok := groupNames[gName]; ok { ctx.PropertyErrorf("group.name", "already exists") } else { groupNames[gName] = true } gSize := proptools.String(group.Size) if gSize == "" { ctx.PropertyErrorf("groups.size", "must be set") } if _, err := strconv.Atoi(gSize); err != nil { ctx.PropertyErrorf("groups.size", "must be a number") } cmd.FlagWithArg("--group=", gName+":"+gSize) addPartitionsToGroup(group.Partitions, gName) } l.output = android.PathForModuleOut(ctx, l.installFileName()).OutputPath cmd.FlagWithOutput("--output=", l.output) builder.Build("build_logical_partition", fmt.Sprintf("Creating %s", l.BaseModuleName())) l.installDir = android.PathForModuleInstall(ctx, "etc") ctx.InstallFile(l.installDir, l.installFileName(), l.output) } // Add a rule that converts the filesystem for the given partition to the given rule builder. The // path to the sparse file and the text file having the size of the partition are returned. func sparseFilesystem(ctx android.ModuleContext, p partitionProperties, builder *android.RuleBuilder) (sparseImg android.OutputPath, sizeTxt android.OutputPath) { img := android.PathForModuleSrc(ctx, proptools.String(p.Filesystem)) name := proptools.String(p.Name) sparseImg = android.PathForModuleOut(ctx, name+".img").OutputPath builder.Temporary(sparseImg) builder.Command().BuiltTool("img2simg").Input(img).Output(sparseImg) sizeTxt = android.PathForModuleOut(ctx, name+"-size.txt").OutputPath builder.Temporary(sizeTxt) builder.Command().BuiltTool("sparse_img").Flag("--get_partition_size").Input(sparseImg). Text("| ").Text("tr").FlagWithArg("-d ", "'\n'"). Text("> ").Output(sizeTxt) return sparseImg, sizeTxt } var _ android.AndroidMkEntriesProvider = (*logicalPartition)(nil) // Implements android.AndroidMkEntriesProvider func (l *logicalPartition) AndroidMkEntries() []android.AndroidMkEntries { return []android.AndroidMkEntries{android.AndroidMkEntries{ Class: "ETC", OutputFile: android.OptionalPathForPath(l.output), ExtraEntries: []android.AndroidMkExtraEntriesFunc{ func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { entries.SetString("LOCAL_MODULE_PATH", l.installDir.String()) entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.installFileName()) }, }, }} } var _ Filesystem = (*logicalPartition)(nil) func (l *logicalPartition) OutputPath() android.Path { return l.output } func (l *logicalPartition) SignedOutputPath() android.Path { return nil // logical partition is not signed by itself } var _ android.OutputFileProducer = (*logicalPartition)(nil) // Implements android.OutputFileProducer func (l *logicalPartition) OutputFiles(tag string) (android.Paths, error) { if tag == "" { return []android.Path{l.output}, nil } return nil, fmt.Errorf("unsupported module reference tag %q", tag) }