diff options
Diffstat (limited to 'android')
-rw-r--r-- | android/androidmk.go | 363 | ||||
-rw-r--r-- | android/arch.go | 4 | ||||
-rw-r--r-- | android/bootjar.go | 117 | ||||
-rw-r--r-- | android/config.go | 8 | ||||
-rw-r--r-- | android/module.go | 17 | ||||
-rw-r--r-- | android/namespace.go | 12 | ||||
-rw-r--r-- | android/namespace_test.go | 43 | ||||
-rw-r--r-- | android/paths.go | 53 | ||||
-rw-r--r-- | android/paths_test.go | 5 | ||||
-rw-r--r-- | android/prebuilt.go | 53 | ||||
-rw-r--r-- | android/prebuilt_etc.go | 57 | ||||
-rw-r--r-- | android/prebuilt_etc_test.go | 46 | ||||
-rw-r--r-- | android/sh_binary.go | 75 | ||||
-rw-r--r-- | android/sh_binary_test.go | 79 | ||||
-rw-r--r-- | android/testing.go | 11 | ||||
-rw-r--r-- | android/variable.go | 21 |
16 files changed, 740 insertions, 224 deletions
diff --git a/android/androidmk.go b/android/androidmk.go index bd49e4c6..4a968f5e 100644 --- a/android/androidmk.go +++ b/android/androidmk.go @@ -32,6 +32,8 @@ func init() { RegisterSingletonType("androidmk", AndroidMkSingleton) } +// Deprecated: consider using AndroidMkEntriesProvider instead, especially if you're not going to +// use the Custom function. type AndroidMkDataProvider interface { AndroidMk() AndroidMkData BaseModuleName() string @@ -55,6 +57,195 @@ type AndroidMkData struct { type AndroidMkExtraFunc func(w io.Writer, outputFile Path) +// Allows modules to customize their Android*.mk output. +type AndroidMkEntriesProvider interface { + AndroidMkEntries() AndroidMkEntries + BaseModuleName() string +} + +type AndroidMkEntries struct { + Class string + SubName string + DistFile OptionalPath + OutputFile OptionalPath + Disabled bool + Include string + Required []string + + header bytes.Buffer + footer bytes.Buffer + + ExtraEntries []AndroidMkExtraEntriesFunc + + EntryMap map[string][]string + entryOrder []string +} + +type AndroidMkExtraEntriesFunc func(entries *AndroidMkEntries) + +func (a *AndroidMkEntries) SetString(name, value string) { + if _, ok := a.EntryMap[name]; !ok { + a.entryOrder = append(a.entryOrder, name) + } + a.EntryMap[name] = []string{value} +} + +func (a *AndroidMkEntries) SetBoolIfTrue(name string, flag bool) { + if flag { + if _, ok := a.EntryMap[name]; !ok { + a.entryOrder = append(a.entryOrder, name) + } + a.EntryMap[name] = []string{"true"} + } +} + +func (a *AndroidMkEntries) AddStrings(name string, value ...string) { + if len(value) == 0 { + return + } + if _, ok := a.EntryMap[name]; !ok { + a.entryOrder = append(a.entryOrder, name) + } + a.EntryMap[name] = append(a.EntryMap[name], value...) +} + +func (a *AndroidMkEntries) fillInEntries(config Config, bpPath string, mod blueprint.Module) { + a.EntryMap = make(map[string][]string) + amod := mod.(Module).base() + name := amod.BaseModuleName() + + if a.Include == "" { + a.Include = "$(BUILD_PREBUILT)" + } + a.Required = append(a.Required, amod.commonProperties.Required...) + + // Fill in the header part. + if len(amod.commonProperties.Dist.Targets) > 0 { + distFile := a.DistFile + if !distFile.Valid() { + distFile = a.OutputFile + } + if distFile.Valid() { + dest := filepath.Base(distFile.String()) + + if amod.commonProperties.Dist.Dest != nil { + var err error + if dest, err = validateSafePath(*amod.commonProperties.Dist.Dest); err != nil { + // This was checked in ModuleBase.GenerateBuildActions + panic(err) + } + } + + if amod.commonProperties.Dist.Suffix != nil { + ext := filepath.Ext(dest) + suffix := *amod.commonProperties.Dist.Suffix + dest = strings.TrimSuffix(dest, ext) + suffix + ext + } + + if amod.commonProperties.Dist.Dir != nil { + var err error + if dest, err = validateSafePath(*amod.commonProperties.Dist.Dir, dest); err != nil { + // This was checked in ModuleBase.GenerateBuildActions + panic(err) + } + } + + goals := strings.Join(amod.commonProperties.Dist.Targets, " ") + fmt.Fprintln(&a.header, ".PHONY:", goals) + fmt.Fprintf(&a.header, "$(call dist-for-goals,%s,%s:%s)\n", + goals, distFile.String(), dest) + } + } + + fmt.Fprintln(&a.header, "\ninclude $(CLEAR_VARS)") + + // Collect make variable assignment entries. + a.SetString("LOCAL_PATH", filepath.Dir(bpPath)) + a.SetString("LOCAL_MODULE", name+a.SubName) + a.SetString("LOCAL_MODULE_CLASS", a.Class) + a.SetString("LOCAL_PREBUILT_MODULE_FILE", a.OutputFile.String()) + a.AddStrings("LOCAL_REQUIRED_MODULES", a.Required...) + + archStr := amod.Arch().ArchType.String() + host := false + switch amod.Os().Class { + case Host: + // Make cannot identify LOCAL_MODULE_HOST_ARCH:= common. + if archStr != "common" { + a.SetString("LOCAL_MODULE_HOST_ARCH", archStr) + } + host = true + case HostCross: + // Make cannot identify LOCAL_MODULE_HOST_CROSS_ARCH:= common. + if archStr != "common" { + a.SetString("LOCAL_MODULE_HOST_CROSS_ARCH", archStr) + } + host = true + case Device: + // Make cannot identify LOCAL_MODULE_TARGET_ARCH:= common. + if archStr != "common" { + a.SetString("LOCAL_MODULE_TARGET_ARCH", archStr) + } + + a.AddStrings("LOCAL_INIT_RC", amod.commonProperties.Init_rc...) + a.AddStrings("LOCAL_VINTF_FRAGMENTS", amod.commonProperties.Vintf_fragments...) + a.SetBoolIfTrue("LOCAL_PROPRIETARY_MODULE", Bool(amod.commonProperties.Proprietary)) + if Bool(amod.commonProperties.Vendor) || Bool(amod.commonProperties.Soc_specific) { + a.SetString("LOCAL_VENDOR_MODULE", "true") + } + a.SetBoolIfTrue("LOCAL_ODM_MODULE", Bool(amod.commonProperties.Device_specific)) + a.SetBoolIfTrue("LOCAL_PRODUCT_MODULE", Bool(amod.commonProperties.Product_specific)) + a.SetBoolIfTrue("LOCAL_PRODUCT_SERVICES_MODULE", Bool(amod.commonProperties.Product_services_specific)) + if amod.commonProperties.Owner != nil { + a.SetString("LOCAL_MODULE_OWNER", *amod.commonProperties.Owner) + } + } + + if amod.noticeFile.Valid() { + a.SetString("LOCAL_NOTICE_FILE", amod.noticeFile.String()) + } + + if host { + makeOs := amod.Os().String() + if amod.Os() == Linux || amod.Os() == LinuxBionic { + makeOs = "linux" + } + a.SetString("LOCAL_MODULE_HOST_OS", makeOs) + a.SetString("LOCAL_IS_HOST_MODULE", "true") + } + + prefix := "" + if amod.ArchSpecific() { + switch amod.Os().Class { + case Host: + prefix = "HOST_" + case HostCross: + prefix = "HOST_CROSS_" + case Device: + prefix = "TARGET_" + + } + + if amod.Arch().ArchType != config.Targets[amod.Os()][0].Arch.ArchType { + prefix = "2ND_" + prefix + } + } + for _, extra := range a.ExtraEntries { + extra(a) + } + + // Write to footer. + fmt.Fprintln(&a.footer, "include "+a.Include) +} + +func (a *AndroidMkEntries) write(w io.Writer) { + w.Write(a.header.Bytes()) + for _, name := range a.entryOrder { + fmt.Fprintln(w, name+" := "+strings.Join(a.EntryMap[name], " ")) + } + w.Write(a.footer.Bytes()) +} + func AndroidMkSingleton() Singleton { return &androidMkSingleton{} } @@ -157,6 +348,8 @@ func translateAndroidMkModule(ctx SingletonContext, w io.Writer, mod blueprint.M return translateAndroidModule(ctx, w, mod, x) case bootstrap.GoBinaryTool: return translateGoBinaryModule(ctx, w, mod, x) + case AndroidMkEntriesProvider: + return translateAndroidMkEntriesModule(ctx, w, mod, x) default: return nil } @@ -176,35 +369,30 @@ func translateGoBinaryModule(ctx SingletonContext, w io.Writer, mod blueprint.Mo func translateAndroidModule(ctx SingletonContext, w io.Writer, mod blueprint.Module, provider AndroidMkDataProvider) error { - name := provider.BaseModuleName() amod := mod.(Module).base() - - if !amod.Enabled() { - return nil - } - - if amod.commonProperties.SkipInstall { - return nil - } - - if !amod.commonProperties.NamespaceExportedToMake { - // TODO(jeffrygaston) do we want to validate that there are no modules being - // exported to Kati that depend on this module? + if shouldSkipAndroidMkProcessing(amod) { return nil } data := provider.AndroidMk() - if data.Include == "" { data.Include = "$(BUILD_PREBUILT)" } - data.Required = append(data.Required, amod.commonProperties.Required...) - - // Make does not understand LinuxBionic - if amod.Os() == LinuxBionic { - return nil + // Get the preamble content through AndroidMkEntries logic. + entries := AndroidMkEntries{ + Class: data.Class, + SubName: data.SubName, + DistFile: data.DistFile, + OutputFile: data.OutputFile, + Disabled: data.Disabled, + Include: data.Include, + Required: data.Required, } + entries.fillInEntries(ctx.Config(), ctx.BlueprintFile(mod), mod) + // preamble doesn't need the footer content. + entries.footer = bytes.Buffer{} + entries.write(&data.preamble) prefix := "" if amod.ArchSpecific() { @@ -223,115 +411,7 @@ func translateAndroidModule(ctx SingletonContext, w io.Writer, mod blueprint.Mod } } - if len(amod.commonProperties.Dist.Targets) > 0 { - distFile := data.DistFile - if !distFile.Valid() { - distFile = data.OutputFile - } - if distFile.Valid() { - dest := filepath.Base(distFile.String()) - - if amod.commonProperties.Dist.Dest != nil { - var err error - dest, err = validateSafePath(*amod.commonProperties.Dist.Dest) - if err != nil { - // This was checked in ModuleBase.GenerateBuildActions - panic(err) - } - } - - if amod.commonProperties.Dist.Suffix != nil { - ext := filepath.Ext(dest) - suffix := *amod.commonProperties.Dist.Suffix - dest = strings.TrimSuffix(dest, ext) + suffix + ext - } - - if amod.commonProperties.Dist.Dir != nil { - var err error - dest, err = validateSafePath(*amod.commonProperties.Dist.Dir, dest) - if err != nil { - // This was checked in ModuleBase.GenerateBuildActions - panic(err) - } - } - - goals := strings.Join(amod.commonProperties.Dist.Targets, " ") - fmt.Fprintln(&data.preamble, ".PHONY:", goals) - fmt.Fprintf(&data.preamble, "$(call dist-for-goals,%s,%s:%s)\n", - goals, distFile.String(), dest) - } - } - - fmt.Fprintln(&data.preamble, "\ninclude $(CLEAR_VARS)") - fmt.Fprintln(&data.preamble, "LOCAL_PATH :=", filepath.Dir(ctx.BlueprintFile(mod))) - fmt.Fprintln(&data.preamble, "LOCAL_MODULE :=", name+data.SubName) - fmt.Fprintln(&data.preamble, "LOCAL_MODULE_CLASS :=", data.Class) - fmt.Fprintln(&data.preamble, "LOCAL_PREBUILT_MODULE_FILE :=", data.OutputFile.String()) - - if len(data.Required) > 0 { - fmt.Fprintln(&data.preamble, "LOCAL_REQUIRED_MODULES := "+strings.Join(data.Required, " ")) - } - - archStr := amod.Arch().ArchType.String() - host := false - switch amod.Os().Class { - case Host: - // Make cannot identify LOCAL_MODULE_HOST_ARCH:= common. - if archStr != "common" { - fmt.Fprintln(&data.preamble, "LOCAL_MODULE_HOST_ARCH :=", archStr) - } - host = true - case HostCross: - // Make cannot identify LOCAL_MODULE_HOST_CROSS_ARCH:= common. - if archStr != "common" { - fmt.Fprintln(&data.preamble, "LOCAL_MODULE_HOST_CROSS_ARCH :=", archStr) - } - host = true - case Device: - // Make cannot identify LOCAL_MODULE_TARGET_ARCH:= common. - if archStr != "common" { - fmt.Fprintln(&data.preamble, "LOCAL_MODULE_TARGET_ARCH :=", archStr) - } - - if len(amod.commonProperties.Init_rc) > 0 { - fmt.Fprintln(&data.preamble, "LOCAL_INIT_RC := ", strings.Join(amod.commonProperties.Init_rc, " ")) - } - if len(amod.commonProperties.Vintf_fragments) > 0 { - fmt.Fprintln(&data.preamble, "LOCAL_VINTF_FRAGMENTS := ", strings.Join(amod.commonProperties.Vintf_fragments, " ")) - } - if Bool(amod.commonProperties.Proprietary) { - fmt.Fprintln(&data.preamble, "LOCAL_PROPRIETARY_MODULE := true") - } - if Bool(amod.commonProperties.Vendor) || Bool(amod.commonProperties.Soc_specific) { - fmt.Fprintln(&data.preamble, "LOCAL_VENDOR_MODULE := true") - } - if Bool(amod.commonProperties.Device_specific) { - fmt.Fprintln(&data.preamble, "LOCAL_ODM_MODULE := true") - } - if Bool(amod.commonProperties.Product_specific) { - fmt.Fprintln(&data.preamble, "LOCAL_PRODUCT_MODULE := true") - } - if Bool(amod.commonProperties.Product_services_specific) { - fmt.Fprintln(&data.preamble, "LOCAL_PRODUCT_SERVICES_MODULE := true") - } - if amod.commonProperties.Owner != nil { - fmt.Fprintln(&data.preamble, "LOCAL_MODULE_OWNER :=", *amod.commonProperties.Owner) - } - } - - if amod.noticeFile.Valid() { - fmt.Fprintln(&data.preamble, "LOCAL_NOTICE_FILE :=", amod.noticeFile.String()) - } - - if host { - makeOs := amod.Os().String() - if amod.Os() == Linux || amod.Os() == LinuxBionic { - makeOs = "linux" - } - fmt.Fprintln(&data.preamble, "LOCAL_MODULE_HOST_OS :=", makeOs) - fmt.Fprintln(&data.preamble, "LOCAL_IS_HOST_MODULE := true") - } - + name := provider.BaseModuleName() blueprintDir := filepath.Dir(ctx.BlueprintFile(mod)) if data.Custom != nil { @@ -360,3 +440,30 @@ func WriteAndroidMkData(w io.Writer, data AndroidMkData) { fmt.Fprintln(w, "include "+data.Include) } + +func translateAndroidMkEntriesModule(ctx SingletonContext, w io.Writer, mod blueprint.Module, + provider AndroidMkEntriesProvider) error { + if shouldSkipAndroidMkProcessing(mod.(Module).base()) { + return nil + } + + entries := provider.AndroidMkEntries() + entries.fillInEntries(ctx.Config(), ctx.BlueprintFile(mod), mod) + + entries.write(w) + + return nil +} + +func shouldSkipAndroidMkProcessing(module *ModuleBase) bool { + if !module.commonProperties.NamespaceExportedToMake { + // TODO(jeffrygaston) do we want to validate that there are no modules being + // exported to Kati that depend on this module? + return true + } + + return !module.Enabled() || + module.commonProperties.SkipInstall || + // Make does not understand LinuxBionic + module.Os() == LinuxBionic +} diff --git a/android/arch.go b/android/arch.go index 957a659c..6e8c4bdb 100644 --- a/android/arch.go +++ b/android/arch.go @@ -557,6 +557,10 @@ func newArch(name, multilib string) ArchType { return archType } +func ArchTypeList() []ArchType { + return append([]ArchType(nil), archTypeList...) +} + func (a ArchType) String() string { return a.Name } diff --git a/android/bootjar.go b/android/bootjar.go new file mode 100644 index 00000000..ea04dc8f --- /dev/null +++ b/android/bootjar.go @@ -0,0 +1,117 @@ +// Copyright (C) 2019 The LineageOS 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 android + +import ( + "strings" +) + +// Keys are bootjar name, value is whether or not +// we've marked a module as being a provider for it. +var jarMap map[string]bool + +func init() { + PreArchMutators(RegisterBootJarMutators) +} + +// Note: registration function is structured this way so that it can be included +// from soong module tests. +func RegisterBootJarMutators(ctx RegisterMutatorsContext) { + // Note: can't use Parallel() since we touch global jarMap + ctx.TopDown("bootjar_exportednamespace", bootJarMutatorExportedNamespace) + ctx.TopDown("bootjar_anynamespace", bootJarMutatorAnyNamespace) +} + +func mutatorInit(mctx TopDownMutatorContext) { + // Did we init already ? + if jarMap != nil { + return + } + + jarMap = make(map[string]bool) + for _, moduleName := range mctx.Config().BootJars() { + jarMap[moduleName] = false + } +} + +// Mark modules in soong exported namespace as providing a boot jar. +func bootJarMutatorExportedNamespace(mctx TopDownMutatorContext) { + bootJarMutator(mctx, true) +} + +// Mark modules in any namespace (incl root) as providing a boot jar. +func bootJarMutatorAnyNamespace(mctx TopDownMutatorContext) { + bootJarMutator(mctx, false) +} + +func bootJarMutator(mctx TopDownMutatorContext, requireExportedNamespace bool) { + mutatorInit(mctx) + + module, ok := mctx.Module().(Module) + if !ok { + // Not a proper module + return + } + + // Does this module produce a dex jar ? + if _, ok := module.(interface{ DexJar() Path }); !ok { + // No + return + } + + // If jarMap is empty we must be running in a test so + // set boot jar provide to true for all modules. + if len(jarMap) == 0 { + module.base().commonProperties.BootJarProvider = true + return + } + + name := mctx.ModuleName() + dir := mctx.ModuleDir() + + // Special treatment for hiddenapi modules - create extra + // jarMap entries if needed. + baseName := strings.TrimSuffix(name, "-hiddenapi") + if name != baseName { + _, baseIsBootJar := jarMap[baseName] + _, alreadyExists := jarMap[name] + if baseIsBootJar && !alreadyExists { + // This is a hidden api module whose base name exists in the boot jar list + // and we've not visited it before. Create a map entry for it. + jarMap[name] = false + } + } + + // Does this module match the name of a boot jar ? + if found, exists := jarMap[name]; !exists || found { + // No + return + } + + if requireExportedNamespace { + for _, n := range mctx.Config().ExportedNamespaces() { + if strings.HasPrefix(dir, n) { + jarMap[name] = true + module.base().commonProperties.BootJarProvider = true + break + } + } + } else { + jarMap[name] = true + module.base().commonProperties.BootJarProvider = true + } + + return +} diff --git a/android/config.go b/android/config.go index d1db87b2..7c604a42 100644 --- a/android/config.go +++ b/android/config.go @@ -584,7 +584,7 @@ func (c *config) DefaultAppCertificateDir(ctx PathContext) SourcePath { if defaultCert != "" { return PathForSource(ctx, filepath.Dir(defaultCert)) } else { - return PathForSource(ctx, "build/target/product/security") + return PathForSource(ctx, "build/make/target/product/security") } } @@ -601,7 +601,7 @@ func (c *config) DefaultAppCertificate(ctx PathContext) (pem, key SourcePath) { func (c *config) ApexKeyDir(ctx ModuleContext) SourcePath { // TODO(b/121224311): define another variable such as TARGET_APEX_KEY_OVERRIDE defaultCert := String(c.productVariables.DefaultAppCertificate) - if defaultCert == "" || filepath.Dir(defaultCert) == "build/target/product/security" { + if defaultCert == "" || filepath.Dir(defaultCert) == "build/make/target/product/security" { // When defaultCert is unset or is set to the testkeys path, use the APEX keys // that is under the module dir return pathForModuleSrc(ctx) @@ -882,6 +882,10 @@ func (c *deviceConfig) DeviceKernelHeaderDirs() []string { return c.config.productVariables.DeviceKernelHeaders } +func (c *deviceConfig) SpecificCameraParametersLibrary() string { + return String(c.config.productVariables.Lineage.Specific_camera_parameter_library) +} + func (c *deviceConfig) NativeCoverageEnabled() bool { return Bool(c.config.productVariables.NativeCoverage) } diff --git a/android/module.go b/android/module.go index 6a796227..17ddb050 100644 --- a/android/module.go +++ b/android/module.go @@ -129,6 +129,7 @@ type ModuleContext interface { AddMissingDependencies(deps []string) InstallInData() bool + InstallInTestcases() bool InstallInSanitizerDir() bool InstallInRecovery() bool @@ -185,6 +186,7 @@ type Module interface { Enabled() bool Target() Target InstallInData() bool + InstallInTestcases() bool InstallInSanitizerDir() bool InstallInRecovery() bool SkipInstall() @@ -302,6 +304,9 @@ type commonProperties struct { SkipInstall bool `blueprint:"mutated"` NamespaceExportedToMake bool `blueprint:"mutated"` + + // Whether this module provides a boot jar + BootJarProvider bool `blueprint:"mutated"` } type hostAndDeviceProperties struct { @@ -519,6 +524,10 @@ func (a *ModuleBase) Name() string { return String(a.nameProperties.Name) } +func (a *ModuleBase) BootJarProvider() bool { + return a.commonProperties.BootJarProvider +} + // BaseModuleName returns the name of the module as specified in the blueprints file. func (a *ModuleBase) BaseModuleName() string { return String(a.nameProperties.Name) @@ -656,6 +665,10 @@ func (p *ModuleBase) InstallInData() bool { return false } +func (p *ModuleBase) InstallInTestcases() bool { + return false +} + func (p *ModuleBase) InstallInSanitizerDir() bool { return false } @@ -1252,6 +1265,10 @@ func (a *androidModuleContext) InstallInData() bool { return a.module.InstallInData() } +func (a *androidModuleContext) InstallInTestcases() bool { + return a.module.InstallInTestcases() +} + func (a *androidModuleContext) InstallInSanitizerDir() bool { return a.module.InstallInSanitizerDir() } diff --git a/android/namespace.go b/android/namespace.go index 50bdcba7..a2bd0242 100644 --- a/android/namespace.go +++ b/android/namespace.go @@ -228,6 +228,11 @@ func (r *NameResolver) parseFullyQualifiedName(name string) (namespaceName strin } func (r *NameResolver) getNamespacesToSearchForModule(sourceNamespace *Namespace) (searchOrder []*Namespace) { + if sourceNamespace.visibleNamespaces == nil { + // When handling dependencies before namespaceMutator, assume they are non-Soong Blueprint modules and give + // access to all namespaces. + return r.sortedNamespaces.sortedItems() + } return sourceNamespace.visibleNamespaces } @@ -265,7 +270,12 @@ func (r *NameResolver) FindNamespaceImports(namespace *Namespace) (err error) { for _, name := range namespace.importedNamespaceNames { imp, ok := r.namespaceAt(name) if !ok { - return fmt.Errorf("namespace %v does not exist", name) + if (name != "all") { + return fmt.Errorf("namespace %v does not exist", name) + } else { + namespace.visibleNamespaces = make([]*Namespace, 0, 2+len(namespace.importedNamespaceNames)) + return nil + } } namespace.visibleNamespaces = append(namespace.visibleNamespaces, imp) } diff --git a/android/namespace_test.go b/android/namespace_test.go index 9a791a53..ec392dfc 100644 --- a/android/namespace_test.go +++ b/android/namespace_test.go @@ -93,6 +93,28 @@ func TestImplicitlyImportRootNamespace(t *testing.T) { // setupTest will report any errors } +func TestDependingOnBlueprintModuleInRootNamespace(t *testing.T) { + _ = setupTest(t, + map[string]string{ + ".": ` + blueprint_test_module { + name: "a", + } + `, + "dir1": ` + soong_namespace { + } + blueprint_test_module { + name: "b", + deps: ["a"], + } + `, + }, + ) + + // setupTest will report any errors +} + func TestDependingOnModuleInImportedNamespace(t *testing.T) { ctx := setupTest(t, map[string]string{ @@ -625,6 +647,7 @@ func setupTestFromFiles(bps map[string][]byte) (ctx *TestContext, errs []error) ctx.MockFileSystem(bps) ctx.RegisterModuleType("test_module", ModuleFactoryAdaptor(newTestModule)) ctx.RegisterModuleType("soong_namespace", ModuleFactoryAdaptor(NamespaceFactory)) + ctx.RegisterModuleType("blueprint_test_module", newBlueprintTestModule) ctx.PreArchMutators(RegisterNamespaceMutator) ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) { ctx.BottomUp("rename", renameMutator) @@ -649,6 +672,7 @@ func setupTestExpectErrs(bps map[string]string) (ctx *TestContext, errs []error) } func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) { + t.Helper() ctx, errs := setupTestExpectErrs(bps) FailIfErrored(t, errs) return ctx @@ -726,3 +750,22 @@ func newTestModule() Module { InitAndroidModule(m) return m } + +type blueprintTestModule struct { + blueprint.SimpleName + properties struct { + Deps []string + } +} + +func (b *blueprintTestModule) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { + return b.properties.Deps +} + +func (b *blueprintTestModule) GenerateBuildActions(blueprint.ModuleContext) { +} + +func newBlueprintTestModule() (blueprint.Module, []interface{}) { + m := &blueprintTestModule{} + return m, []interface{}{&m.properties, &m.SimpleName.Properties} +} diff --git a/android/paths.go b/android/paths.go index 0f20b844..edddce90 100644 --- a/android/paths.go +++ b/android/paths.go @@ -46,6 +46,7 @@ type ModuleInstallPathContext interface { androidBaseContext InstallInData() bool + InstallInTestcases() bool InstallInSanitizerDir() bool InstallInRecovery() bool } @@ -644,6 +645,31 @@ func pathForSource(ctx PathContext, pathComponents ...string) (SourcePath, error return ret, nil } +// pathForSourceRelaxed creates a SourcePath from pathComponents, but does not check that it exists. +// It differs from pathForSource in that the path is allowed to exist outside of the PathContext. +func pathForSourceRelaxed(ctx PathContext, pathComponents ...string) (SourcePath, error) { + p := filepath.Join(pathComponents...) + ret := SourcePath{basePath{p, ctx.Config(), ""}} + + abs, err := filepath.Abs(ret.String()) + if err != nil { + return ret, err + } + buildroot, err := filepath.Abs(ctx.Config().buildDir) + if err != nil { + return ret, err + } + if strings.HasPrefix(abs, buildroot) { + return ret, fmt.Errorf("source path %s is in output", abs) + } + + if pathtools.IsGlob(ret.String()) { + return ret, fmt.Errorf("path may not contain a glob: %s", ret.String()) + } + + return ret, nil +} + // existsWithDependencies returns true if the path exists, and adds appropriate dependencies to rerun if the // path does not exist. func existsWithDependencies(ctx PathContext, path SourcePath) (exists bool, err error) { @@ -697,6 +723,31 @@ func PathForSource(ctx PathContext, pathComponents ...string) SourcePath { return path } +// PathForSourceRelaxed joins the provided path components. Unlike PathForSource, +// the result is allowed to exist outside of the source dir. +// On error, it will return a usable, but invalid SourcePath, and report a ModuleError. +func PathForSourceRelaxed(ctx PathContext, pathComponents ...string) SourcePath { + path, err := pathForSourceRelaxed(ctx, pathComponents...) + if err != nil { + reportPathError(ctx, err) + } + + if modCtx, ok := ctx.(ModuleContext); ok && ctx.Config().AllowMissingDependencies() { + exists, err := existsWithDependencies(ctx, path) + if err != nil { + reportPathError(ctx, err) + } + if !exists { + modCtx.AddMissingDependencies([]string{path.String()}) + } + } else if exists, _, err := ctx.Fs().Exists(path.String()); err != nil { + reportPathErrorf(ctx, "%s: %s", path, err.Error()) + } else if !exists { + reportPathErrorf(ctx, "source path %s does not exist", path) + } + return path +} + // ExistentPathForSource returns an OptionalPath with the SourcePath if the // path exists, or an empty OptionalPath if it doesn't exist. Dependencies are added // so that the ninja file will be regenerated if the state of the path changes. @@ -1117,6 +1168,8 @@ func modulePartition(ctx ModuleInstallPathContext) string { var partition string if ctx.InstallInData() { partition = "data" + } else if ctx.InstallInTestcases() { + partition = "testcases" } else if ctx.InstallInRecovery() { // the layout of recovery partion is the same as that of system partition partition = "recovery/root/system" diff --git a/android/paths_test.go b/android/paths_test.go index b52d7133..c956a795 100644 --- a/android/paths_test.go +++ b/android/paths_test.go @@ -203,6 +203,7 @@ type moduleInstallPathContextImpl struct { androidBaseContextImpl inData bool + inTestcases bool inSanitizerDir bool inRecovery bool } @@ -221,6 +222,10 @@ func (m moduleInstallPathContextImpl) InstallInData() bool { return m.inData } +func (m moduleInstallPathContextImpl) InstallInTestcases() bool { + return m.inTestcases +} + func (m moduleInstallPathContextImpl) InstallInSanitizerDir() bool { return m.inSanitizerDir } diff --git a/android/prebuilt.go b/android/prebuilt.go index 3be10f72..48d3b68b 100644 --- a/android/prebuilt.go +++ b/android/prebuilt.go @@ -16,6 +16,7 @@ package android import ( "fmt" + "reflect" "github.com/google/blueprint" "github.com/google/blueprint/proptools" @@ -43,13 +44,21 @@ type Prebuilt struct { properties PrebuiltProperties module Module srcs *[]string - src *string + + // Metadata for single source Prebuilt modules. + srcProps reflect.Value + srcField reflect.StructField } func (p *Prebuilt) Name(name string) string { return "prebuilt_" + name } +// The below source-related functions and the srcs, src fields are based on an assumption that +// prebuilt modules have a static source property at the moment. Currently there is only one +// exception, android_app_import, which chooses a source file depending on the product's DPI +// preference configs. We'll want to add native support for dynamic source cases if we end up having +// more modules like this. func (p *Prebuilt) SingleSourcePath(ctx ModuleContext) Path { if p.srcs != nil { if len(*p.srcs) == 0 { @@ -66,11 +75,16 @@ func (p *Prebuilt) SingleSourcePath(ctx ModuleContext) Path { // sources. return PathForModuleSrc(ctx, (*p.srcs)[0]) } else { - if proptools.String(p.src) == "" { - ctx.PropertyErrorf("src", "missing prebuilt source file") + if !p.srcProps.IsValid() { + ctx.ModuleErrorf("prebuilt source was not set") + } + src := p.getSingleSourceFieldValue() + if src == "" { + ctx.PropertyErrorf(proptools.FieldNameForProperty(p.srcField.Name), + "missing prebuilt source file") return nil } - return PathForModuleSrc(ctx, *p.src) + return PathForModuleSrc(ctx, src) } } @@ -84,10 +98,12 @@ func InitPrebuiltModule(module PrebuiltInterface, srcs *[]string) { p.srcs = srcs } -func InitSingleSourcePrebuiltModule(module PrebuiltInterface, src *string) { +func InitSingleSourcePrebuiltModule(module PrebuiltInterface, srcProps interface{}, srcField string) { p := module.Prebuilt() module.AddProperties(&p.properties) - p.src = src + p.srcProps = reflect.ValueOf(srcProps).Elem() + p.srcField, _ = p.srcProps.Type().FieldByName(srcField) + p.checkSingleSourceProperties() } type PrebuiltInterface interface { @@ -124,7 +140,7 @@ func PrebuiltMutator(ctx BottomUpMutatorContext) { func PrebuiltSelectModuleMutator(ctx TopDownMutatorContext) { if m, ok := ctx.Module().(PrebuiltInterface); ok && m.Prebuilt() != nil { p := m.Prebuilt() - if p.srcs == nil && p.src == nil { + if p.srcs == nil && !p.srcProps.IsValid() { panic(fmt.Errorf("prebuilt module did not have InitPrebuiltModule called on it")) } if !p.properties.SourceExists { @@ -167,7 +183,7 @@ func (p *Prebuilt) usePrebuilt(ctx TopDownMutatorContext, source Module) bool { return false } - if p.src != nil && *p.src == "" { + if p.srcProps.IsValid() && p.getSingleSourceFieldValue() == "" { return false } @@ -182,3 +198,24 @@ func (p *Prebuilt) usePrebuilt(ctx TopDownMutatorContext, source Module) bool { func (p *Prebuilt) SourceExists() bool { return p.properties.SourceExists } + +func (p *Prebuilt) checkSingleSourceProperties() { + if !p.srcProps.IsValid() || p.srcField.Name == "" { + panic(fmt.Errorf("invalid single source prebuilt %+v", p)) + } + + if p.srcProps.Kind() != reflect.Struct && p.srcProps.Kind() != reflect.Interface { + panic(fmt.Errorf("invalid single source prebuilt %+v", p.srcProps)) + } +} + +func (p *Prebuilt) getSingleSourceFieldValue() string { + value := p.srcProps.FieldByIndex(p.srcField.Index) + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + if value.Kind() != reflect.String { + panic(fmt.Errorf("prebuilt src field %q should be a string or a pointer to one", p.srcField.Name)) + } + return value.String() +} diff --git a/android/prebuilt_etc.go b/android/prebuilt_etc.go index 8f23d78e..f35e3c76 100644 --- a/android/prebuilt_etc.go +++ b/android/prebuilt_etc.go @@ -14,11 +14,7 @@ package android -import ( - "fmt" - "io" - "strings" -) +import "strconv" // TODO(jungw): Now that it handles more than the ones in etc/, consider renaming this file. @@ -135,38 +131,27 @@ func (p *PrebuiltEtc) GenerateAndroidBuildActions(ctx ModuleContext) { }) } -func (p *PrebuiltEtc) AndroidMk() AndroidMkData { - return AndroidMkData{ - Custom: func(w io.Writer, name, prefix, moduleDir string, data AndroidMkData) { - nameSuffix := "" - if p.inRecovery() && !p.onlyInRecovery() { - nameSuffix = ".recovery" - } - fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)") - fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir) - fmt.Fprintln(w, "LOCAL_MODULE :=", name+nameSuffix) - fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC") - if p.commonProperties.Owner != nil { - fmt.Fprintln(w, "LOCAL_MODULE_OWNER :=", *p.commonProperties.Owner) - } - fmt.Fprintln(w, "LOCAL_MODULE_TAGS := optional") - if p.Host() { - fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true") - } - fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", p.outputFilePath.String()) - fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", "$(OUT_DIR)/"+p.installDirPath.RelPathString()) - fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", p.outputFilePath.Base()) - fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE :=", !p.Installable()) - fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(data.Required, " ")) - fmt.Fprintln(w, "LOCAL_MODULE_TARGET_ARCH :=", p.Arch().ArchType.String()) - if p.additionalDependencies != nil { - fmt.Fprint(w, "LOCAL_ADDITIONAL_DEPENDENCIES :=") - for _, path := range *p.additionalDependencies { - fmt.Fprint(w, " "+path.String()) +func (p *PrebuiltEtc) AndroidMkEntries() AndroidMkEntries { + nameSuffix := "" + if p.inRecovery() && !p.onlyInRecovery() { + nameSuffix = ".recovery" + } + return AndroidMkEntries{ + Class: "ETC", + SubName: nameSuffix, + OutputFile: OptionalPathForPath(p.outputFilePath), + ExtraEntries: []AndroidMkExtraEntriesFunc{ + func(entries *AndroidMkEntries) { + entries.SetString("LOCAL_MODULE_TAGS", "optional") + entries.SetString("LOCAL_MODULE_PATH", "$(OUT_DIR)/"+p.installDirPath.RelPathString()) + entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base()) + entries.SetString("LOCAL_UNINSTALLABLE_MODULE", strconv.FormatBool(!p.Installable())) + if p.additionalDependencies != nil { + for _, path := range *p.additionalDependencies { + entries.SetString("LOCAL_ADDITIONAL_DEPENDENCIES", path.String()) + } } - fmt.Fprintln(w, "") - } - fmt.Fprintln(w, "include $(BUILD_PREBUILT)") + }, }, } } diff --git a/android/prebuilt_etc_test.go b/android/prebuilt_etc_test.go index e0ade7e5..ee6e990e 100644 --- a/android/prebuilt_etc_test.go +++ b/android/prebuilt_etc_test.go @@ -15,12 +15,10 @@ package android import ( - "bufio" - "bytes" "io/ioutil" "os" "path/filepath" - "strings" + "reflect" "testing" ) @@ -139,45 +137,33 @@ func TestPrebuiltEtcGlob(t *testing.T) { } func TestPrebuiltEtcAndroidMk(t *testing.T) { - ctx, _ := testPrebuiltEtc(t, ` + ctx, config := testPrebuiltEtc(t, ` prebuilt_etc { name: "foo", src: "foo.conf", owner: "abc", filename_from_src: true, + required: ["modA", "moduleB"], } `) - data := AndroidMkData{} - data.Required = append(data.Required, "modA", "moduleB") - - expected := map[string]string{ - "LOCAL_MODULE": "foo", - "LOCAL_MODULE_CLASS": "ETC", - "LOCAL_MODULE_OWNER": "abc", - "LOCAL_INSTALLED_MODULE_STEM": "foo.conf", - "LOCAL_REQUIRED_MODULES": "modA moduleB", + expected := map[string][]string{ + "LOCAL_MODULE": {"foo"}, + "LOCAL_MODULE_CLASS": {"ETC"}, + "LOCAL_MODULE_OWNER": {"abc"}, + "LOCAL_INSTALLED_MODULE_STEM": {"foo.conf"}, + "LOCAL_REQUIRED_MODULES": {"modA", "moduleB"}, } mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a_core").Module().(*PrebuiltEtc) - buf := &bytes.Buffer{} - mod.AndroidMk().Custom(buf, "foo", "", "", data) - for k, expected := range expected { - found := false - scanner := bufio.NewScanner(bytes.NewReader(buf.Bytes())) - for scanner.Scan() { - line := scanner.Text() - tok := strings.Split(line, " := ") - if tok[0] == k { - found = true - if tok[1] != expected { - t.Errorf("Incorrect %s '%s', expected '%s'", k, tok[1], expected) - } + entries := AndroidMkEntriesForTest(t, config, "", mod) + for k, expectedValue := range expected { + if value, ok := entries.EntryMap[k]; ok { + if !reflect.DeepEqual(value, expectedValue) { + t.Errorf("Incorrect %s '%s', expected '%s'", k, value, expectedValue) } - } - - if !found { - t.Errorf("No %s defined, saw %s", k, buf.String()) + } else { + t.Errorf("No %s defined, saw %q", k, entries.EntryMap) } } } diff --git a/android/sh_binary.go b/android/sh_binary.go index cf415c56..ba0c8be7 100644 --- a/android/sh_binary.go +++ b/android/sh_binary.go @@ -16,7 +16,6 @@ package android import ( "fmt" - "io" "strings" ) @@ -30,6 +29,7 @@ func init() { RegisterModuleType("sh_binary", ShBinaryFactory) RegisterModuleType("sh_binary_host", ShBinaryHostFactory) RegisterModuleType("sh_test", ShTestFactory) + RegisterModuleType("sh_test_host", ShTestHostFactory) } type shBinaryProperties struct { @@ -58,6 +58,10 @@ type TestProperties struct { // the name of the test configuration (for example "AndroidTest.xml") that should be // installed with the module. Test_config *string `android:"arch_variant"` + + // list of files or filegroup modules that provide data that should be installed alongside + // the test. + Data []string `android:"path,arch_variant"` } type ShBinary struct { @@ -73,6 +77,8 @@ type ShTest struct { ShBinary testProperties TestProperties + + data Paths } func (s *ShBinary) DepsMutator(ctx BottomUpMutatorContext) { @@ -122,30 +128,54 @@ func (s *ShBinary) GenerateAndroidBuildActions(ctx ModuleContext) { }) } -func (s *ShBinary) AndroidMk() AndroidMkData { - return AndroidMkData{ +func (s *ShBinary) AndroidMkEntries() AndroidMkEntries { + return AndroidMkEntries{ Class: "EXECUTABLES", OutputFile: OptionalPathForPath(s.outputFilePath), Include: "$(BUILD_SYSTEM)/soong_cc_prebuilt.mk", - Extra: []AndroidMkExtraFunc{ - func(w io.Writer, outputFile Path) { - fmt.Fprintln(w, "LOCAL_MODULE_RELATIVE_PATH :=", String(s.properties.Sub_dir)) - fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX :=") - fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", s.outputFilePath.Rel()) + ExtraEntries: []AndroidMkExtraEntriesFunc{ + func(entries *AndroidMkEntries) { + s.customAndroidMkEntries(entries) }, }, } } -func (s *ShTest) AndroidMk() AndroidMkData { - data := s.ShBinary.AndroidMk() - data.Class = "NATIVE_TESTS" - data.Extra = append(data.Extra, func(w io.Writer, outputFile Path) { - fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=", - strings.Join(s.testProperties.Test_suites, " ")) - fmt.Fprintln(w, "LOCAL_TEST_CONFIG :=", String(s.testProperties.Test_config)) - }) - return data +func (s *ShBinary) customAndroidMkEntries(entries *AndroidMkEntries) { + entries.SetString("LOCAL_MODULE_RELATIVE_PATH", String(s.properties.Sub_dir)) + entries.SetString("LOCAL_MODULE_SUFFIX", "") + entries.SetString("LOCAL_MODULE_STEM", s.outputFilePath.Rel()) +} + +func (s *ShTest) GenerateAndroidBuildActions(ctx ModuleContext) { + s.ShBinary.GenerateAndroidBuildActions(ctx) + + s.data = PathsForModuleSrc(ctx, s.testProperties.Data) +} + +func (s *ShTest) AndroidMkEntries() AndroidMkEntries { + return AndroidMkEntries{ + Class: "NATIVE_TESTS", + OutputFile: OptionalPathForPath(s.outputFilePath), + Include: "$(BUILD_SYSTEM)/soong_cc_prebuilt.mk", + ExtraEntries: []AndroidMkExtraEntriesFunc{ + func(entries *AndroidMkEntries) { + s.customAndroidMkEntries(entries) + + entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", s.testProperties.Test_suites...) + entries.SetString("LOCAL_TEST_CONFIG", String(s.testProperties.Test_config)) + for _, d := range s.data { + rel := d.Rel() + path := d.String() + if !strings.HasSuffix(path, rel) { + panic(fmt.Errorf("path %q does not end with %q", path, rel)) + } + path = strings.TrimSuffix(path, rel) + entries.AddStrings("LOCAL_TEST_DATA", path+":"+rel) + } + }, + }, + } } func InitShBinaryModule(s *ShBinary) { @@ -170,6 +200,7 @@ func ShBinaryHostFactory() Module { return module } +// sh_test defines a shell script based test module. func ShTestFactory() Module { module := &ShTest{} InitShBinaryModule(&module.ShBinary) @@ -178,3 +209,13 @@ func ShTestFactory() Module { InitAndroidArchModule(module, HostAndDeviceSupported, MultilibFirst) return module } + +// sh_test_host defines a shell script based test module that runs on a host. +func ShTestHostFactory() Module { + module := &ShTest{} + InitShBinaryModule(&module.ShBinary) + module.AddProperties(&module.testProperties) + + InitAndroidArchModule(module, HostSupported, MultilibFirst) + return module +} diff --git a/android/sh_binary_test.go b/android/sh_binary_test.go new file mode 100644 index 00000000..67360830 --- /dev/null +++ b/android/sh_binary_test.go @@ -0,0 +1,79 @@ +package android + +import ( + "io/ioutil" + "os" + "reflect" + "testing" +) + +func testShBinary(t *testing.T, bp string) (*TestContext, Config) { + buildDir, err := ioutil.TempDir("", "soong_sh_binary_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(buildDir) + + config := TestArchConfig(buildDir, nil) + + ctx := NewTestArchContext() + ctx.RegisterModuleType("sh_test", ModuleFactoryAdaptor(ShTestFactory)) + ctx.RegisterModuleType("sh_test_host", ModuleFactoryAdaptor(ShTestHostFactory)) + ctx.Register() + mockFiles := map[string][]byte{ + "Android.bp": []byte(bp), + "test.sh": nil, + "testdata/data1": nil, + "testdata/sub/data2": nil, + } + ctx.MockFileSystem(mockFiles) + _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) + FailIfErrored(t, errs) + _, errs = ctx.PrepareBuildActions(config) + FailIfErrored(t, errs) + + return ctx, config +} + +func TestShTestTestData(t *testing.T) { + ctx, config := testShBinary(t, ` + sh_test { + name: "foo", + src: "test.sh", + filename: "test.sh", + data: [ + "testdata/data1", + "testdata/sub/data2", + ], + } + `) + + mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest) + + entries := AndroidMkEntriesForTest(t, config, "", mod) + expected := []string{":testdata/data1", ":testdata/sub/data2"} + actual := entries.EntryMap["LOCAL_TEST_DATA"] + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Unexpected test data expected: %q, actual: %q", expected, actual) + } +} + +func TestShTestHost(t *testing.T) { + ctx, _ := testShBinary(t, ` + sh_test_host { + name: "foo", + src: "test.sh", + filename: "test.sh", + data: [ + "testdata/data1", + "testdata/sub/data2", + ], + } + `) + + buildOS := BuildOs.String() + mod := ctx.ModuleForTests("foo", buildOS+"_x86_64").Module().(*ShTest) + if !mod.Host() { + t.Errorf("host bit is not set for a sh_test_host module.") + } +} diff --git a/android/testing.go b/android/testing.go index 0ec5af58..ab3c69cf 100644 --- a/android/testing.go +++ b/android/testing.go @@ -369,3 +369,14 @@ func FailIfNoMatchingErrors(t *testing.T, pattern string, errs []error) { } } } + +func AndroidMkEntriesForTest(t *testing.T, config Config, bpPath string, mod blueprint.Module) AndroidMkEntries { + var p AndroidMkEntriesProvider + var ok bool + if p, ok = mod.(AndroidMkEntriesProvider); !ok { + t.Errorf("module does not implmement AndroidMkEntriesProvider: " + mod.Name()) + } + entries := p.AndroidMkEntries() + entries.fillInEntries(config, bpPath, mod) + return entries +} diff --git a/android/variable.go b/android/variable.go index e643c0eb..56fdcc18 100644 --- a/android/variable.go +++ b/android/variable.go @@ -20,6 +20,8 @@ import ( "runtime" "strings" + "lineage/soong/android" + "github.com/google/blueprint/proptools" ) @@ -124,6 +126,9 @@ type variableProperties struct { Static_libs []string Srcs []string } + + // include Lineage variables + Lineage android.Product_variables } `android:"arch_variant"` } @@ -287,6 +292,9 @@ type productVariables struct { ProductHiddenAPIStubsTest []string `json:",omitempty"` TargetFSConfigGen []string `json:",omitempty"` + + // include Lineage variables + Lineage android.ProductVariables } func boolPtr(v bool) *bool { @@ -346,7 +354,13 @@ func variableMutator(mctx BottomUpMutatorContext) { a := module.base() variableValues := reflect.ValueOf(&a.variableProperties.Product_variables).Elem() zeroValues := reflect.ValueOf(zeroProductVariables.Product_variables) + valStruct := reflect.ValueOf(mctx.Config().productVariables) + doVariableMutation(mctx, a, variableValues, zeroValues, valStruct) +} + +func doVariableMutation(mctx BottomUpMutatorContext, a *ModuleBase, variableValues reflect.Value, zeroValues reflect.Value, + valStruct reflect.Value) { for i := 0; i < variableValues.NumField(); i++ { variableValue := variableValues.Field(i) zeroValue := zeroValues.Field(i) @@ -354,8 +368,11 @@ func variableMutator(mctx BottomUpMutatorContext) { property := "product_variables." + proptools.PropertyNameForField(name) // Check that the variable was set for the product - val := reflect.ValueOf(mctx.Config().productVariables).FieldByName(name) - if !val.IsValid() || val.Kind() != reflect.Ptr || val.IsNil() { + val := valStruct.FieldByName(name) + if val.IsValid() && val.Kind() == reflect.Struct { + doVariableMutation(mctx, a, variableValue, zeroValue, val) + continue + } else if !val.IsValid() || val.Kind() != reflect.Ptr || val.IsNil() { continue } |