1// Copyright 2020 Google Inc. All rights reserved. 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 android 16 17import ( 18 "fmt" 19 "path/filepath" 20 "strings" 21 22 "github.com/google/blueprint" 23 "github.com/google/blueprint/proptools" 24) 25 26// PackagingSpec abstracts a request to place a built artifact at a certain path in a package. A 27// package can be the traditional <partition>.img, but isn't limited to those. Other examples could 28// be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS 29// running on a VM), or a zip archive for some of the host tools. 30type PackagingSpec struct { 31 // Path relative to the root of the package 32 relPathInPackage string 33 34 // The path to the built artifact 35 srcPath Path 36 37 // If this is not empty, then relPathInPackage should be a symlink to this target. (Then 38 // srcPath is of course ignored.) 39 symlinkTarget string 40 41 // Whether relPathInPackage should be marked as executable or not 42 executable bool 43 44 effectiveLicenseFiles *Paths 45 46 partition string 47 48 // Whether this packaging spec represents an installation of the srcPath (i.e. this struct 49 // is created via InstallFile or InstallSymlink) or a simple packaging (i.e. created via 50 // PackageFile). 51 skipInstall bool 52 53 // Paths of aconfig files for the built artifact 54 aconfigPaths *Paths 55 56 // ArchType of the module which produced this packaging spec 57 archType ArchType 58} 59 60func (p *PackagingSpec) Equals(other *PackagingSpec) bool { 61 if other == nil { 62 return false 63 } 64 if p.relPathInPackage != other.relPathInPackage { 65 return false 66 } 67 if p.srcPath != other.srcPath || p.symlinkTarget != other.symlinkTarget { 68 return false 69 } 70 if p.executable != other.executable { 71 return false 72 } 73 if p.partition != other.partition { 74 return false 75 } 76 return true 77} 78 79// Get file name of installed package 80func (p *PackagingSpec) FileName() string { 81 if p.relPathInPackage != "" { 82 return filepath.Base(p.relPathInPackage) 83 } 84 85 return "" 86} 87 88// Path relative to the root of the package 89func (p *PackagingSpec) RelPathInPackage() string { 90 return p.relPathInPackage 91} 92 93func (p *PackagingSpec) SetRelPathInPackage(relPathInPackage string) { 94 p.relPathInPackage = relPathInPackage 95} 96 97func (p *PackagingSpec) EffectiveLicenseFiles() Paths { 98 if p.effectiveLicenseFiles == nil { 99 return Paths{} 100 } 101 return *p.effectiveLicenseFiles 102} 103 104func (p *PackagingSpec) Partition() string { 105 return p.partition 106} 107 108func (p *PackagingSpec) SkipInstall() bool { 109 return p.skipInstall 110} 111 112// Paths of aconfig files for the built artifact 113func (p *PackagingSpec) GetAconfigPaths() Paths { 114 return *p.aconfigPaths 115} 116 117type PackageModule interface { 118 Module 119 packagingBase() *PackagingBase 120 121 // AddDeps adds dependencies to the `deps` modules. This should be called in DepsMutator. 122 // When adding the dependencies, depTag is used as the tag. If `deps` modules are meant to 123 // be copied to a zip in CopyDepsToZip, `depTag` should implement PackagingItem marker interface. 124 AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag) 125 126 // GatherPackagingSpecs gathers PackagingSpecs of transitive dependencies. 127 GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec 128 GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec 129 130 // CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and 131 // returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions, 132 // followed by a build rule that unzips it and creates the final output (img, zip, tar.gz, 133 // etc.) from the extracted files 134 CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) []string 135} 136 137// PackagingBase provides basic functionality for packaging dependencies. A module is expected to 138// include this struct and call InitPackageModule. 139type PackagingBase struct { 140 properties PackagingProperties 141 142 // Allows this module to skip missing dependencies. In most cases, this is not required, but 143 // for rare cases like when there's a dependency to a module which exists in certain repo 144 // checkouts, this is needed. 145 IgnoreMissingDependencies bool 146 147 // If this is set to true by a module type inheriting PackagingBase, the deps property 148 // collects the first target only even with compile_multilib: true. 149 DepsCollectFirstTargetOnly bool 150} 151 152type depsProperty struct { 153 // Modules to include in this package 154 Deps proptools.Configurable[[]string] `android:"arch_variant"` 155} 156 157type packagingMultilibProperties struct { 158 First depsProperty `android:"arch_variant"` 159 Common depsProperty `android:"arch_variant"` 160 Lib32 depsProperty `android:"arch_variant"` 161 Lib64 depsProperty `android:"arch_variant"` 162 Both depsProperty `android:"arch_variant"` 163 Prefer32 depsProperty `android:"arch_variant"` 164} 165 166type packagingArchProperties struct { 167 Arm64 depsProperty 168 Arm depsProperty 169 X86_64 depsProperty 170 X86 depsProperty 171} 172 173type PackagingProperties struct { 174 Deps proptools.Configurable[[]string] `android:"arch_variant"` 175 Multilib packagingMultilibProperties `android:"arch_variant"` 176 Arch packagingArchProperties 177} 178 179func InitPackageModule(p PackageModule) { 180 base := p.packagingBase() 181 p.AddProperties(&base.properties) 182} 183 184func (p *PackagingBase) packagingBase() *PackagingBase { 185 return p 186} 187 188// From deps and multilib.*.deps, select the dependencies that are for the given arch deps is for 189// the current archicture when this module is not configured for multi target. When configured for 190// multi target, deps is selected for each of the targets and is NOT selected for the current 191// architecture which would be Common. 192func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []string { 193 get := func(prop proptools.Configurable[[]string]) []string { 194 return prop.GetOrDefault(ctx, nil) 195 } 196 197 var ret []string 198 if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 { 199 ret = append(ret, get(p.properties.Deps)...) 200 } else if arch.Multilib == "lib32" { 201 ret = append(ret, get(p.properties.Multilib.Lib32.Deps)...) 202 // multilib.prefer32.deps are added for lib32 only when they support 32-bit arch 203 for _, dep := range get(p.properties.Multilib.Prefer32.Deps) { 204 if checkIfOtherModuleSupportsLib32(ctx, dep) { 205 ret = append(ret, dep) 206 } 207 } 208 } else if arch.Multilib == "lib64" { 209 ret = append(ret, get(p.properties.Multilib.Lib64.Deps)...) 210 // multilib.prefer32.deps are added for lib64 only when they don't support 32-bit arch 211 for _, dep := range get(p.properties.Multilib.Prefer32.Deps) { 212 if !checkIfOtherModuleSupportsLib32(ctx, dep) { 213 ret = append(ret, dep) 214 } 215 } 216 } else if arch == Common { 217 ret = append(ret, get(p.properties.Multilib.Common.Deps)...) 218 } 219 220 if p.DepsCollectFirstTargetOnly { 221 if len(get(p.properties.Multilib.First.Deps)) > 0 { 222 ctx.PropertyErrorf("multilib.first.deps", "not supported. use \"deps\" instead") 223 } 224 for i, t := range ctx.MultiTargets() { 225 if t.Arch.ArchType == arch { 226 ret = append(ret, get(p.properties.Multilib.Both.Deps)...) 227 if i == 0 { 228 ret = append(ret, get(p.properties.Deps)...) 229 } 230 } 231 } 232 } else { 233 if len(get(p.properties.Multilib.Both.Deps)) > 0 { 234 ctx.PropertyErrorf("multilib.both.deps", "not supported. use \"deps\" instead") 235 } 236 for i, t := range ctx.MultiTargets() { 237 if t.Arch.ArchType == arch { 238 ret = append(ret, get(p.properties.Deps)...) 239 if i == 0 { 240 ret = append(ret, get(p.properties.Multilib.First.Deps)...) 241 } 242 } 243 } 244 } 245 246 if ctx.Arch().ArchType == Common { 247 switch arch { 248 case Arm64: 249 ret = append(ret, get(p.properties.Arch.Arm64.Deps)...) 250 case Arm: 251 ret = append(ret, get(p.properties.Arch.Arm.Deps)...) 252 case X86_64: 253 ret = append(ret, get(p.properties.Arch.X86_64.Deps)...) 254 case X86: 255 ret = append(ret, get(p.properties.Arch.X86.Deps)...) 256 } 257 } 258 259 return FirstUniqueStrings(ret) 260} 261 262func getSupportedTargets(ctx BaseModuleContext) []Target { 263 var ret []Target 264 // The current and the common OS targets are always supported 265 ret = append(ret, ctx.Target()) 266 if ctx.Arch().ArchType != Common { 267 ret = append(ret, Target{Os: ctx.Os(), Arch: Arch{ArchType: Common}}) 268 } 269 // If this module is configured for multi targets, those should be supported as well 270 ret = append(ret, ctx.MultiTargets()...) 271 return ret 272} 273 274// getLib32Target returns the 32-bit target from the list of targets this module supports. If this 275// module doesn't support 32-bit target, nil is returned. 276func getLib32Target(ctx BaseModuleContext) *Target { 277 for _, t := range getSupportedTargets(ctx) { 278 if t.Arch.ArchType.Multilib == "lib32" { 279 return &t 280 } 281 } 282 return nil 283} 284 285// checkIfOtherModuleSUpportsLib32 returns true if 32-bit variant of dep exists. 286func checkIfOtherModuleSupportsLib32(ctx BaseModuleContext, dep string) bool { 287 t := getLib32Target(ctx) 288 if t == nil { 289 // This packaging module doesn't support 32bit. No point of checking if dep supports 32-bit 290 // or not. 291 return false 292 } 293 return ctx.OtherModuleFarDependencyVariantExists(t.Variations(), dep) 294} 295 296// PackagingItem is a marker interface for dependency tags. 297// Direct dependencies with a tag implementing PackagingItem are packaged in CopyDepsToZip(). 298type PackagingItem interface { 299 // IsPackagingItem returns true if the dep is to be packaged 300 IsPackagingItem() bool 301} 302 303// DepTag provides default implementation of PackagingItem interface. 304// PackagingBase-derived modules can define their own dependency tag by embedding this, which 305// can be passed to AddDeps() or AddDependencies(). 306type PackagingItemAlwaysDepTag struct { 307} 308 309// IsPackagingItem returns true if the dep is to be packaged 310func (PackagingItemAlwaysDepTag) IsPackagingItem() bool { 311 return true 312} 313 314// See PackageModule.AddDeps 315func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag) { 316 for _, t := range getSupportedTargets(ctx) { 317 for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) { 318 if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) { 319 continue 320 } 321 ctx.AddFarVariationDependencies(t.Variations(), depTag, dep) 322 } 323 } 324} 325 326func (p *PackagingBase) GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec { 327 m := make(map[string]PackagingSpec) 328 329 var arches []ArchType 330 for _, target := range getSupportedTargets(ctx) { 331 arches = append(arches, target.Arch.ArchType) 332 } 333 334 // filter out packaging specs for unsupported architecture 335 filterArch := func(ps PackagingSpec) bool { 336 for _, arch := range arches { 337 if arch == ps.archType { 338 return true 339 } 340 } 341 return false 342 } 343 344 ctx.VisitDirectDeps(func(child Module) { 345 if pi, ok := ctx.OtherModuleDependencyTag(child).(PackagingItem); !ok || !pi.IsPackagingItem() { 346 return 347 } 348 for _, ps := range child.TransitivePackagingSpecs() { 349 if !filterArch(ps) { 350 continue 351 } 352 353 if filter != nil { 354 if !filter(ps) { 355 continue 356 } 357 } 358 dstPath := ps.relPathInPackage 359 if existingPs, ok := m[dstPath]; ok { 360 if !existingPs.Equals(&ps) { 361 ctx.ModuleErrorf("packaging conflict at %v:\n%v\n%v", dstPath, existingPs, ps) 362 } 363 continue 364 } 365 366 m[dstPath] = ps 367 } 368 }) 369 return m 370} 371 372// See PackageModule.GatherPackagingSpecs 373func (p *PackagingBase) GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec { 374 return p.GatherPackagingSpecsWithFilter(ctx, nil) 375} 376 377// CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec 378// entries into the specified directory. 379func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir WritablePath) (entries []string) { 380 if len(specs) == 0 { 381 return entries 382 } 383 seenDir := make(map[string]bool) 384 preparerPath := PathForModuleOut(ctx, "preparer.sh") 385 cmd := builder.Command().Tool(preparerPath) 386 var sb strings.Builder 387 sb.WriteString("set -e\n") 388 for _, k := range SortedKeys(specs) { 389 ps := specs[k] 390 destPath := filepath.Join(dir.String(), ps.relPathInPackage) 391 destDir := filepath.Dir(destPath) 392 entries = append(entries, ps.relPathInPackage) 393 if _, ok := seenDir[destDir]; !ok { 394 seenDir[destDir] = true 395 sb.WriteString(fmt.Sprintf("mkdir -p %s\n", destDir)) 396 } 397 if ps.symlinkTarget == "" { 398 cmd.Implicit(ps.srcPath) 399 sb.WriteString(fmt.Sprintf("cp %s %s\n", ps.srcPath, destPath)) 400 } else { 401 sb.WriteString(fmt.Sprintf("ln -sf %s %s\n", ps.symlinkTarget, destPath)) 402 } 403 if ps.executable { 404 sb.WriteString(fmt.Sprintf("chmod a+x %s\n", destPath)) 405 } 406 } 407 408 WriteExecutableFileRuleVerbatim(ctx, preparerPath, sb.String()) 409 410 return entries 411} 412 413// See PackageModule.CopyDepsToZip 414func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) (entries []string) { 415 builder := NewRuleBuilder(pctx, ctx) 416 417 dir := PathForModuleOut(ctx, ".zip") 418 builder.Command().Text("rm").Flag("-rf").Text(dir.String()) 419 builder.Command().Text("mkdir").Flag("-p").Text(dir.String()) 420 entries = p.CopySpecsToDir(ctx, builder, specs, dir) 421 422 builder.Command(). 423 BuiltTool("soong_zip"). 424 FlagWithOutput("-o ", zipOut). 425 FlagWithArg("-C ", dir.String()). 426 Flag("-L 0"). // no compression because this will be unzipped soon 427 FlagWithArg("-D ", dir.String()) 428 builder.Command().Text("rm").Flag("-rf").Text(dir.String()) 429 430 builder.Build("zip_deps", fmt.Sprintf("Zipping deps for %s", ctx.ModuleName())) 431 return entries 432} 433