diff options
| -rw-r--r-- | android/Android.bp | 1 | ||||
| -rw-r--r-- | android/packaging.go | 171 | ||||
| -rw-r--r-- | android/packaging_test.go | 188 |
3 files changed, 360 insertions, 0 deletions
diff --git a/android/Android.bp b/android/Android.bp index c6f01fe9a..e7eff2e72 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -76,6 +76,7 @@ bootstrap_go_package { "neverallow_test.go", "onceper_test.go", "package_test.go", + "packaging_test.go", "path_properties_test.go", "paths_test.go", "prebuilt_test.go", diff --git a/android/packaging.go b/android/packaging.go index 8d0de9e8b..512e4ba53 100644 --- a/android/packaging.go +++ b/android/packaging.go @@ -14,6 +14,13 @@ package android +import ( + "fmt" + "path/filepath" + + "github.com/google/blueprint" +) + // PackagingSpec abstracts a request to place a built artifact at a certain path in a package. // A package can be the traditional <partition>.img, but isn't limited to those. Other examples could // be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS running @@ -32,3 +39,167 @@ type PackagingSpec struct { // Whether relPathInPackage should be marked as executable or not executable bool } + +type PackageModule interface { + Module + packagingBase() *PackagingBase + + // AddDeps adds dependencies to the `deps` modules. This should be called in DepsMutator. + AddDeps(ctx BottomUpMutatorContext) + + // CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and + // returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions, + // followed by a build rule that unzips it and creates the final output (img, zip, tar.gz, + // etc.) from the extracted files + CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) []string +} + +// PackagingBase provides basic functionality for packaging dependencies. A module is expected to +// include this struct and call InitPackageModule. +type PackagingBase struct { + properties PackagingProperties + + // Allows this module to skip missing dependencies. In most cases, this + // is not required, but for rare cases like when there's a dependency + // to a module which exists in certain repo checkouts, this is needed. + IgnoreMissingDependencies bool +} + +type depsProperty struct { + // Modules to include in this package + Deps []string `android:"arch_variant"` +} + +type packagingMultilibProperties struct { + First depsProperty `android:"arch_variant"` + Common depsProperty `android:"arch_variant"` + Lib32 depsProperty `android:"arch_variant"` + Lib64 depsProperty `android:"arch_variant"` +} + +type PackagingProperties struct { + Deps []string `android:"arch_variant"` + Multilib packagingMultilibProperties `android:"arch_variant"` +} + +type packagingDependencyTag struct{ blueprint.BaseDependencyTag } + +var depTag = packagingDependencyTag{} + +func InitPackageModule(p PackageModule) { + base := p.packagingBase() + p.AddProperties(&base.properties) +} + +func (p *PackagingBase) packagingBase() *PackagingBase { + return p +} + +// From deps and multilib.*.deps, select the dependencies that are for the given arch +// deps is for the current archicture when this module is not configured for multi target. +// When configured for multi target, deps is selected for each of the targets and is NOT +// selected for the current architecture which would be Common. +func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []string { + var ret []string + if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 { + ret = append(ret, p.properties.Deps...) + } else if arch.Multilib == "lib32" { + ret = append(ret, p.properties.Multilib.Lib32.Deps...) + } else if arch.Multilib == "lib64" { + ret = append(ret, p.properties.Multilib.Lib64.Deps...) + } else if arch == Common { + ret = append(ret, p.properties.Multilib.Common.Deps...) + } + for i, t := range ctx.MultiTargets() { + if t.Arch.ArchType == arch { + ret = append(ret, p.properties.Deps...) + if i == 0 { + ret = append(ret, p.properties.Multilib.First.Deps...) + } + } + } + return FirstUniqueStrings(ret) +} + +func (p *PackagingBase) getSupportedTargets(ctx BaseModuleContext) []Target { + var ret []Target + // The current and the common OS targets are always supported + ret = append(ret, ctx.Target()) + if ctx.Arch().ArchType != Common { + ret = append(ret, Target{Os: ctx.Os(), Arch: Arch{ArchType: Common}}) + } + // If this module is configured for multi targets, those should be supported as well + ret = append(ret, ctx.MultiTargets()...) + return ret +} + +// See PackageModule.AddDeps +func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext) { + for _, t := range p.getSupportedTargets(ctx) { + for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) { + if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) { + continue + } + ctx.AddFarVariationDependencies(t.Variations(), depTag, dep) + } + } +} + +// See PackageModule.CopyDepsToZip +func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) (entries []string) { + var supportedArches []string + for _, t := range p.getSupportedTargets(ctx) { + supportedArches = append(supportedArches, t.Arch.ArchType.String()) + } + m := make(map[string]PackagingSpec) + ctx.WalkDeps(func(child Module, parent Module) bool { + // Don't track modules with unsupported arch + // TODO(jiyong): remove this when aosp/1501613 lands. + if !InList(child.Target().Arch.ArchType.String(), supportedArches) { + return false + } + for _, ps := range child.PackagingSpecs() { + if _, ok := m[ps.relPathInPackage]; !ok { + m[ps.relPathInPackage] = ps + } + } + return true + }) + + builder := NewRuleBuilder() + + dir := PathForModuleOut(ctx, ".zip").OutputPath + builder.Command().Text("rm").Flag("-rf").Text(dir.String()) + builder.Command().Text("mkdir").Flag("-p").Text(dir.String()) + + seenDir := make(map[string]bool) + for _, k := range SortedStringKeys(m) { + ps := m[k] + destPath := dir.Join(ctx, ps.relPathInPackage).String() + destDir := filepath.Dir(destPath) + entries = append(entries, ps.relPathInPackage) + if _, ok := seenDir[destDir]; !ok { + seenDir[destDir] = true + builder.Command().Text("mkdir").Flag("-p").Text(destDir) + } + if ps.symlinkTarget == "" { + builder.Command().Text("cp").Input(ps.srcPath).Text(destPath) + } else { + builder.Command().Text("ln").Flag("-sf").Text(ps.symlinkTarget).Text(destPath) + } + if ps.executable { + builder.Command().Text("chmod").Flag("a+x").Text(destPath) + } + } + + builder.Command(). + BuiltTool(ctx, "soong_zip"). + FlagWithOutput("-o ", zipOut). + FlagWithArg("-C ", dir.String()). + Flag("-L 0"). // no compression because this will be unzipped soon + FlagWithArg("-D ", dir.String()) + builder.Command().Text("rm").Flag("-rf").Text(dir.String()) + + builder.Build(pctx, ctx, "zip_deps", fmt.Sprintf("Zipping deps for %s", ctx.ModuleName())) + return entries +} diff --git a/android/packaging_test.go b/android/packaging_test.go new file mode 100644 index 000000000..7710c7fd8 --- /dev/null +++ b/android/packaging_test.go @@ -0,0 +1,188 @@ +// Copyright 2020 Google Inc. All rights reserved. +// +// 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 android + +import ( + "reflect" + "testing" +) + +// Module to be packaged +type componentTestModule struct { + ModuleBase + props struct { + Deps []string + } +} + +func componentTestModuleFactory() Module { + m := &componentTestModule{} + m.AddProperties(&m.props) + InitAndroidArchModule(m, HostAndDeviceSupported, MultilibBoth) + return m +} + +func (m *componentTestModule) DepsMutator(ctx BottomUpMutatorContext) { + ctx.AddDependency(ctx.Module(), nil, m.props.Deps...) +} + +func (m *componentTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { + builtFile := PathForModuleOut(ctx, m.Name()) + dir := ctx.Target().Arch.ArchType.Multilib + installDir := PathForModuleInstall(ctx, dir) + ctx.InstallFile(installDir, m.Name(), builtFile) +} + +// Module that itself is a package +type packageTestModule struct { + ModuleBase + PackagingBase + + entries []string +} + +func packageTestModuleFactory() Module { + module := &packageTestModule{} + InitPackageModule(module) + InitAndroidMultiTargetsArchModule(module, DeviceSupported, MultilibCommon) + return module +} + +func (m *packageTestModule) DepsMutator(ctx BottomUpMutatorContext) { + m.AddDeps(ctx) +} + +func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { + zipFile := PathForModuleOut(ctx, "myzip.zip").OutputPath + m.entries = m.CopyDepsToZip(ctx, zipFile) +} + +func runPackagingTest(t *testing.T, bp string, expected []string) { + t.Helper() + + config := TestArchConfig(buildDir, nil, bp, nil) + + ctx := NewTestArchContext(config) + ctx.RegisterModuleType("component", componentTestModuleFactory) + ctx.RegisterModuleType("package_module", packageTestModuleFactory) + ctx.Register() + + _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) + FailIfErrored(t, errs) + _, errs = ctx.PrepareBuildActions(config) + FailIfErrored(t, errs) + + p := ctx.ModuleForTests("package", "android_common").Module().(*packageTestModule) + actual := p.entries + actual = SortedUniqueStrings(actual) + expected = SortedUniqueStrings(expected) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("\ngot: %v\nexpected: %v\n", actual, expected) + } +} + +func TestPackagingBase(t *testing.T) { + runPackagingTest(t, + ` + component { + name: "foo", + } + + package_module { + name: "package", + deps: ["foo"], + } + `, []string{"lib64/foo"}) + + runPackagingTest(t, + ` + component { + name: "foo", + deps: ["bar"], + } + + component { + name: "bar", + } + + package_module { + name: "package", + deps: ["foo"], + } + `, []string{"lib64/foo", "lib64/bar"}) + + runPackagingTest(t, + ` + component { + name: "foo", + deps: ["bar"], + } + + component { + name: "bar", + } + + package_module { + name: "package", + deps: ["foo"], + compile_multilib: "both", + } + `, []string{"lib32/foo", "lib32/bar", "lib64/foo", "lib64/bar"}) + + runPackagingTest(t, + ` + component { + name: "foo", + } + + component { + name: "bar", + compile_multilib: "32", + } + + package_module { + name: "package", + deps: ["foo"], + multilib: { + lib32: { + deps: ["bar"], + }, + }, + compile_multilib: "both", + } + `, []string{"lib32/foo", "lib32/bar", "lib64/foo"}) + + runPackagingTest(t, + ` + component { + name: "foo", + } + + component { + name: "bar", + } + + package_module { + name: "package", + deps: ["foo"], + multilib: { + first: { + deps: ["bar"], + }, + }, + compile_multilib: "both", + } + `, []string{"lib32/foo", "lib64/foo", "lib64/bar"}) +} |