From 925aa09ea1525030be3bbdd00842a8787f879fd2 Mon Sep 17 00:00:00 2001 From: Rob Seymour Date: Tue, 10 Aug 2021 20:42:03 +0000 Subject: [PATCH] Add module based host-tools snapshot Add new module: host_snapshot { name: "host-snapshot" deps: [ (list of host tools) ], ... } Package host tools using android.PackagingBase to capture host tools and transitive packaging data. Add JSON meta data to snapshot that allows snapshot to be installed via development/vendor_snapshot/update.py Add support to generate a fake host snapshot of all host modules that is used to detect required modules via development/vendor_snapshot/update.py. Bug: 192556798 Bug: 194799048 Bug: 192896149 Test: m HOST_FAKE_SNAPSHOT_ENABLE=true host-fake-snapshot dist -- check snapshot exists in dist Change-Id: I849c4db801cd858408f6fe6a3ce69262a23a5be9 --- android/config.go | 4 + android/defs.go | 9 ++ android/variable.go | 1 + cc/cc.go | 3 + cc/vendor_snapshot.go | 8 +- etc/prebuilt_etc.go | 9 +- python/python.go | 15 ++- sh/sh_binary.go | 6 + snapshot/Android.bp | 8 ++ snapshot/host_fake_snapshot.go | 149 ++++++++++++++++++++++ snapshot/host_snapshot.go | 221 +++++++++++++++++++++++++++++++++ snapshot/host_test.go | 170 +++++++++++++++++++++++++ snapshot/snapshot_base.go | 16 +++ snapshot/test.go | 24 ++++ snapshot/util.go | 19 +++ 15 files changed, 642 insertions(+), 20 deletions(-) create mode 100644 snapshot/host_fake_snapshot.go create mode 100644 snapshot/host_snapshot.go create mode 100644 snapshot/host_test.go create mode 100644 snapshot/test.go diff --git a/android/config.go b/android/config.go index b3b8f3cc2..fb16db1e2 100644 --- a/android/config.go +++ b/android/config.go @@ -1517,6 +1517,10 @@ func (c *deviceConfig) RecoverySnapshotDirsIncludedMap() map[string]bool { c.config.productVariables.RecoverySnapshotDirsIncluded) } +func (c *deviceConfig) HostFakeSnapshotEnabled() bool { + return c.config.productVariables.HostFakeSnapshotEnabled +} + func (c *deviceConfig) ShippingApiLevel() ApiLevel { if c.config.productVariables.ShippingApiLevel == nil { return NoneApiLevel diff --git a/android/defs.go b/android/defs.go index b3ff376d1..c8e2e9b3c 100644 --- a/android/defs.go +++ b/android/defs.go @@ -188,6 +188,15 @@ func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) buildWriteFileRule(ctx, outputFile, content) } +func CatFileRule(ctx BuilderContext, paths Paths, outputFile WritablePath) { + ctx.Build(pctx, BuildParams{ + Rule: Cat, + Inputs: paths, + Output: outputFile, + Description: "combine files to " + outputFile.Base(), + }) +} + // shellUnescape reverses proptools.ShellEscape func shellUnescape(s string) string { // Remove leading and trailing quotes if present diff --git a/android/variable.go b/android/variable.go index 0fb907810..51e90ce45 100644 --- a/android/variable.go +++ b/android/variable.go @@ -321,6 +321,7 @@ type productVariables struct { VendorSnapshotDirsExcluded []string `json:",omitempty"` RecoverySnapshotDirsExcluded []string `json:",omitempty"` RecoverySnapshotDirsIncluded []string `json:",omitempty"` + HostFakeSnapshotEnabled bool `json:",omitempty"` BoardVendorSepolicyDirs []string `json:",omitempty"` BoardOdmSepolicyDirs []string `json:",omitempty"` diff --git a/cc/cc.go b/cc/cc.go index 39d89e595..34d8b6f15 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -30,6 +30,7 @@ import ( "android/soong/android" "android/soong/cc/config" "android/soong/genrule" + "android/soong/snapshot" ) func init() { @@ -3386,6 +3387,8 @@ func (c *Module) AlwaysRequiresPlatformApexVariant() bool { return c.IsStubs() || c.Target().NativeBridge == android.NativeBridgeEnabled } +var _ snapshot.RelativeInstallPath = (*Module)(nil) + // // Defaults // diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go index ba4d79fcf..8a17e2e54 100644 --- a/cc/vendor_snapshot.go +++ b/cc/vendor_snapshot.go @@ -132,12 +132,9 @@ func isSnapshotAware(cfg android.DeviceConfig, m LinkableInterface, inProprietar return false } -// This is to be saved as .json files, which is for development/vendor_snapshot/update.py. -// These flags become Android.bp snapshot module properties. +// Extend the snapshot.SnapshotJsonFlags to include cc specific fields. type snapshotJsonFlags struct { - ModuleName string `json:",omitempty"` - RelativeInstallPath string `json:",omitempty"` - + snapshot.SnapshotJsonFlags // library flags ExportedDirs []string `json:",omitempty"` ExportedSystemDirs []string `json:",omitempty"` @@ -154,7 +151,6 @@ type snapshotJsonFlags struct { SharedLibs []string `json:",omitempty"` StaticLibs []string `json:",omitempty"` RuntimeLibs []string `json:",omitempty"` - Required []string `json:",omitempty"` // extra config files InitRc []string `json:",omitempty"` diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go index 8aeb0dde7..5979ca748 100644 --- a/etc/prebuilt_etc.go +++ b/etc/prebuilt_etc.go @@ -519,13 +519,6 @@ func PrebuiltRFSAFactory() android.Module { return module } -// Flags to be included in the snapshot -type snapshotJsonFlags struct { - ModuleName string `json:",omitempty"` - Filename string `json:",omitempty"` - RelativeInstallPath string `json:",omitempty"` -} - // Copy file into the snapshot func copyFile(ctx android.SingletonContext, path android.Path, out string, fake bool) android.OutputPath { if fake { @@ -612,7 +605,7 @@ func generatePrebuiltSnapshot(s snapshot.SnapshotSingleton, ctx android.Singleto snapshotLibOut := filepath.Join(snapshotArchDir, targetArch, "etc", m.BaseModuleName()) snapshotOutputs = append(snapshotOutputs, copyFile(ctx, m.OutputFile(), snapshotLibOut, s.Fake)) - prop := snapshotJsonFlags{} + prop := snapshot.SnapshotJsonFlags{} propOut := snapshotLibOut + ".json" prop.ModuleName = m.BaseModuleName() if m.subdirProperties.Relative_install_path != nil { diff --git a/python/python.go b/python/python.go index 0f5b7880e..2a848ca9c 100644 --- a/python/python.go +++ b/python/python.go @@ -310,13 +310,16 @@ func versionSplitMutator() func(android.BottomUpMutatorContext) { // HostToolPath returns a path if appropriate such that this module can be used as a host tool, // fulfilling HostToolProvider interface. func (p *Module) HostToolPath() android.OptionalPath { - if p.installer == nil { - // python_library is just meta module, and doesn't have any installer. - return android.OptionalPath{} + if p.installer != nil { + if bin, ok := p.installer.(*binaryDecorator); ok { + // TODO: This should only be set when building host binaries -- tests built for device would be + // setting this incorrectly. + return android.OptionalPathForPath(bin.path) + } } - // TODO: This should only be set when building host binaries -- tests built for device would be - // setting this incorrectly. - return android.OptionalPathForPath(p.installer.(*binaryDecorator).path) + + return android.OptionalPath{} + } // OutputFiles returns output files based on given tag, returns an error if tag is unsupported. diff --git a/sh/sh_binary.go b/sh/sh_binary.go index 16474282e..b1f77d074 100644 --- a/sh/sh_binary.go +++ b/sh/sh_binary.go @@ -26,6 +26,7 @@ import ( "android/soong/android" "android/soong/bazel" "android/soong/cc" + "android/soong/snapshot" "android/soong/tradefed" ) @@ -195,6 +196,9 @@ func (s *ShBinary) SubDir() string { return proptools.String(s.properties.Sub_dir) } +func (s *ShBinary) RelativeInstallPath() string { + return s.SubDir() +} func (s *ShBinary) Installable() bool { return s.properties.Installable == nil || proptools.Bool(s.properties.Installable) } @@ -566,3 +570,5 @@ func (m *bazelShBinary) Name() string { func (m *bazelShBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {} var Bool = proptools.Bool + +var _ snapshot.RelativeInstallPath = (*ShBinary)(nil) diff --git a/snapshot/Android.bp b/snapshot/Android.bp index f17ac532a..335499306 100644 --- a/snapshot/Android.bp +++ b/snapshot/Android.bp @@ -11,12 +11,20 @@ bootstrap_go_package { "soong", "soong-android", ], + // Source file name convention is to include _snapshot as a + // file suffix for files that are generating snapshots. srcs: [ + "host_fake_snapshot.go", + "host_snapshot.go", "recovery_snapshot.go", "snapshot.go", "snapshot_base.go", "util.go", "vendor_snapshot.go", ], + testSrcs: [ + "host_test.go", + "test.go", + ], pluginFor: ["soong_build"], } diff --git a/snapshot/host_fake_snapshot.go b/snapshot/host_fake_snapshot.go new file mode 100644 index 000000000..6b4e12b05 --- /dev/null +++ b/snapshot/host_fake_snapshot.go @@ -0,0 +1,149 @@ +// Copyright 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 snapshot + +import ( + "encoding/json" + "path/filepath" + + "android/soong/android" +) + +// The host_snapshot module creates a snapshot of host tools to be used +// in a minimal source tree. In order to create the host_snapshot the +// user must explicitly list the modules to be included. The +// host-fake-snapshot, defined in this file, is a utility to help determine +// which host modules are being used in the minimal source tree. +// +// The host-fake-snapshot is designed to run in a full source tree and +// will result in a snapshot that contains an empty file for each host +// tool found in the tree. The fake snapshot is only used to determine +// the host modules that the minimal source tree depends on, hence the +// snapshot uses an empty file for each module and saves on having to +// actually build any tool to generate the snapshot. The fake snapshot +// is compatible with an actual host_snapshot and is installed into a +// minimal source tree via the development/vendor_snapshot/update.py +// script. +// +// After generating the fake snapshot and installing into the minimal +// source tree, the dependent modules are determined via the +// development/vendor_snapshot/update.py script (see script for more +// information). These modules are then used to define the actual +// host_snapshot to be used. This is a similar process to the other +// snapshots (vendor, recovery,...) +// +// Example +// +// Full source tree: +// 1/ Generate fake host snapshot +// +// Minimal source tree: +// 2/ Install the fake host snapshot +// 3/ List the host modules used from the snapshot +// 4/ Remove fake host snapshot +// +// Full source tree: +// 4/ Create host_snapshot with modules identified in step 3 +// +// Minimal source tree: +// 5/ Install host snapshot +// 6/ Build +// +// The host-fake-snapshot is a singleton module, that will be built +// if HOST_FAKE_SNAPSHOT_ENABLE=true. + +func init() { + registerHostSnapshotComponents(android.InitRegistrationContext) +} + +func registerHostSnapshotComponents(ctx android.RegistrationContext) { + ctx.RegisterSingletonType("host-fake-snapshot", HostToolsFakeAndroidSingleton) +} + +type hostFakeSingleton struct { + snapshotDir string + zipFile android.OptionalPath +} + +func (c *hostFakeSingleton) init() { + c.snapshotDir = "host-fake-snapshot" + +} +func HostToolsFakeAndroidSingleton() android.Singleton { + singleton := &hostFakeSingleton{} + singleton.init() + return singleton +} + +func (c *hostFakeSingleton) GenerateBuildActions(ctx android.SingletonContext) { + if !ctx.DeviceConfig().HostFakeSnapshotEnabled() { + return + } + // Find all host binary modules add 'fake' versions to snapshot + var outputs android.Paths + seen := make(map[string]bool) + var jsonData []SnapshotJsonFlags + ctx.VisitAllModules(func(module android.Module) { + if module.Target().Os != ctx.Config().BuildOSTarget.Os { + return + } + if module.Target().Arch.ArchType != ctx.Config().BuildOSTarget.Arch.ArchType { + return + } + + if android.IsModulePrebuilt(module) { + return + } + + if !module.Enabled() || module.IsHideFromMake() { + return + } + apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo) + if !apexInfo.IsForPlatform() { + return + } + path := hostBinToolPath(module) + if path.Valid() && path.String() != "" { + outFile := filepath.Join(c.snapshotDir, path.String()) + if !seen[outFile] { + seen[outFile] = true + outputs = append(outputs, WriteStringToFileRule(ctx, "", outFile)) + jsonData = append(jsonData, *hostBinJsonDesc(module)) + } + } + }) + + marsh, err := json.Marshal(jsonData) + if err != nil { + ctx.Errorf("host fake snapshot json marshal failure: %#v", err) + return + } + outputs = append(outputs, WriteStringToFileRule(ctx, string(marsh), filepath.Join(c.snapshotDir, "host_snapshot.json"))) + c.zipFile = zipSnapshot(ctx, c.snapshotDir, c.snapshotDir, outputs) + +} +func (c *hostFakeSingleton) MakeVars(ctx android.MakeVarsContext) { + if !c.zipFile.Valid() { + return + } + ctx.Phony( + "host-fake-snapshot", + c.zipFile.Path()) + + ctx.DistForGoal( + "host-fake-snapshot", + c.zipFile.Path()) + +} diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go new file mode 100644 index 000000000..2a25a00e5 --- /dev/null +++ b/snapshot/host_snapshot.go @@ -0,0 +1,221 @@ +// Copyright 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 snapshot + +import ( + "encoding/json" + "fmt" + "path/filepath" + "sort" + + "github.com/google/blueprint" + "github.com/google/blueprint/proptools" + + "android/soong/android" +) + +// +// The host_snapshot module creates a snapshot of the modules defined in +// the deps property. The modules within the deps property (host tools) +// are ones that return a valid path via HostToolPath() of the +// HostToolProvider. The created snapshot contains the binaries and any +// transitive PackagingSpecs of the included host tools, along with a JSON +// meta file. +// +// The snapshot is installed into a source tree via +// development/vendor_snapshot/update.py, the included modules are +// provided as preferred prebuilts. +// +// To determine which tools to include in the host snapshot see +// host_fake_snapshot.go. + +func init() { + registerHostBuildComponents(android.InitRegistrationContext) +} + +func registerHostBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("host_snapshot", hostSnapshotFactory) +} + +// Relative installation path +type RelativeInstallPath interface { + RelativeInstallPath() string +} + +type hostSnapshot struct { + android.ModuleBase + android.PackagingBase + + zipFile android.OptionalPath + installDir android.InstallPath +} + +func hostSnapshotFactory() android.Module { + module := &hostSnapshot{} + initHostToolsModule(module) + return module +} +func initHostToolsModule(module *hostSnapshot) { + android.InitPackageModule(module) + android.InitAndroidMultiTargetsArchModule(module, android.HostSupported, android.MultilibCommon) +} + +var dependencyTag = struct { + blueprint.BaseDependencyTag + android.InstallAlwaysNeededDependencyTag + android.PackagingItemAlwaysDepTag +}{} + +func (f *hostSnapshot) DepsMutator(ctx android.BottomUpMutatorContext) { + f.AddDeps(ctx, dependencyTag) +} +func (f *hostSnapshot) installFileName() string { + return f.Name() + ".zip" +} + +// Create zipfile with JSON description, notice files... for dependent modules +func (f *hostSnapshot) CreateMetaData(ctx android.ModuleContext, fileName string) android.OutputPath { + var jsonData []SnapshotJsonFlags + var metaPaths android.Paths + + metaZipFile := android.PathForModuleOut(ctx, fileName).OutputPath + + // Create JSON file based on the direct dependencies + ctx.VisitDirectDeps(func(dep android.Module) { + desc := hostBinJsonDesc(dep) + if desc != nil { + jsonData = append(jsonData, *desc) + } + if len(dep.EffectiveLicenseFiles()) > 0 { + noticeFile := android.PathForModuleOut(ctx, "NOTICE_FILES", dep.Name()+".txt").OutputPath + android.CatFileRule(ctx, dep.EffectiveLicenseFiles(), noticeFile) + metaPaths = append(metaPaths, noticeFile) + } + + }) + // Sort notice paths and json data for repeatble build + sort.Slice(jsonData, func(i, j int) bool { + return (jsonData[i].ModuleName < jsonData[j].ModuleName) + }) + sort.Slice(metaPaths, func(i, j int) bool { + return (metaPaths[i].String() < metaPaths[j].String()) + }) + + marsh, err := json.Marshal(jsonData) + if err != nil { + ctx.ModuleErrorf("host snapshot json marshal failure: %#v", err) + return android.OutputPath{} + } + + jsonZipFile := android.PathForModuleOut(ctx, "host_snapshot.json").OutputPath + metaPaths = append(metaPaths, jsonZipFile) + rspFile := android.PathForModuleOut(ctx, "host_snapshot.rsp").OutputPath + android.WriteFileRule(ctx, jsonZipFile, string(marsh)) + + builder := android.NewRuleBuilder(pctx, ctx) + + builder.Command(). + BuiltTool("soong_zip"). + FlagWithArg("-C ", android.PathForModuleOut(ctx).OutputPath.String()). + FlagWithOutput("-o ", metaZipFile). + FlagWithRspFileInputList("-r ", rspFile, metaPaths) + builder.Build("zip_meta", fmt.Sprintf("zipping meta data for %s", ctx.ModuleName())) + + return metaZipFile +} + +// Create the host tool zip file +func (f *hostSnapshot) GenerateAndroidBuildActions(ctx android.ModuleContext) { + // Create a zip file for the binaries, and a zip of the meta data, then merge zips + depsZipFile := android.PathForModuleOut(ctx, f.Name()+"_deps.zip").OutputPath + modsZipFile := android.PathForModuleOut(ctx, f.Name()+"_mods.zip").OutputPath + outputFile := android.PathForModuleOut(ctx, f.installFileName()).OutputPath + + f.installDir = android.PathForModuleInstall(ctx) + + f.CopyDepsToZip(ctx, depsZipFile) + + builder := android.NewRuleBuilder(pctx, ctx) + builder.Command(). + BuiltTool("zip2zip"). + FlagWithInput("-i ", depsZipFile). + FlagWithOutput("-o ", modsZipFile). + Text("**/*:" + proptools.ShellEscape(f.installDir.String())) + + metaZipFile := f.CreateMetaData(ctx, f.Name()+"_meta.zip") + + builder.Command(). + BuiltTool("merge_zips"). + Output(outputFile). + Input(metaZipFile). + Input(modsZipFile) + + builder.Build("manifest", fmt.Sprintf("Adding manifest %s", f.installFileName())) + zip := ctx.InstallFile(f.installDir, f.installFileName(), outputFile) + f.zipFile = android.OptionalPathForPath(zip) + +} + +// Implements android.AndroidMkEntriesProvider +func (f *hostSnapshot) AndroidMkEntries() []android.AndroidMkEntries { + if !f.zipFile.Valid() { + return []android.AndroidMkEntries{} + } + + return []android.AndroidMkEntries{android.AndroidMkEntries{ + Class: "ETC", + OutputFile: f.zipFile, + DistFiles: android.MakeDefaultDistFiles(f.zipFile.Path()), + ExtraEntries: []android.AndroidMkExtraEntriesFunc{ + func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { + entries.SetString("LOCAL_MODULE_PATH", f.installDir.ToMakePath().String()) + entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName()) + }, + }, + }} +} + +// Get host tools path and relative install string helpers +func hostBinToolPath(m android.Module) android.OptionalPath { + if provider, ok := m.(android.HostToolProvider); ok { + return provider.HostToolPath() + } + return android.OptionalPath{} + +} +func hostRelativePathString(m android.Module) string { + var outString string + if rel, ok := m.(RelativeInstallPath); ok { + outString = rel.RelativeInstallPath() + } + return outString +} + +// Create JSON description for given module, only create descriptions for binary modueles which +// provide a valid HostToolPath +func hostBinJsonDesc(m android.Module) *SnapshotJsonFlags { + path := hostBinToolPath(m) + relPath := hostRelativePathString(m) + if path.Valid() && path.String() != "" { + return &SnapshotJsonFlags{ + ModuleName: m.Name(), + ModuleStemName: filepath.Base(path.String()), + Filename: path.String(), + Required: append(m.HostRequiredModuleNames(), m.RequiredModuleNames()...), + RelativeInstallPath: relPath, + } + } + return nil +} diff --git a/snapshot/host_test.go b/snapshot/host_test.go new file mode 100644 index 000000000..ab9feddfb --- /dev/null +++ b/snapshot/host_test.go @@ -0,0 +1,170 @@ +// Copyright 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 snapshot + +import ( + "path/filepath" + "testing" + + "android/soong/android" +) + +// host_snapshot and host-fake-snapshot test functions + +type hostTestModule struct { + android.ModuleBase + props struct { + Deps []string + } +} + +func hostTestBinOut(bin string) string { + return filepath.Join("out", "bin", bin) +} + +func (c *hostTestModule) HostToolPath() android.OptionalPath { + return (android.OptionalPathForPath(android.PathForTesting(hostTestBinOut(c.Name())))) +} + +func hostTestModuleFactory() android.Module { + m := &hostTestModule{} + m.AddProperties(&m.props) + android.InitAndroidArchModule(m, android.HostSupported, android.MultilibFirst) + return m +} +func (m *hostTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { + builtFile := android.PathForModuleOut(ctx, m.Name()) + dir := ctx.Target().Arch.ArchType.Multilib + installDir := android.PathForModuleInstall(ctx, dir) + ctx.InstallFile(installDir, m.Name(), builtFile) +} + +// Common blueprint used for testing +var hostTestBp = ` + license_kind { + name: "test_notice", + conditions: ["notice"], + } + license { + name: "host_test_license", + visibility: ["//visibility:public"], + license_kinds: [ + "test_notice" + ], + license_text: [ + "NOTICE", + ], + } + component { + name: "foo", + deps: ["bar"], + } + component { + name: "bar", + licenses: ["host_test_license"], + } + ` + +var hostTestModBp = ` + host_snapshot { + name: "test-host-snapshot", + deps: [ + "foo", + ], + } + ` + +var prepareForHostTest = android.GroupFixturePreparers( + android.PrepareForTestWithAndroidBuildComponents, + android.PrepareForTestWithLicenses, + android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { + ctx.RegisterModuleType("component", hostTestModuleFactory) + }), +) + +// Prepare for host_snapshot test +var prepareForHostModTest = android.GroupFixturePreparers( + prepareForHostTest, + android.FixtureWithRootAndroidBp(hostTestBp+hostTestModBp), + android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { + registerHostBuildComponents(ctx) + }), +) + +// Prepare for fake host snapshot test disabled +var prepareForFakeHostTest = android.GroupFixturePreparers( + prepareForHostTest, + android.FixtureWithRootAndroidBp(hostTestBp), + android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { + registerHostSnapshotComponents(ctx) + }), +) + +// Prepare for fake host snapshot test enabled +var prepareForFakeHostTestEnabled = android.GroupFixturePreparers( + prepareForFakeHostTest, + android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { + variables.HostFakeSnapshotEnabled = true + }), +) + +// Validate that a hostSnapshot object is created containing zip files and JSON file +// content of zip file is not validated as this is done by PackagingSpecs +func TestHostSnapshot(t *testing.T) { + result := prepareForHostModTest.RunTest(t) + t.Helper() + ctx := result.TestContext.ModuleForTests("test-host-snapshot", result.Config.BuildOS.String()+"_common") + mod := ctx.Module().(*hostSnapshot) + if ctx.MaybeOutput("host_snapshot.json").Rule == nil { + t.Error("Manifest file not found") + } + zips := []string{"_deps.zip", "_mods.zip", ".zip"} + + for _, zip := range zips { + zFile := mod.Name() + zip + if ctx.MaybeOutput(zFile).Rule == nil { + t.Error("Zip file ", zFile, "not found") + } + + } +} + +// Validate fake host snapshot contains binary modules as well as the JSON meta file +func TestFakeHostSnapshotEnable(t *testing.T) { + result := prepareForFakeHostTestEnabled.RunTest(t) + t.Helper() + bins := []string{"foo", "bar"} + ctx := result.TestContext.SingletonForTests("host-fake-snapshot") + if ctx.MaybeOutput(filepath.Join("host-fake-snapshot", "host_snapshot.json")).Rule == nil { + t.Error("Manifest file not found") + } + for _, bin := range bins { + if ctx.MaybeOutput(filepath.Join("host-fake-snapshot", hostTestBinOut(bin))).Rule == nil { + t.Error("Binary file ", bin, "not found") + } + + } +} + +// Validate not fake host snapshot if HostFakeSnapshotEnabled has not been set to true +func TestFakeHostSnapshotDisable(t *testing.T) { + result := prepareForFakeHostTest.RunTest(t) + t.Helper() + ctx := result.TestContext.SingletonForTests("host-fake-snapshot") + if len(ctx.AllOutputs()) != 0 { + t.Error("Fake host snapshot not empty when disabled") + } + +} diff --git a/snapshot/snapshot_base.go b/snapshot/snapshot_base.go index de93f3eb0..79d3cf6f3 100644 --- a/snapshot/snapshot_base.go +++ b/snapshot/snapshot_base.go @@ -102,3 +102,19 @@ func isDirectoryExcluded(dir string, excludedMap directoryMap, includedMap direc return isDirectoryExcluded(filepath.Dir(dir), excludedMap, includedMap) } } + +// This is to be saved as .json files, which is for development/vendor_snapshot/update.py. +// These flags become Android.bp snapshot module properties. +// +// Attributes are optional and will be populated based on each module's need. +// Common attributes are defined here, languages may extend this struct to add +// additional attributes. +type SnapshotJsonFlags struct { + ModuleName string `json:",omitempty"` + RelativeInstallPath string `json:",omitempty"` + Filename string `json:",omitempty"` + ModuleStemName string `json:",omitempty"` + + // dependencies + Required []string `json:",omitempty"` +} diff --git a/snapshot/test.go b/snapshot/test.go new file mode 100644 index 000000000..346af2b43 --- /dev/null +++ b/snapshot/test.go @@ -0,0 +1,24 @@ +// Copyright 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 snapshot + +import ( + "os" + "testing" +) + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} diff --git a/snapshot/util.go b/snapshot/util.go index 2297dfc2b..f44705227 100644 --- a/snapshot/util.go +++ b/snapshot/util.go @@ -34,3 +34,22 @@ func CopyFileRule(pctx android.PackageContext, ctx android.SingletonContext, pat }) return outPath } + +// zip snapshot +func zipSnapshot(ctx android.SingletonContext, dir string, baseName string, snapshotOutputs android.Paths) android.OptionalPath { + zipPath := android.PathForOutput( + ctx, dir, baseName+".zip") + + zipRule := android.NewRuleBuilder(pctx, ctx) + rspFile := android.PathForOutput( + ctx, dir, baseName+"_list.rsp") + + zipRule.Command(). + BuiltTool("soong_zip"). + FlagWithOutput("-o ", zipPath). + FlagWithArg("-C ", android.PathForOutput(ctx, dir).String()). + FlagWithRspFileInputList("-r ", rspFile, snapshotOutputs) + + zipRule.Build(zipPath.String(), baseName+" snapshot "+zipPath.String()) + return android.OptionalPathForPath(zipPath) +}