From 571cccfcbc795f2bc955ca7fb3ac09085b360e09 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 4 Feb 2019 11:22:08 -0800 Subject: [PATCH] Prepare for a type-safe OnceKey Add an opaque OnceKey type and use it for all calls to Once in build/soong. A future patch will convert the arguments to Once* to OnceKey once users outside build/soong have been updated. Test: onceper_test.go Change-Id: Ifcb338e6e603e804e507203c9508d30ffb2df966 --- Android.bp | 1 + android/apex.go | 3 +- android/api_levels.go | 4 +- android/arch.go | 2 +- android/onceper.go | 24 ++++++- android/onceper_test.go | 135 ++++++++++++++++++++++++++++++++++++++ cc/compiler.go | 8 +-- cc/library.go | 8 ++- cc/makevars.go | 22 +++---- cc/pgo.go | 5 +- cc/sanitize.go | 12 +++- java/android_resources.go | 2 +- java/dexpreopt.go | 4 +- java/hiddenapi.go | 2 +- java/sdk.go | 2 +- java/sdk_library.go | 4 +- 16 files changed, 206 insertions(+), 32 deletions(-) create mode 100644 android/onceper_test.go diff --git a/Android.bp b/Android.bp index 5e9c600b0..591e5c85f 100644 --- a/Android.bp +++ b/Android.bp @@ -78,6 +78,7 @@ bootstrap_go_package { "android/expand_test.go", "android/namespace_test.go", "android/neverallow_test.go", + "android/onceper_test.go", "android/paths_test.go", "android/prebuilt_test.go", "android/prebuilt_etc_test.go", diff --git a/android/apex.go b/android/apex.go index a93baf6d4..bf11ba25f 100644 --- a/android/apex.go +++ b/android/apex.go @@ -139,6 +139,7 @@ func (m *ApexModuleBase) CreateApexVariations(mctx BottomUpMutatorContext) []blu var apexData OncePer var apexNamesMapMutex sync.Mutex +var apexNamesKey = NewOnceKey("apexNames") // This structure maintains the global mapping in between modules and APEXes. // Examples: @@ -147,7 +148,7 @@ var apexNamesMapMutex sync.Mutex // apexNamesMap()["foo"]["bar"] == false: module foo is indirectly depended on by APEX bar // apexNamesMap()["foo"]["bar"] doesn't exist: foo is not built for APEX bar func apexNamesMap() map[string]map[string]bool { - return apexData.Once("apexNames", func() interface{} { + return apexData.Once(apexNamesKey, func() interface{} { return make(map[string]map[string]bool) }).(map[string]map[string]bool) } diff --git a/android/api_levels.go b/android/api_levels.go index 1b56625b2..51d470381 100644 --- a/android/api_levels.go +++ b/android/api_levels.go @@ -51,8 +51,10 @@ func GetApiLevelsJson(ctx PathContext) WritablePath { return PathForOutput(ctx, "api_levels.json") } +var apiLevelsMapKey = NewOnceKey("ApiLevelsMap") + func getApiLevelsMap(config Config) map[string]int { - return config.Once("ApiLevelsMap", func() interface{} { + return config.Once(apiLevelsMapKey, func() interface{} { baseApiLevel := 9000 apiLevelsMap := map[string]int{ "G": 9, diff --git a/android/arch.go b/android/arch.go index 953e6cfc7..ad812a4a0 100644 --- a/android/arch.go +++ b/android/arch.go @@ -879,7 +879,7 @@ func InitArchModule(m Module) { propertiesValue.Interface())) } - archPropTypes := archPropTypeMap.Once(t, func() interface{} { + archPropTypes := archPropTypeMap.Once(NewCustomOnceKey(t), func() interface{} { return createArchType(t) }).([]reflect.Type) diff --git a/android/onceper.go b/android/onceper.go index f19f75c08..6160506b0 100644 --- a/android/onceper.go +++ b/android/onceper.go @@ -24,8 +24,6 @@ type OncePer struct { valuesLock sync.Mutex } -type valueMap map[interface{}]interface{} - // Once computes a value the first time it is called with a given key per OncePer, and returns the // value without recomputing when called with the same key. key must be hashable. func (once *OncePer) Once(key interface{}, value func() interface{}) interface{} { @@ -50,6 +48,8 @@ func (once *OncePer) Once(key interface{}, value func() interface{}) interface{} return v } +// Get returns the value previously computed with Once for a given key. If Once has not been called for the given +// key Get will panic. func (once *OncePer) Get(key interface{}) interface{} { v, ok := once.values.Load(key) if !ok { @@ -59,10 +59,12 @@ func (once *OncePer) Get(key interface{}) interface{} { return v } +// OnceStringSlice is the same as Once, but returns the value cast to a []string func (once *OncePer) OnceStringSlice(key interface{}, value func() []string) []string { return once.Once(key, func() interface{} { return value() }).([]string) } +// OnceStringSlice is the same as Once, but returns two values cast to []string func (once *OncePer) Once2StringSlice(key interface{}, value func() ([]string, []string)) ([]string, []string) { type twoStringSlice [2][]string s := once.Once(key, func() interface{} { @@ -72,3 +74,21 @@ func (once *OncePer) Once2StringSlice(key interface{}, value func() ([]string, [ }).(twoStringSlice) return s[0], s[1] } + +// OnceKey is an opaque type to be used as the key in calls to Once. +type OnceKey struct { + key interface{} +} + +// NewOnceKey returns an opaque OnceKey object for the provided key. Two calls to NewOnceKey with the same key string +// DO NOT produce the same OnceKey object. +func NewOnceKey(key string) OnceKey { + return OnceKey{&key} +} + +// NewCustomOnceKey returns an opaque OnceKey object for the provided key. The key can be any type that is valid as the +// key in a map, i.e. comparable. Two calls to NewCustomOnceKey with key values that compare equal will return OnceKey +// objects that access the same value stored with Once. +func NewCustomOnceKey(key interface{}) OnceKey { + return OnceKey{key} +} diff --git a/android/onceper_test.go b/android/onceper_test.go new file mode 100644 index 000000000..d2ca9ad70 --- /dev/null +++ b/android/onceper_test.go @@ -0,0 +1,135 @@ +// Copyright 2019 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 ( + "testing" +) + +func TestOncePer_Once(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + a := once.Once(key, func() interface{} { return "a" }).(string) + b := once.Once(key, func() interface{} { return "b" }).(string) + + if a != "a" { + t.Errorf(`first call to Once should return "a": %q`, a) + } + + if b != "a" { + t.Errorf(`second call to Once with the same key should return "a": %q`, b) + } +} + +func TestOncePer_Get(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + a := once.Once(key, func() interface{} { return "a" }).(string) + b := once.Get(key).(string) + + if a != "a" { + t.Errorf(`first call to Once should return "a": %q`, a) + } + + if b != "a" { + t.Errorf(`Get with the same key should return "a": %q`, b) + } +} + +func TestOncePer_Get_panic(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + defer func() { + p := recover() + + if p == nil { + t.Error("call to Get for unused key should panic") + } + }() + + once.Get(key) +} + +func TestOncePer_OnceStringSlice(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + a := once.OnceStringSlice(key, func() []string { return []string{"a"} }) + b := once.OnceStringSlice(key, func() []string { return []string{"a"} }) + + if a[0] != "a" { + t.Errorf(`first call to OnceStringSlice should return ["a"]: %q`, a) + } + + if b[0] != "a" { + t.Errorf(`second call to OnceStringSlice with the same key should return ["a"]: %q`, b) + } +} + +func TestOncePer_Once2StringSlice(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + a, b := once.Once2StringSlice(key, func() ([]string, []string) { return []string{"a"}, []string{"b"} }) + c, d := once.Once2StringSlice(key, func() ([]string, []string) { return []string{"c"}, []string{"d"} }) + + if a[0] != "a" || b[0] != "b" { + t.Errorf(`first call to Once2StringSlice should return ["a"], ["b"]: %q, %q`, a, b) + } + + if c[0] != "a" || d[0] != "b" { + t.Errorf(`second call to Once2StringSlice with the same key should return ["a"], ["b"]: %q, %q`, c, d) + } +} + +func TestNewOnceKey(t *testing.T) { + once := OncePer{} + key1 := NewOnceKey("key") + key2 := NewOnceKey("key") + + a := once.Once(key1, func() interface{} { return "a" }).(string) + b := once.Once(key2, func() interface{} { return "b" }).(string) + + if a != "a" { + t.Errorf(`first call to Once should return "a": %q`, a) + } + + if b != "b" { + t.Errorf(`second call to Once with the NewOnceKey from same string should return "b": %q`, b) + } +} + +func TestNewCustomOnceKey(t *testing.T) { + type key struct { + key string + } + once := OncePer{} + key1 := NewCustomOnceKey(key{"key"}) + key2 := NewCustomOnceKey(key{"key"}) + + a := once.Once(key1, func() interface{} { return "a" }).(string) + b := once.Once(key2, func() interface{} { return "b" }).(string) + + if a != "a" { + t.Errorf(`first call to Once should return "a": %q`, a) + } + + if b != "a" { + t.Errorf(`second call to Once with the NewCustomOnceKey from equal key should return "a": %q`, b) + } +} diff --git a/cc/compiler.go b/cc/compiler.go index fbe10b50a..0aee0bdb3 100644 --- a/cc/compiler.go +++ b/cc/compiler.go @@ -250,8 +250,8 @@ func warningsAreAllowed(subdir string) bool { return false } -func addToModuleList(ctx ModuleContext, list string, module string) { - getNamedMapForConfig(ctx.Config(), list).Store(module, true) +func addToModuleList(ctx ModuleContext, key android.OnceKey, module string) { + getNamedMapForConfig(ctx.Config(), key).Store(module, true) } // Create a Flags struct that collects the compile flags from global values, @@ -503,10 +503,10 @@ func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags, deps if len(compiler.Properties.Srcs) > 0 { module := ctx.ModuleDir() + "/Android.bp:" + ctx.ModuleName() if inList("-Wno-error", flags.CFlags) || inList("-Wno-error", flags.CppFlags) { - addToModuleList(ctx, modulesUsingWnoError, module) + addToModuleList(ctx, modulesUsingWnoErrorKey, module) } else if !inList("-Werror", flags.CFlags) && !inList("-Werror", flags.CppFlags) { if warningsAreAllowed(ctx.ModuleDir()) { - addToModuleList(ctx, modulesAddedWall, module) + addToModuleList(ctx, modulesAddedWallKey, module) flags.CFlags = append([]string{"-Wall"}, flags.CFlags...) } else { flags.CFlags = append([]string{"-Wall", "-Werror"}, flags.CFlags...) diff --git a/cc/library.go b/cc/library.go index d716ffac7..b4b89d2ac 100644 --- a/cc/library.go +++ b/cc/library.go @@ -968,8 +968,10 @@ func (library *libraryDecorator) stubsVersion() string { return library.MutatedProperties.StubsVersion } +var versioningMacroNamesListKey = android.NewOnceKey("versioningMacroNamesList") + func versioningMacroNamesList(config android.Config) *map[string]string { - return config.Once("versioningMacroNamesList", func() interface{} { + return config.Once(versioningMacroNamesListKey, func() interface{} { m := make(map[string]string) return &m }).(*map[string]string) @@ -1059,9 +1061,11 @@ func LinkageMutator(mctx android.BottomUpMutatorContext) { } } +var stubVersionsKey = android.NewOnceKey("stubVersions") + // maps a module name to the list of stubs versions available for the module func stubsVersionsFor(config android.Config) map[string][]string { - return config.Once("stubVersions", func() interface{} { + return config.Once(stubVersionsKey, func() interface{} { return make(map[string][]string) }).(map[string][]string) } diff --git a/cc/makevars.go b/cc/makevars.go index fb567babb..d91735f1f 100644 --- a/cc/makevars.go +++ b/cc/makevars.go @@ -24,24 +24,24 @@ import ( "android/soong/cc/config" ) -const ( - modulesAddedWall = "ModulesAddedWall" - modulesUsingWnoError = "ModulesUsingWnoError" - modulesMissingProfileFile = "ModulesMissingProfileFile" +var ( + modulesAddedWallKey = android.NewOnceKey("ModulesAddedWall") + modulesUsingWnoErrorKey = android.NewOnceKey("ModulesUsingWnoError") + modulesMissingProfileFileKey = android.NewOnceKey("ModulesMissingProfileFile") ) func init() { android.RegisterMakeVarsProvider(pctx, makeVarsProvider) } -func getNamedMapForConfig(config android.Config, name string) *sync.Map { - return config.Once(name, func() interface{} { +func getNamedMapForConfig(config android.Config, key android.OnceKey) *sync.Map { + return config.Once(key, func() interface{} { return &sync.Map{} }).(*sync.Map) } -func makeStringOfKeys(ctx android.MakeVarsContext, setName string) string { - set := getNamedMapForConfig(ctx.Config(), setName) +func makeStringOfKeys(ctx android.MakeVarsContext, key android.OnceKey) string { + set := getNamedMapForConfig(ctx.Config(), key) keys := []string{} set.Range(func(key interface{}, value interface{}) bool { keys = append(keys, key.(string)) @@ -117,9 +117,9 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("LSDUMP_PATHS", strings.Join(lsdumpPaths, " ")) ctx.Strict("ANDROID_WARNING_ALLOWED_PROJECTS", makeStringOfWarningAllowedProjects()) - ctx.Strict("SOONG_MODULES_ADDED_WALL", makeStringOfKeys(ctx, modulesAddedWall)) - ctx.Strict("SOONG_MODULES_USING_WNO_ERROR", makeStringOfKeys(ctx, modulesUsingWnoError)) - ctx.Strict("SOONG_MODULES_MISSING_PGO_PROFILE_FILE", makeStringOfKeys(ctx, modulesMissingProfileFile)) + ctx.Strict("SOONG_MODULES_ADDED_WALL", makeStringOfKeys(ctx, modulesAddedWallKey)) + ctx.Strict("SOONG_MODULES_USING_WNO_ERROR", makeStringOfKeys(ctx, modulesUsingWnoErrorKey)) + ctx.Strict("SOONG_MODULES_MISSING_PGO_PROFILE_FILE", makeStringOfKeys(ctx, modulesMissingProfileFileKey)) ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(asanCflags, " ")) ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", strings.Join(asanLdflags, " ")) diff --git a/cc/pgo.go b/cc/pgo.go index a341ab9f9..9363916a7 100644 --- a/cc/pgo.go +++ b/cc/pgo.go @@ -36,7 +36,8 @@ var ( } ) -const pgoProfileProjectsConfigKey = "PgoProfileProjects" +var pgoProfileProjectsConfigKey = android.NewOnceKey("PgoProfileProjects") + const profileInstrumentFlag = "-fprofile-generate=/data/local/tmp" const profileSamplingFlag = "-gline-tables-only" const profileUseInstrumentFormat = "-fprofile-use=%s" @@ -49,7 +50,7 @@ func getPgoProfileProjects(config android.DeviceConfig) []string { } func recordMissingProfileFile(ctx BaseModuleContext, missing string) { - getNamedMapForConfig(ctx.Config(), modulesMissingProfileFile).Store(missing, true) + getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true) } type PgoProperties struct { diff --git a/cc/sanitize.go b/cc/sanitize.go index bcc4de3fc..fc2ed5071 100644 --- a/cc/sanitize.go +++ b/cc/sanitize.go @@ -958,20 +958,26 @@ func sanitizerMutator(t sanitizerType) func(android.BottomUpMutatorContext) { } } +var cfiStaticLibsKey = android.NewOnceKey("cfiStaticLibs") + func cfiStaticLibs(config android.Config) *[]string { - return config.Once("cfiStaticLibs", func() interface{} { + return config.Once(cfiStaticLibsKey, func() interface{} { return &[]string{} }).(*[]string) } +var hwasanStaticLibsKey = android.NewOnceKey("hwasanStaticLibs") + func hwasanStaticLibs(config android.Config) *[]string { - return config.Once("hwasanStaticLibs", func() interface{} { + return config.Once(hwasanStaticLibsKey, func() interface{} { return &[]string{} }).(*[]string) } +var hwasanVendorStaticLibsKey = android.NewOnceKey("hwasanVendorStaticLibs") + func hwasanVendorStaticLibs(config android.Config) *[]string { - return config.Once("hwasanVendorStaticLibs", func() interface{} { + return config.Once(hwasanVendorStaticLibsKey, func() interface{} { return &[]string{} }).(*[]string) } diff --git a/java/android_resources.go b/java/android_resources.go index efd3e3dd0..44cb709e4 100644 --- a/java/android_resources.go +++ b/java/android_resources.go @@ -46,7 +46,7 @@ type overlayGlobResult struct { paths android.DirectorySortedPaths } -const overlayDataKey = "overlayDataKey" +var overlayDataKey = android.NewOnceKey("overlayDataKey") type globbedResourceDir struct { dir android.Path diff --git a/java/dexpreopt.go b/java/dexpreopt.go index bb72b7dec..33c46f4fe 100644 --- a/java/dexpreopt.go +++ b/java/dexpreopt.go @@ -81,12 +81,14 @@ func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool { return false } +var dexpreoptGlobalConfigKey = android.NewOnceKey("DexpreoptGlobalConfig") + func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.ModuleOutPath) android.ModuleOutPath { if d.dexpreoptDisabled(ctx) { return dexJarFile } - globalConfig := ctx.Config().Once("DexpreoptGlobalConfig", func() interface{} { + globalConfig := ctx.Config().Once(dexpreoptGlobalConfigKey, func() interface{} { if f := ctx.Config().DexpreoptGlobalConfig(); f != "" { ctx.AddNinjaFileDeps(f) globalConfig, err := dexpreopt.LoadGlobalConfig(f) diff --git a/java/hiddenapi.go b/java/hiddenapi.go index 983daa79c..c7eac733f 100644 --- a/java/hiddenapi.go +++ b/java/hiddenapi.go @@ -121,7 +121,7 @@ func hiddenAPIEncodeDex(ctx android.ModuleContext, output android.WritablePath, hiddenAPISaveDexInputs(ctx, dexInput) } -const hiddenAPIOutputsKey = "hiddenAPIOutputsKey" +var hiddenAPIOutputsKey = android.NewOnceKey("hiddenAPIOutputsKey") var hiddenAPIOutputsLock sync.Mutex diff --git a/java/sdk.go b/java/sdk.go index 988610f5b..0959be7de 100644 --- a/java/sdk.go +++ b/java/sdk.go @@ -28,7 +28,7 @@ func init() { android.RegisterPreSingletonType("sdk", sdkSingletonFactory) } -const sdkSingletonKey = "sdkSingletonKey" +var sdkSingletonKey = android.NewOnceKey("sdkSingletonKey") type sdkContext interface { // sdkVersion eturns the sdk_version property of the current module, or an empty string if it is not set. diff --git a/java/sdk_library.go b/java/sdk_library.go index ca3131c2f..1b0fe75e0 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -627,8 +627,10 @@ func (module *sdkLibrary) ImplementationJars(ctx android.BaseContext, sdkVersion } } +var javaSdkLibrariesKey = android.NewOnceKey("javaSdkLibraries") + func javaSdkLibraries(config android.Config) *[]string { - return config.Once("javaSdkLibraries", func() interface{} { + return config.Once(javaSdkLibrariesKey, func() interface{} { return &[]string{} }).(*[]string) }