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
diff --git a/Android.bp b/Android.bp
index 5e9c600..591e5c8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -78,6 +78,7 @@
         "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 a93baf6..bf11ba2 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -139,6 +139,7 @@
 
 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 @@
 // 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 1b56625..51d4703 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -51,8 +51,10 @@
 	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 953e6cf..ad812a4 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -879,7 +879,7 @@
 				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 f19f75c..6160506 100644
--- a/android/onceper.go
+++ b/android/onceper.go
@@ -24,8 +24,6 @@
 	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 @@
 	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 @@
 	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 @@
 	}).(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 0000000..d2ca9ad
--- /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 fbe10b5..0aee0bd 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -250,8 +250,8 @@
 	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 @@
 	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 d716ffa..b4b89d2 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -968,8 +968,10 @@
 	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 @@
 	}
 }
 
+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 fb567ba..d91735f 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -24,24 +24,24 @@
 	"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 @@
 	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 a341ab9..9363916 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -36,7 +36,8 @@
 	}
 )
 
-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 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 bcc4de3..fc2ed50 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -958,20 +958,26 @@
 	}
 }
 
+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 efd3e3d..44cb709 100644
--- a/java/android_resources.go
+++ b/java/android_resources.go
@@ -46,7 +46,7 @@
 	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 bb72b7d..33c46f4 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -81,12 +81,14 @@
 	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 983daa7..c7eac73 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -121,7 +121,7 @@
 	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 988610f..0959be7 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -28,7 +28,7 @@
 	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 ca3131c..1b0fe75 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -627,8 +627,10 @@
 	}
 }
 
+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)
 }