Merge "Abstract sdk_version string using sdkSpec type"
diff --git a/Android.bp b/Android.bp
index 0f7ef9e..9b55c8c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,7 @@
         "blueprint",
         "blueprint-bootstrap",
         "soong",
+        "soong-android-soongconfig",
         "soong-env",
         "soong-shared",
     ],
@@ -73,6 +74,7 @@
         "android/sdk.go",
         "android/sh_binary.go",
         "android/singleton.go",
+        "android/soong_config_modules.go",
         "android/testing.go",
         "android/util.go",
         "android/variable.go",
@@ -101,6 +103,7 @@
         "android/prebuilt_test.go",
         "android/prebuilt_etc_test.go",
         "android/rule_builder_test.go",
+        "android/soong_config_modules_test.go",
         "android/util_test.go",
         "android/variable_test.go",
         "android/visibility_test.go",
@@ -109,6 +112,20 @@
 }
 
 bootstrap_go_package {
+    name: "soong-android-soongconfig",
+    pkgPath: "android/soong/android/soongconfig",
+    deps: [
+        "blueprint",
+        "blueprint-parser",
+        "blueprint-proptools",
+    ],
+    srcs: [
+        "android/soongconfig/config.go",
+        "android/soongconfig/modules.go",
+    ],
+}
+
+bootstrap_go_package {
     name: "soong-cc-config",
     pkgPath: "android/soong/cc/config",
     deps: [
diff --git a/README.md b/README.md
index b6fda50..b1bb425 100644
--- a/README.md
+++ b/README.md
@@ -376,36 +376,14 @@
 be resolved by hand to a single module with any differences inside
 `target: { android: { }, host: { } }` blocks.
 
-## Build logic
+### Conditionals
 
-The build logic is written in Go using the
-[blueprint](http://godoc.org/github.com/google/blueprint) framework.  Build
-logic receives module definitions parsed into Go structures using reflection
-and produces build rules.  The build rules are collected by blueprint and
-written to a [ninja](http://ninja-build.org) build file.
-
-## Other documentation
-
-* [Best Practices](docs/best_practices.md)
-* [Build Performance](docs/perf.md)
-* [Generating CLion Projects](docs/clion.md)
-* [Generating YouCompleteMe/VSCode compile\_commands.json file](docs/compdb.md)
-* Make-specific documentation: [build/make/README.md](https://android.googlesource.com/platform/build/+/master/README.md)
-
-## FAQ
-
-### How do I write conditionals?
-
-Soong deliberately does not support conditionals in Android.bp files.  We
+Soong deliberately does not support most conditionals in Android.bp files.  We
 suggest removing most conditionals from the build.  See
 [Best Practices](docs/best_practices.md#removing-conditionals) for some
 examples on how to remove conditionals.
 
-In cases where build time conditionals are unavoidable, complexity in build
-rules that would require conditionals are handled in Go through Soong plugins.
-This allows Go language features to be used for better readability and
-testability, and implicit dependencies introduced by conditionals can be
-tracked.  Most conditionals supported natively by Soong are converted to a map
+Most conditionals supported natively by Soong are converted to a map
 property.  When building the module one of the properties in the map will be
 selected, and its values appended to the property with the same name at the
 top level of the module.
@@ -430,6 +408,106 @@
 be built.  When building for x86 the `generic.cpp` and 'x86.cpp' sources will
 be built.
 
+#### Soong Config Variables
+
+When converting vendor modules that contain conditionals, simple conditionals
+can be supported through Soong config variables using `soong_config_*`
+modules that describe the module types, variables and possible values:
+
+```
+soong_config_module_type {
+    name: "acme_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "acme",
+    variables: ["board", "feature"],
+    properties: ["cflags", "srcs"],
+}
+
+soong_config_string_variable {
+    name: "board",
+    values: ["soc_a", "soc_b"],
+}
+
+soong_config_bool_variable {
+    name: "feature",
+}
+```
+
+This example describes a new `acme_cc_defaults` module type that extends the
+`cc_defaults` module type, with two additional conditionals based on variables
+`board` and `feature`, which can affect properties `cflags` and `srcs`.
+
+The values of the variables can be set from a product's `BoardConfig.mk` file:
+```
+SOONG_CONFIG_NAMESPACES += acme
+SOONG_CONFIG_acme += \
+    board \
+    feature \
+
+SOONG_CONFIG_acme_board := soc_a
+SOONG_CONFIG_acme_feature := true
+```
+
+The `acme_cc_defaults` module type can be used anywhere after the definition in
+the file where it is defined, or can be imported into another file with:
+```
+soong_config_module_type_import {
+    from: "device/acme/Android.bp",
+    module_types: ["acme_cc_defaults"],
+}
+```
+
+It can used like any other module type:
+```
+acme_cc_defaults {
+    name: "acme_defaults",
+    cflags: ["-DGENERIC"],
+    soong_config_variables: {
+        board: {
+            soc_a: {
+                cflags: ["-DSOC_A"],
+            },
+            soc_b: {
+                cflags: ["-DSOC_B"],
+            },
+        },
+        feature: {
+            cflags: ["-DFEATURE"],
+        },
+    },
+}
+
+cc_library {
+    name: "libacme_foo",
+    defaults: ["acme_defaults"],
+    srcs: ["*.cpp"],
+}
+```
+
+With the `BoardConfig.mk` snippet above, libacme_foo would build with
+cflags "-DGENERIC -DSOC_A -DFEATURE".
+
+`soong_config_module_type` modules will work best when used to wrap defaults
+modules (`cc_defaults`, `java_defaults`, etc.), which can then be referenced
+by all of the vendor's other modules using the normal namespace and visibility
+rules.
+
+## Build logic
+
+The build logic is written in Go using the
+[blueprint](http://godoc.org/github.com/google/blueprint) framework.  Build
+logic receives module definitions parsed into Go structures using reflection
+and produces build rules.  The build rules are collected by blueprint and
+written to a [ninja](http://ninja-build.org) build file.
+
+## Other documentation
+
+* [Best Practices](docs/best_practices.md)
+* [Build Performance](docs/perf.md)
+* [Generating CLion Projects](docs/clion.md)
+* [Generating YouCompleteMe/VSCode compile\_commands.json file](docs/compdb.md)
+* Make-specific documentation: [build/make/README.md](https://android.googlesource.com/platform/build/+/master/README.md)
+
 ## Developing for Soong
 
 To load Soong code in a Go-aware IDE, create a directory outside your android tree and then:
diff --git a/android/config.go b/android/config.go
index b7b4b02..bab3477 100644
--- a/android/config.go
+++ b/android/config.go
@@ -29,6 +29,8 @@
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
+
+	"android/soong/android/soongconfig"
 )
 
 var Bool = proptools.Bool
@@ -67,19 +69,7 @@
 	*deviceConfig
 }
 
-type VendorConfig interface {
-	// Bool interprets the variable named `name` as a boolean, returning true if, after
-	// lowercasing, it matches one of "1", "y", "yes", "on", or "true". Unset, or any other
-	// value will return false.
-	Bool(name string) bool
-
-	// String returns the string value of `name`. If the variable was not set, it will
-	// return the empty string.
-	String(name string) string
-
-	// IsSet returns whether the variable `name` was set by Make.
-	IsSet(name string) bool
-}
+type VendorConfig soongconfig.SoongConfig
 
 type config struct {
 	FileConfigurableOptions
@@ -129,8 +119,6 @@
 	OncePer
 }
 
-type vendorConfig map[string]string
-
 type jsonConfigurable interface {
 	SetDefaultConfig()
 }
@@ -1142,21 +1130,7 @@
 }
 
 func (c *config) VendorConfig(name string) VendorConfig {
-	return vendorConfig(c.productVariables.VendorVars[name])
-}
-
-func (c vendorConfig) Bool(name string) bool {
-	v := strings.ToLower(c[name])
-	return v == "1" || v == "y" || v == "yes" || v == "on" || v == "true"
-}
-
-func (c vendorConfig) String(name string) string {
-	return c[name]
-}
-
-func (c vendorConfig) IsSet(name string) bool {
-	_, ok := c[name]
-	return ok
+	return soongconfig.Config(c.productVariables.VendorVars[name])
 }
 
 func (c *config) NdkAbis() bool {
diff --git a/android/hooks.go b/android/hooks.go
index 04ba69e..e8cd81b 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -34,6 +34,9 @@
 	AppendProperties(...interface{})
 	PrependProperties(...interface{})
 	CreateModule(ModuleFactory, ...interface{}) Module
+
+	registerScopedModuleType(name string, factory blueprint.ModuleFactory)
+	moduleFactories() map[string]blueprint.ModuleFactory
 }
 
 func AddLoadHook(m blueprint.Module, hook func(LoadHookContext)) {
@@ -52,6 +55,10 @@
 	module Module
 }
 
+func (l *loadHookContext) moduleFactories() map[string]blueprint.ModuleFactory {
+	return l.bp.ModuleFactories()
+}
+
 func (l *loadHookContext) AppendProperties(props ...interface{}) {
 	for _, p := range props {
 		err := proptools.AppendMatchingProperties(l.Module().base().customizableProperties,
@@ -101,6 +108,10 @@
 	return module
 }
 
+func (l *loadHookContext) registerScopedModuleType(name string, factory blueprint.ModuleFactory) {
+	l.bp.RegisterScopedModuleType(name, factory)
+}
+
 type InstallHookContext interface {
 	ModuleContext
 	Path() InstallPath
diff --git a/android/module.go b/android/module.go
index 4a72889..96c2e1e 100644
--- a/android/module.go
+++ b/android/module.go
@@ -62,6 +62,7 @@
 	ModuleName() string
 	ModuleDir() string
 	ModuleType() string
+	BlueprintsFile() string
 
 	ContainsProperty(name string) bool
 	Errorf(pos scanner.Position, fmt string, args ...interface{})
@@ -524,9 +525,13 @@
 	}
 }
 
+func initAndroidModuleBase(m Module) {
+	m.base().module = m
+}
+
 func InitAndroidModule(m Module) {
+	initAndroidModuleBase(m)
 	base := m.base()
-	base.module = m
 
 	m.AddProperties(
 		&base.nameProperties,
diff --git a/android/namespace.go b/android/namespace.go
index 64ad7e9..9d7e8ac 100644
--- a/android/namespace.go
+++ b/android/namespace.go
@@ -162,6 +162,12 @@
 	return namespace
 }
 
+// A NamelessModule can never be looked up by name.  It must still implement Name(), but the return
+// value doesn't have to be unique.
+type NamelessModule interface {
+	Nameless()
+}
+
 func (r *NameResolver) NewModule(ctx blueprint.NamespaceContext, moduleGroup blueprint.ModuleGroup, module blueprint.Module) (namespace blueprint.Namespace, errs []error) {
 	// if this module is a namespace, then save it to our list of namespaces
 	newNamespace, ok := module.(*NamespaceModule)
@@ -173,6 +179,10 @@
 		return nil, nil
 	}
 
+	if _, ok := module.(NamelessModule); ok {
+		return nil, nil
+	}
+
 	// if this module is not a namespace, then save it into the appropriate namespace
 	ns := r.findNamespaceFromCtx(ctx)
 
diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go
new file mode 100644
index 0000000..bdec64b
--- /dev/null
+++ b/android/soong_config_modules.go
@@ -0,0 +1,369 @@
+// 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
+
+// This file provides module types that implement wrapper module types that add conditionals on
+// Soong config variables.
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+	"text/scanner"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android/soongconfig"
+)
+
+func init() {
+	RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
+	RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
+	RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
+	RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+}
+
+type soongConfigModuleTypeImport struct {
+	ModuleBase
+	properties soongConfigModuleTypeImportProperties
+}
+
+type soongConfigModuleTypeImportProperties struct {
+	From         string
+	Module_types []string
+}
+
+// soong_config_module_type_import imports module types with conditionals on Soong config
+// variables from another Android.bp file.  The imported module type will exist for all
+// modules after the import in the Android.bp file.
+//
+// For example, an Android.bp file could have:
+//
+//     soong_config_module_type_import {
+//         from: "device/acme/Android.bp.bp",
+//         module_types: ["acme_cc_defaults"],
+//     }
+//
+//     acme_cc_defaults {
+//         name: "acme_defaults",
+//         cflags: ["-DGENERIC"],
+//         soong_config_variables: {
+//             board: {
+//                 soc_a: {
+//                     cflags: ["-DSOC_A"],
+//                 },
+//                 soc_b: {
+//                     cflags: ["-DSOC_B"],
+//                 },
+//             },
+//             feature: {
+//                 cflags: ["-DFEATURE"],
+//             },
+//         },
+//     }
+//
+//     cc_library {
+//         name: "libacme_foo",
+//         defaults: ["acme_defaults"],
+//         srcs: ["*.cpp"],
+//     }
+//
+// And device/acme/Android.bp could have:
+//
+//     soong_config_module_type {
+//         name: "acme_cc_defaults",
+//         module_type: "cc_defaults",
+//         config_namespace: "acme",
+//         variables: ["board", "feature"],
+//         properties: ["cflags", "srcs"],
+//     }
+//
+//     soong_config_string_variable {
+//         name: "board",
+//         values: ["soc_a", "soc_b"],
+//     }
+//
+//     soong_config_bool_variable {
+//         name: "feature",
+//     }
+//
+// If an acme BoardConfig.mk file contained:
+//
+//     SOONG_CONFIG_NAMESPACES += acme
+//     SOONG_CONFIG_acme += \
+//         board \
+//         feature \
+//
+//     SOONG_CONFIG_acme_board := soc_a
+//     SOONG_CONFIG_acme_feature := true
+//
+// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
+func soongConfigModuleTypeImportFactory() Module {
+	module := &soongConfigModuleTypeImport{}
+
+	module.AddProperties(&module.properties)
+	AddLoadHook(module, func(ctx LoadHookContext) {
+		importModuleTypes(ctx, module.properties.From, module.properties.Module_types...)
+	})
+
+	initAndroidModuleBase(module)
+	return module
+}
+
+func (m *soongConfigModuleTypeImport) Name() string {
+	return "soong_config_module_type_import_" + soongconfig.CanonicalizeToProperty(m.properties.From)
+}
+
+func (*soongConfigModuleTypeImport) Nameless()                                 {}
+func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {}
+
+// Create dummy modules for soong_config_module_type and soong_config_*_variable
+
+type soongConfigModuleTypeModule struct {
+	ModuleBase
+	properties soongconfig.ModuleTypeProperties
+}
+
+// soong_config_module_type defines module types with conditionals on Soong config
+// variables from another Android.bp file.  The new module type will exist for all
+// modules after the definition in an Android.bp file, and can be imported into other
+// Android.bp files using soong_config_module_type_import.
+//
+// For example, an Android.bp file could have:
+//
+//     soong_config_module_type {
+//         name: "acme_cc_defaults",
+//         module_type: "cc_defaults",
+//         config_namespace: "acme",
+//         variables: ["board", "feature"],
+//         properties: ["cflags", "srcs"],
+//     }
+//
+//     soong_config_string_variable {
+//         name: "board",
+//         values: ["soc_a", "soc_b"],
+//     }
+//
+//     soong_config_bool_variable {
+//         name: "feature",
+//     }
+//
+//     acme_cc_defaults {
+//         name: "acme_defaults",
+//         cflags: ["-DGENERIC"],
+//         soong_config_variables: {
+//             board: {
+//                 soc_a: {
+//                     cflags: ["-DSOC_A"],
+//                 },
+//                 soc_b: {
+//                     cflags: ["-DSOC_B"],
+//                 },
+//             },
+//             feature: {
+//                 cflags: ["-DFEATURE"],
+//             },
+//         },
+//     }
+//
+//     cc_library {
+//         name: "libacme_foo",
+//         defaults: ["acme_defaults"],
+//         srcs: ["*.cpp"],
+//     }
+//
+// And device/acme/Android.bp could have:
+//
+// If an acme BoardConfig.mk file contained:
+//
+//     SOONG_CONFIG_NAMESPACES += acme
+//     SOONG_CONFIG_acme += \
+//         board \
+//         feature \
+//
+//     SOONG_CONFIG_acme_board := soc_a
+//     SOONG_CONFIG_acme_feature := true
+//
+// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
+func soongConfigModuleTypeFactory() Module {
+	module := &soongConfigModuleTypeModule{}
+
+	module.AddProperties(&module.properties)
+
+	AddLoadHook(module, func(ctx LoadHookContext) {
+		// A soong_config_module_type module should implicitly import itself.
+		importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name)
+	})
+
+	initAndroidModuleBase(module)
+
+	return module
+}
+
+func (m *soongConfigModuleTypeModule) Name() string {
+	return m.properties.Name
+}
+func (*soongConfigModuleTypeModule) Nameless()                                     {}
+func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
+
+type soongConfigStringVariableDummyModule struct {
+	ModuleBase
+	properties       soongconfig.VariableProperties
+	stringProperties soongconfig.StringVariableProperties
+}
+
+type soongConfigBoolVariableDummyModule struct {
+	ModuleBase
+	properties soongconfig.VariableProperties
+}
+
+// soong_config_string_variable defines a variable and a set of possible string values for use
+// in a soong_config_module_type definition.
+func soongConfigStringVariableDummyFactory() Module {
+	module := &soongConfigStringVariableDummyModule{}
+	module.AddProperties(&module.properties, &module.stringProperties)
+	initAndroidModuleBase(module)
+	return module
+}
+
+// soong_config_string_variable defines a variable with true or false values for use
+// in a soong_config_module_type definition.
+func soongConfigBoolVariableDummyFactory() Module {
+	module := &soongConfigBoolVariableDummyModule{}
+	module.AddProperties(&module.properties)
+	initAndroidModuleBase(module)
+	return module
+}
+
+func (m *soongConfigStringVariableDummyModule) Name() string {
+	return m.properties.Name
+}
+func (*soongConfigStringVariableDummyModule) Nameless()                                     {}
+func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
+
+func (m *soongConfigBoolVariableDummyModule) Name() string {
+	return m.properties.Name
+}
+func (*soongConfigBoolVariableDummyModule) Nameless()                                     {}
+func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
+
+func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) {
+	from = filepath.Clean(from)
+	if filepath.Ext(from) != ".bp" {
+		ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from)
+		return
+	}
+
+	if strings.HasPrefix(from, "../") {
+		ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree",
+			from)
+		return
+	}
+
+	moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from)
+	if moduleTypeDefinitions == nil {
+		return
+	}
+	for _, moduleType := range moduleTypes {
+		if factory, ok := moduleTypeDefinitions[moduleType]; ok {
+			ctx.registerScopedModuleType(moduleType, factory)
+		} else {
+			ctx.PropertyErrorf("module_types", "module type %q not defined in %q",
+				moduleType, from)
+		}
+	}
+}
+
+// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file.  It caches the
+// result so each file is only parsed once.
+func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory {
+	type onceKeyType string
+	key := NewCustomOnceKey(onceKeyType(filepath.Clean(from)))
+
+	reportErrors := func(ctx LoadHookContext, filename string, errs ...error) {
+		for _, err := range errs {
+			if parseErr, ok := err.(*parser.ParseError); ok {
+				ctx.Errorf(parseErr.Pos, "%s", parseErr.Err)
+			} else {
+				ctx.Errorf(scanner.Position{Filename: filename}, "%s", err)
+			}
+		}
+	}
+
+	return ctx.Config().Once(key, func() interface{} {
+		r, err := ctx.Config().fs.Open(from)
+		if err != nil {
+			ctx.PropertyErrorf("from", "failed to open %q: %s", from, err)
+			return (map[string]blueprint.ModuleFactory)(nil)
+		}
+
+		mtDef, errs := soongconfig.Parse(r, from)
+
+		if len(errs) > 0 {
+			reportErrors(ctx, from, errs...)
+			return (map[string]blueprint.ModuleFactory)(nil)
+		}
+
+		globalModuleTypes := ctx.moduleFactories()
+
+		factories := make(map[string]blueprint.ModuleFactory)
+
+		for name, moduleType := range mtDef.ModuleTypes {
+			factory := globalModuleTypes[moduleType.BaseModuleType]
+			if factory != nil {
+				factories[name] = soongConfigModuleFactory(factory, moduleType)
+			} else {
+				reportErrors(ctx, from,
+					fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
+			}
+		}
+
+		if ctx.Failed() {
+			return (map[string]blueprint.ModuleFactory)(nil)
+		}
+
+		return factories
+	}).(map[string]blueprint.ModuleFactory)
+}
+
+// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns
+// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config
+// variables.
+func soongConfigModuleFactory(factory blueprint.ModuleFactory,
+	moduleType *soongconfig.ModuleType) blueprint.ModuleFactory {
+
+	conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType)
+	if conditionalFactoryProps.IsValid() {
+		return func() (blueprint.Module, []interface{}) {
+			module, props := factory()
+
+			conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps.Elem())
+			props = append(props, conditionalProps.Interface())
+
+			AddLoadHook(module, func(ctx LoadHookContext) {
+				config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
+				for _, ps := range soongconfig.PropertiesToApply(moduleType, conditionalProps, config) {
+					ctx.AppendProperties(ps)
+				}
+			})
+
+			return module, props
+		}
+	} else {
+		return factory
+	}
+}
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
new file mode 100644
index 0000000..66feba8
--- /dev/null
+++ b/android/soong_config_modules_test.go
@@ -0,0 +1,141 @@
+// 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 (
+	"reflect"
+	"testing"
+)
+
+type soongConfigTestModule struct {
+	ModuleBase
+	props soongConfigTestModuleProperties
+}
+
+type soongConfigTestModuleProperties struct {
+	Cflags []string
+}
+
+func soongConfigTestModuleFactory() Module {
+	m := &soongConfigTestModule{}
+	m.AddProperties(&m.props)
+	InitAndroidModule(m)
+	return m
+}
+
+func (t soongConfigTestModule) GenerateAndroidBuildActions(ModuleContext) {}
+
+func TestSoongConfigModule(t *testing.T) {
+	configBp := `
+		soong_config_module_type {
+			name: "acme_test_defaults",
+			module_type: "test_defaults",
+			config_namespace: "acme",
+			variables: ["board", "feature1", "feature2", "feature3"],
+			properties: ["cflags", "srcs"],
+		}
+
+		soong_config_string_variable {
+			name: "board",
+			values: ["soc_a", "soc_b"],
+		}
+
+		soong_config_bool_variable {
+			name: "feature1",
+		}
+
+		soong_config_bool_variable {
+			name: "feature2",
+		}
+
+		soong_config_bool_variable {
+			name: "feature3",
+		}
+	`
+
+	importBp := `
+		soong_config_module_type_import {
+			from: "SoongConfig.bp",
+			module_types: ["acme_test_defaults"],
+		}
+	`
+
+	bp := `
+		acme_test_defaults {
+			name: "foo",
+			cflags: ["-DGENERIC"],
+			soong_config_variables: {
+				board: {
+					soc_a: {
+						cflags: ["-DSOC_A"],
+					},
+					soc_b: {
+						cflags: ["-DSOC_B"],
+					},
+				},
+				feature1: {
+					cflags: ["-DFEATURE1"],
+				},
+				feature2: {
+					cflags: ["-DFEATURE2"],
+				},
+				feature3: {
+					cflags: ["-DFEATURE3"],
+				},
+			},
+		}
+    `
+
+	run := func(t *testing.T, bp string, fs map[string][]byte) {
+		config := TestConfig(buildDir, nil, bp, fs)
+
+		config.TestProductVariables.VendorVars = map[string]map[string]string{
+			"acme": map[string]string{
+				"board":    "soc_a",
+				"feature1": "true",
+				"feature2": "false",
+				// FEATURE3 unset
+			},
+		}
+
+		ctx := NewTestContext()
+		ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
+		ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
+		ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
+		ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+		ctx.RegisterModuleType("test_defaults", soongConfigTestModuleFactory)
+		ctx.Register(config)
+
+		_, errs := ctx.ParseBlueprintsFiles("Android.bp")
+		FailIfErrored(t, errs)
+		_, errs = ctx.PrepareBuildActions(config)
+		FailIfErrored(t, errs)
+
+		foo := ctx.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
+		if g, w := foo.props.Cflags, []string{"-DGENERIC", "-DSOC_A", "-DFEATURE1"}; !reflect.DeepEqual(g, w) {
+			t.Errorf("wanted foo cflags %q, got %q", w, g)
+		}
+	}
+
+	t.Run("single file", func(t *testing.T) {
+		run(t, configBp+bp, nil)
+	})
+
+	t.Run("import", func(t *testing.T) {
+		run(t, importBp+bp, map[string][]byte{
+			"SoongConfig.bp": []byte(configBp),
+		})
+	})
+}
diff --git a/android/soongconfig/config.go b/android/soongconfig/config.go
new file mode 100644
index 0000000..39a776c
--- /dev/null
+++ b/android/soongconfig/config.go
@@ -0,0 +1,51 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package soongconfig
+
+import "strings"
+
+type SoongConfig interface {
+	// Bool interprets the variable named `name` as a boolean, returning true if, after
+	// lowercasing, it matches one of "1", "y", "yes", "on", or "true". Unset, or any other
+	// value will return false.
+	Bool(name string) bool
+
+	// String returns the string value of `name`. If the variable was not set, it will
+	// return the empty string.
+	String(name string) string
+
+	// IsSet returns whether the variable `name` was set by Make.
+	IsSet(name string) bool
+}
+
+func Config(vars map[string]string) SoongConfig {
+	return soongConfig(vars)
+}
+
+type soongConfig map[string]string
+
+func (c soongConfig) Bool(name string) bool {
+	v := strings.ToLower(c[name])
+	return v == "1" || v == "y" || v == "yes" || v == "on" || v == "true"
+}
+
+func (c soongConfig) String(name string) string {
+	return c[name]
+}
+
+func (c soongConfig) IsSet(name string) bool {
+	_, ok := c[name]
+	return ok
+}
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
new file mode 100644
index 0000000..aa4f5c5
--- /dev/null
+++ b/android/soongconfig/modules.go
@@ -0,0 +1,517 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package soongconfig
+
+import (
+	"fmt"
+	"io"
+	"reflect"
+	"sort"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/proptools"
+)
+
+var soongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
+
+// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file.  It caches the
+// result so each file is only parsed once.
+func Parse(r io.Reader, from string) (*SoongConfigDefinition, []error) {
+	scope := parser.NewScope(nil)
+	file, errs := parser.ParseAndEval(from, r, scope)
+
+	if len(errs) > 0 {
+		return nil, errs
+	}
+
+	mtDef := &SoongConfigDefinition{
+		ModuleTypes: make(map[string]*ModuleType),
+		variables:   make(map[string]soongConfigVariable),
+	}
+
+	for _, def := range file.Defs {
+		switch def := def.(type) {
+		case *parser.Module:
+			newErrs := processImportModuleDef(mtDef, def)
+
+			if len(newErrs) > 0 {
+				errs = append(errs, newErrs...)
+			}
+
+		case *parser.Assignment:
+			// Already handled via Scope object
+		default:
+			panic("unknown definition type")
+		}
+	}
+
+	if len(errs) > 0 {
+		return nil, errs
+	}
+
+	for name, moduleType := range mtDef.ModuleTypes {
+		for _, varName := range moduleType.variableNames {
+			if v, ok := mtDef.variables[varName]; ok {
+				moduleType.Variables = append(moduleType.Variables, v)
+			} else {
+				return nil, []error{
+					fmt.Errorf("unknown variable %q in module type %q", varName, name),
+				}
+			}
+		}
+	}
+
+	return mtDef, nil
+}
+
+func processImportModuleDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
+	switch def.Type {
+	case "soong_config_module_type":
+		return processModuleTypeDef(v, def)
+	case "soong_config_string_variable":
+		return processStringVariableDef(v, def)
+	case "soong_config_bool_variable":
+		return processBoolVariableDef(v, def)
+	default:
+		// Unknown module types will be handled when the file is parsed as a normal
+		// Android.bp file.
+	}
+
+	return nil
+}
+
+type ModuleTypeProperties struct {
+	// the name of the new module type.  Unlike most modules, this name does not need to be unique,
+	// although only one module type with any name will be importable into an Android.bp file.
+	Name string
+
+	// the module type that this module type will extend.
+	Module_type string
+
+	// the SOONG_CONFIG_NAMESPACE value from a BoardConfig.mk that this module type will read
+	// configuration variables from.
+	Config_namespace string
+
+	// the list of SOONG_CONFIG variables that this module type will read
+	Variables []string
+
+	// the list of properties that this module type will extend.
+	Properties []string
+}
+
+func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
+
+	props := &ModuleTypeProperties{}
+
+	_, errs = proptools.UnpackProperties(def.Properties, props)
+	if len(errs) > 0 {
+		return errs
+	}
+
+	if props.Name == "" {
+		errs = append(errs, fmt.Errorf("name property must be set"))
+	}
+
+	if props.Config_namespace == "" {
+		errs = append(errs, fmt.Errorf("config_namespace property must be set"))
+	}
+
+	if props.Module_type == "" {
+		errs = append(errs, fmt.Errorf("module_type property must be set"))
+	}
+
+	if len(errs) > 0 {
+		return errs
+	}
+
+	mt := &ModuleType{
+		affectableProperties: props.Properties,
+		ConfigNamespace:      props.Config_namespace,
+		BaseModuleType:       props.Module_type,
+		variableNames:        props.Variables,
+	}
+	v.ModuleTypes[props.Name] = mt
+
+	return nil
+}
+
+type VariableProperties struct {
+	Name string
+}
+
+type StringVariableProperties struct {
+	Values []string
+}
+
+func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
+	stringProps := &StringVariableProperties{}
+
+	base, errs := processVariableDef(def, stringProps)
+	if len(errs) > 0 {
+		return errs
+	}
+
+	if len(stringProps.Values) == 0 {
+		return []error{fmt.Errorf("values property must be set")}
+	}
+
+	v.variables[base.variable] = &stringVariable{
+		baseVariable: base,
+		values:       CanonicalizeToProperties(stringProps.Values),
+	}
+
+	return nil
+}
+
+func processBoolVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
+	base, errs := processVariableDef(def)
+	if len(errs) > 0 {
+		return errs
+	}
+
+	v.variables[base.variable] = &boolVariable{
+		baseVariable: base,
+	}
+
+	return nil
+}
+
+func processVariableDef(def *parser.Module,
+	extraProps ...interface{}) (cond baseVariable, errs []error) {
+
+	props := &VariableProperties{}
+
+	allProps := append([]interface{}{props}, extraProps...)
+
+	_, errs = proptools.UnpackProperties(def.Properties, allProps...)
+	if len(errs) > 0 {
+		return baseVariable{}, errs
+	}
+
+	if props.Name == "" {
+		return baseVariable{}, []error{fmt.Errorf("name property must be set")}
+	}
+
+	return baseVariable{
+		variable: props.Name,
+	}, nil
+}
+
+type SoongConfigDefinition struct {
+	ModuleTypes map[string]*ModuleType
+
+	variables map[string]soongConfigVariable
+}
+
+// CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
+// property layout for the Soong config variables, with each possible value an interface{} that
+// contains a nil pointer to another newly constructed type that contains the affectable properties.
+// The reflect.Value will be cloned for each call to the Soong config module type's factory method.
+//
+// For example, the acme_cc_defaults example above would
+// produce a reflect.Value whose type is:
+// *struct {
+//     Soong_config_variables struct {
+//         Board struct {
+//             Soc_a interface{}
+//             Soc_b interface{}
+//         }
+//     }
+// }
+// And whose value is:
+// &{
+//     Soong_config_variables: {
+//         Board: {
+//             Soc_a: (*struct{ Cflags []string })(nil),
+//             Soc_b: (*struct{ Cflags []string })(nil),
+//         },
+//     },
+// }
+func CreateProperties(factory blueprint.ModuleFactory, moduleType *ModuleType) reflect.Value {
+	var fields []reflect.StructField
+
+	_, factoryProps := factory()
+	affectablePropertiesType := createAffectablePropertiesType(moduleType.affectableProperties, factoryProps)
+	if affectablePropertiesType == nil {
+		return reflect.Value{}
+	}
+
+	for _, c := range moduleType.Variables {
+		fields = append(fields, reflect.StructField{
+			Name: proptools.FieldNameForProperty(c.variableProperty()),
+			Type: c.variableValuesType(),
+		})
+	}
+
+	typ := reflect.StructOf([]reflect.StructField{{
+		Name: soongConfigProperty,
+		Type: reflect.StructOf(fields),
+	}})
+
+	props := reflect.New(typ)
+	structConditions := props.Elem().FieldByName(soongConfigProperty)
+
+	for i, c := range moduleType.Variables {
+		c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
+	}
+
+	return props
+}
+
+// createAffectablePropertiesType creates a reflect.Type of a struct that has a field for each affectable property
+// that exists in factoryProps.
+func createAffectablePropertiesType(affectableProperties []string, factoryProps []interface{}) reflect.Type {
+	affectableProperties = append([]string(nil), affectableProperties...)
+	sort.Strings(affectableProperties)
+
+	var recurse func(prefix string, aps []string) ([]string, reflect.Type)
+	recurse = func(prefix string, aps []string) ([]string, reflect.Type) {
+		var fields []reflect.StructField
+
+		for len(affectableProperties) > 0 {
+			p := affectableProperties[0]
+			if !strings.HasPrefix(affectableProperties[0], prefix) {
+				break
+			}
+			affectableProperties = affectableProperties[1:]
+
+			nestedProperty := strings.TrimPrefix(p, prefix)
+			if i := strings.IndexRune(nestedProperty, '.'); i >= 0 {
+				var nestedType reflect.Type
+				nestedPrefix := nestedProperty[:i+1]
+
+				affectableProperties, nestedType = recurse(prefix+nestedPrefix, affectableProperties)
+
+				if nestedType != nil {
+					nestedFieldName := proptools.FieldNameForProperty(strings.TrimSuffix(nestedPrefix, "."))
+
+					fields = append(fields, reflect.StructField{
+						Name: nestedFieldName,
+						Type: nestedType,
+					})
+				}
+			} else {
+				typ := typeForPropertyFromPropertyStructs(factoryProps, p)
+				if typ != nil {
+					fields = append(fields, reflect.StructField{
+						Name: proptools.FieldNameForProperty(nestedProperty),
+						Type: typ,
+					})
+				}
+			}
+		}
+
+		var typ reflect.Type
+		if len(fields) > 0 {
+			typ = reflect.StructOf(fields)
+		}
+		return affectableProperties, typ
+	}
+
+	affectableProperties, typ := recurse("", affectableProperties)
+	if len(affectableProperties) > 0 {
+		panic(fmt.Errorf("didn't handle all affectable properties"))
+	}
+
+	if typ != nil {
+		return reflect.PtrTo(typ)
+	}
+
+	return nil
+}
+
+func typeForPropertyFromPropertyStructs(psList []interface{}, property string) reflect.Type {
+	for _, ps := range psList {
+		if typ := typeForPropertyFromPropertyStruct(ps, property); typ != nil {
+			return typ
+		}
+	}
+
+	return nil
+}
+
+func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.Type {
+	v := reflect.ValueOf(ps)
+	for len(property) > 0 {
+		if !v.IsValid() {
+			return nil
+		}
+
+		if v.Kind() == reflect.Interface {
+			if v.IsNil() {
+				return nil
+			} else {
+				v = v.Elem()
+			}
+		}
+
+		if v.Kind() == reflect.Ptr {
+			if v.IsNil() {
+				v = reflect.Zero(v.Type().Elem())
+			} else {
+				v = v.Elem()
+			}
+		}
+
+		if v.Kind() != reflect.Struct {
+			return nil
+		}
+
+		if index := strings.IndexRune(property, '.'); index >= 0 {
+			prefix := property[:index]
+			property = property[index+1:]
+
+			v = v.FieldByName(proptools.FieldNameForProperty(prefix))
+		} else {
+			f := v.FieldByName(proptools.FieldNameForProperty(property))
+			if !f.IsValid() {
+				return nil
+			}
+			return f.Type()
+		}
+	}
+	return nil
+}
+
+// PropertiesToApply returns the applicable properties from a ModuleType that should be applied
+// based on SoongConfig values.
+func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) []interface{} {
+	var ret []interface{}
+	props = props.Elem().FieldByName(soongConfigProperty)
+	for i, c := range moduleType.Variables {
+		if ps := c.PropertiesToApply(config, props.Field(i)); ps != nil {
+			ret = append(ret, ps)
+		}
+	}
+	return ret
+}
+
+type ModuleType struct {
+	BaseModuleType  string
+	ConfigNamespace string
+	Variables       []soongConfigVariable
+
+	affectableProperties []string
+	variableNames        []string
+}
+
+type soongConfigVariable interface {
+	// variableProperty returns the name of the variable.
+	variableProperty() string
+
+	// conditionalValuesType returns a reflect.Type that contains an interface{} for each possible value.
+	variableValuesType() reflect.Type
+
+	// initializeProperties is passed a reflect.Value of the reflect.Type returned by conditionalValuesType and a
+	// reflect.Type of the affectable properties, and should initialize each interface{} in the reflect.Value with
+	// the zero value of the affectable properties type.
+	initializeProperties(v reflect.Value, typ reflect.Type)
+
+	// PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
+	// to the module.
+	PropertiesToApply(config SoongConfig, values reflect.Value) interface{}
+}
+
+type baseVariable struct {
+	variable string
+}
+
+func (c *baseVariable) variableProperty() string {
+	return CanonicalizeToProperty(c.variable)
+}
+
+type stringVariable struct {
+	baseVariable
+	values []string
+}
+
+func (s *stringVariable) variableValuesType() reflect.Type {
+	var fields []reflect.StructField
+
+	for _, v := range s.values {
+		fields = append(fields, reflect.StructField{
+			Name: proptools.FieldNameForProperty(v),
+			Type: emptyInterfaceType,
+		})
+	}
+
+	return reflect.StructOf(fields)
+}
+
+func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
+	for i := range s.values {
+		v.Field(i).Set(reflect.Zero(typ))
+	}
+}
+
+func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
+	for j, v := range s.values {
+		if config.String(s.variable) == v {
+			return values.Field(j).Interface()
+		}
+	}
+
+	return nil
+}
+
+type boolVariable struct {
+	baseVariable
+}
+
+func (b boolVariable) variableValuesType() reflect.Type {
+	return emptyInterfaceType
+}
+
+func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
+	v.Set(reflect.Zero(typ))
+}
+
+func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) interface{} {
+	if config.Bool(b.variable) {
+		return values.Interface()
+	}
+
+	return nil
+}
+
+func CanonicalizeToProperty(v string) string {
+	return strings.Map(func(r rune) rune {
+		switch {
+		case r >= 'A' && r <= 'Z',
+			r >= 'a' && r <= 'z',
+			r >= '0' && r <= '9',
+			r == '_':
+			return r
+		default:
+			return '_'
+		}
+	}, v)
+}
+
+func CanonicalizeToProperties(values []string) []string {
+	ret := make([]string, len(values))
+	for i, v := range values {
+		ret[i] = CanonicalizeToProperty(v)
+	}
+	return ret
+}
+
+type emptyInterfaceStruct struct {
+	i interface{}
+}
+
+var emptyInterfaceType = reflect.TypeOf(emptyInterfaceStruct{}).Field(0).Type
diff --git a/android/soongconfig/modules_test.go b/android/soongconfig/modules_test.go
new file mode 100644
index 0000000..4190016
--- /dev/null
+++ b/android/soongconfig/modules_test.go
@@ -0,0 +1,249 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package soongconfig
+
+import (
+	"reflect"
+	"testing"
+)
+
+func Test_CanonicalizeToProperty(t *testing.T) {
+	tests := []struct {
+		name string
+		arg  string
+		want string
+	}{
+		{
+			name: "lowercase",
+			arg:  "board",
+			want: "board",
+		},
+		{
+			name: "uppercase",
+			arg:  "BOARD",
+			want: "BOARD",
+		},
+		{
+			name: "numbers",
+			arg:  "BOARD123",
+			want: "BOARD123",
+		},
+		{
+			name: "underscore",
+			arg:  "TARGET_BOARD",
+			want: "TARGET_BOARD",
+		},
+		{
+			name: "dash",
+			arg:  "TARGET-BOARD",
+			want: "TARGET_BOARD",
+		},
+		{
+			name: "unicode",
+			arg:  "boardλ",
+			want: "board_",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := CanonicalizeToProperty(tt.arg); got != tt.want {
+				t.Errorf("canonicalizeToProperty() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_typeForPropertyFromPropertyStruct(t *testing.T) {
+	tests := []struct {
+		name     string
+		ps       interface{}
+		property string
+		want     string
+	}{
+		{
+			name: "string",
+			ps: struct {
+				A string
+			}{},
+			property: "a",
+			want:     "string",
+		},
+		{
+			name: "list",
+			ps: struct {
+				A []string
+			}{},
+			property: "a",
+			want:     "[]string",
+		},
+		{
+			name: "missing",
+			ps: struct {
+				A []string
+			}{},
+			property: "b",
+			want:     "",
+		},
+		{
+			name: "nested",
+			ps: struct {
+				A struct {
+					B string
+				}
+			}{},
+			property: "a.b",
+			want:     "string",
+		},
+		{
+			name: "missing nested",
+			ps: struct {
+				A struct {
+					B string
+				}
+			}{},
+			property: "a.c",
+			want:     "",
+		},
+		{
+			name: "not a struct",
+			ps: struct {
+				A string
+			}{},
+			property: "a.b",
+			want:     "",
+		},
+		{
+			name: "nested pointer",
+			ps: struct {
+				A *struct {
+					B string
+				}
+			}{},
+			property: "a.b",
+			want:     "string",
+		},
+		{
+			name: "nested interface",
+			ps: struct {
+				A interface{}
+			}{
+				A: struct {
+					B string
+				}{},
+			},
+			property: "a.b",
+			want:     "string",
+		},
+		{
+			name: "nested interface pointer",
+			ps: struct {
+				A interface{}
+			}{
+				A: &struct {
+					B string
+				}{},
+			},
+			property: "a.b",
+			want:     "string",
+		},
+		{
+			name: "nested interface nil pointer",
+			ps: struct {
+				A interface{}
+			}{
+				A: (*struct {
+					B string
+				})(nil),
+			},
+			property: "a.b",
+			want:     "string",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			typ := typeForPropertyFromPropertyStruct(tt.ps, tt.property)
+			got := ""
+			if typ != nil {
+				got = typ.String()
+			}
+			if got != tt.want {
+				t.Errorf("typeForPropertyFromPropertyStruct() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_createAffectablePropertiesType(t *testing.T) {
+	tests := []struct {
+		name                 string
+		affectableProperties []string
+		factoryProps         interface{}
+		want                 string
+	}{
+		{
+			name:                 "string",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Cflags string
+			}{},
+			want: "*struct { Cflags string }",
+		},
+		{
+			name:                 "list",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Cflags []string
+			}{},
+			want: "*struct { Cflags []string }",
+		},
+		{
+			name:                 "string pointer",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Cflags *string
+			}{},
+			want: "*struct { Cflags *string }",
+		},
+		{
+			name:                 "subset",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Cflags  string
+				Ldflags string
+			}{},
+			want: "*struct { Cflags string }",
+		},
+		{
+			name:                 "none",
+			affectableProperties: []string{"cflags"},
+			factoryProps: struct {
+				Ldflags string
+			}{},
+			want: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			typ := createAffectablePropertiesType(tt.affectableProperties, []interface{}{tt.factoryProps})
+			got := ""
+			if typ != nil {
+				got = typ.String()
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("createAffectablePropertiesType() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/android/variable_test.go b/android/variable_test.go
index 451d43d..751677f 100644
--- a/android/variable_test.go
+++ b/android/variable_test.go
@@ -148,7 +148,7 @@
 		clonedProps := proptools.CloneProperties(reflect.ValueOf(props)).Interface()
 		m.AddProperties(clonedProps)
 
-		// Set a default variableProperties, this will be used as the input to the property struct filter
+		// Set a default soongConfigVariableProperties, this will be used as the input to the property struct filter
 		// for this test module.
 		m.variableProperties = testProductVariableProperties
 		InitAndroidModule(m)
diff --git a/dexpreopt/Android.bp b/dexpreopt/Android.bp
index b8f7ea6..c5f24e2 100644
--- a/dexpreopt/Android.bp
+++ b/dexpreopt/Android.bp
@@ -4,7 +4,6 @@
     srcs: [
         "config.go",
         "dexpreopt.go",
-        "testing.go",
     ],
     testSrcs: [
         "dexpreopt_test.go",
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 72c01d0..2a929c5 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -16,16 +16,14 @@
 
 import (
 	"encoding/json"
-	"fmt"
 	"strings"
 
-	"github.com/google/blueprint"
-
 	"android/soong/android"
 )
 
 // GlobalConfig stores the configuration for dex preopting. The fields are set
-// from product variables via dex_preopt_config.mk.
+// from product variables via dex_preopt_config.mk, except for SoongConfig
+// which come from CreateGlobalSoongConfig.
 type GlobalConfig struct {
 	DisablePreopt        bool     // disable preopt for all modules
 	DisablePreoptModules []string // modules with preopt disabled by product-specific config
@@ -84,6 +82,8 @@
 	BootFlags         string               // extra flags to pass to dex2oat for the boot image
 	Dex2oatImageXmx   string               // max heap size for dex2oat for the boot image
 	Dex2oatImageXms   string               // initial heap size for dex2oat for the boot image
+
+	SoongConfig GlobalSoongConfig // settings read from dexpreopt_soong.config
 }
 
 // GlobalSoongConfig contains the global config that is generated from Soong,
@@ -179,9 +179,12 @@
 	return constructPath(ctx, path).(android.WritablePath)
 }
 
-// ParseGlobalConfig parses the given data assumed to be read from the global
-// dexpreopt.config file into a GlobalConfig struct.
-func ParseGlobalConfig(ctx android.PathContext, data []byte) (GlobalConfig, error) {
+// LoadGlobalConfig reads the global dexpreopt.config file into a GlobalConfig
+// struct, except the SoongConfig field which is set from the provided
+// soongConfig argument. LoadGlobalConfig is used directly in Soong and in
+// dexpreopt_gen called from Make to read the $OUT/dexpreopt.config written by
+// Make.
+func LoadGlobalConfig(ctx android.PathContext, data []byte, soongConfig GlobalSoongConfig) (GlobalConfig, error) {
 	type GlobalJSONConfig struct {
 		GlobalConfig
 
@@ -201,68 +204,17 @@
 	config.GlobalConfig.DirtyImageObjects = android.OptionalPathForPath(constructPath(ctx, config.DirtyImageObjects))
 	config.GlobalConfig.BootImageProfiles = constructPaths(ctx, config.BootImageProfiles)
 
+	// Set this here to force the caller to provide a value for this struct (from
+	// either CreateGlobalSoongConfig or LoadGlobalSoongConfig).
+	config.GlobalConfig.SoongConfig = soongConfig
+
 	return config.GlobalConfig, nil
 }
 
-type globalConfigAndRaw struct {
-	global GlobalConfig
-	data   []byte
-}
-
-// GetGlobalConfig returns the global dexpreopt.config that's created in the
-// make config phase. It is loaded once the first time it is called for any
-// ctx.Config(), and returns the same data for all future calls with the same
-// ctx.Config(). A value can be inserted for tests using
-// setDexpreoptTestGlobalConfig.
-func GetGlobalConfig(ctx android.PathContext) GlobalConfig {
-	return getGlobalConfigRaw(ctx).global
-}
-
-// GetGlobalConfigRawData is the same as GetGlobalConfig, except that it returns
-// the literal content of dexpreopt.config.
-func GetGlobalConfigRawData(ctx android.PathContext) []byte {
-	return getGlobalConfigRaw(ctx).data
-}
-
-var globalConfigOnceKey = android.NewOnceKey("DexpreoptGlobalConfig")
-var testGlobalConfigOnceKey = android.NewOnceKey("TestDexpreoptGlobalConfig")
-
-func getGlobalConfigRaw(ctx android.PathContext) globalConfigAndRaw {
-	return ctx.Config().Once(globalConfigOnceKey, func() interface{} {
-		if data, err := ctx.Config().DexpreoptGlobalConfig(ctx); err != nil {
-			panic(err)
-		} else if data != nil {
-			globalConfig, err := ParseGlobalConfig(ctx, data)
-			if err != nil {
-				panic(err)
-			}
-			return globalConfigAndRaw{globalConfig, data}
-		}
-
-		// No global config filename set, see if there is a test config set
-		return ctx.Config().Once(testGlobalConfigOnceKey, func() interface{} {
-			// Nope, return a config with preopting disabled
-			return globalConfigAndRaw{GlobalConfig{
-				DisablePreopt:          true,
-				DisableGenerateProfile: true,
-			}, nil}
-		})
-	}).(globalConfigAndRaw)
-}
-
-// SetTestGlobalConfig sets a GlobalConfig that future calls to GetGlobalConfig
-// will return. It must be called before the first call to GetGlobalConfig for
-// the config.
-func SetTestGlobalConfig(config android.Config, globalConfig GlobalConfig) {
-	config.Once(testGlobalConfigOnceKey, func() interface{} { return globalConfigAndRaw{globalConfig, nil} })
-}
-
-// ParseModuleConfig parses a per-module dexpreopt.config file into a
-// ModuleConfig struct. It is not used in Soong, which receives a ModuleConfig
-// struct directly from java/dexpreopt.go. It is used in dexpreopt_gen called
-// from Make to read the module dexpreopt.config written in the Make config
-// stage.
-func ParseModuleConfig(ctx android.PathContext, data []byte) (ModuleConfig, error) {
+// LoadModuleConfig reads a per-module dexpreopt.config file into a ModuleConfig struct.  It is not used in Soong, which
+// receives a ModuleConfig struct directly from java/dexpreopt.go.  It is used in dexpreopt_gen called from oMake to
+// read the module dexpreopt.config written by Make.
+func LoadModuleConfig(ctx android.PathContext, data []byte) (ModuleConfig, error) {
 	type ModuleJSONConfig struct {
 		ModuleConfig
 
@@ -301,84 +253,21 @@
 	return config.ModuleConfig, nil
 }
 
-// dex2oatModuleName returns the name of the module to use for the dex2oat host
-// tool. It should be a binary module with public visibility that is compiled
-// and installed for host.
-func dex2oatModuleName(config android.Config) string {
-	// Default to the debug variant of dex2oat to help find bugs.
-	// Set USE_DEX2OAT_DEBUG to false for only building non-debug versions.
-	if config.Getenv("USE_DEX2OAT_DEBUG") == "false" {
-		return "dex2oat"
-	} else {
-		return "dex2oatd"
-	}
-}
-
-var dex2oatDepTag = struct {
-	blueprint.BaseDependencyTag
-}{}
-
-type DexPreoptModule interface {
-	dexPreoptModuleSignature() // Not called - only for type detection.
-}
-
-// RegisterToolDepsMutator registers a mutator that adds the necessary
-// dependencies to binary modules for tools that are required later when
-// Get(Cached)GlobalSoongConfig is called. It should be passed to
-// android.RegistrationContext.FinalDepsMutators, and module types that need
-// dependencies also need to embed DexPreoptModule.
-func RegisterToolDepsMutator(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("dexpreopt_tool_deps", toolDepsMutator).Parallel()
-}
-
-func toolDepsMutator(ctx android.BottomUpMutatorContext) {
-	if GetGlobalConfig(ctx).DisablePreopt {
-		// Only register dependencies if dexpreopting is enabled. Necessary to avoid
-		// them in non-platform builds where dex2oat etc isn't available.
-		//
-		// It would be nice to not register this mutator at all then, but
-		// RegisterMutatorsContext available at registration doesn't have the state
-		// necessary to pass as PathContext to constructPath etc.
-		return
-	}
-	if _, ok := ctx.Module().(DexPreoptModule); !ok {
-		return
-	}
-	dex2oatBin := dex2oatModuleName(ctx.Config())
-	v := ctx.Config().BuildOSTarget.Variations()
-	ctx.AddFarVariationDependencies(v, dex2oatDepTag, dex2oatBin)
-}
-
-func dex2oatPathFromDep(ctx android.ModuleContext) android.Path {
-	dex2oatBin := dex2oatModuleName(ctx.Config())
-
-	dex2oatModule := ctx.GetDirectDepWithTag(dex2oatBin, dex2oatDepTag)
-	if dex2oatModule == nil {
-		// If this happens there's probably a missing call to AddToolDeps in DepsMutator.
-		panic(fmt.Sprintf("Failed to lookup %s dependency", dex2oatBin))
-	}
-
-	dex2oatPath := dex2oatModule.(android.HostToolProvider).HostToolPath()
-	if !dex2oatPath.Valid() {
-		panic(fmt.Sprintf("Failed to find host tool path in %s", dex2oatModule))
-	}
-
-	return dex2oatPath.Path()
-}
-
-// createGlobalSoongConfig creates a GlobalSoongConfig from the current context.
+// CreateGlobalSoongConfig creates a GlobalSoongConfig from the current context.
 // Should not be used in dexpreopt_gen.
-func createGlobalSoongConfig(ctx android.ModuleContext) GlobalSoongConfig {
-	if ctx.Config().TestProductVariables != nil {
-		// If we're called in a test there'll be a confusing error from the path
-		// functions below that gets reported without a stack trace, so let's panic
-		// properly with a more helpful message.
-		panic("This should not be called from tests. Please call GlobalSoongConfigForTests somewhere in the test setup.")
+func CreateGlobalSoongConfig(ctx android.PathContext) GlobalSoongConfig {
+	// Default to debug version to help find bugs.
+	// Set USE_DEX2OAT_DEBUG to false for only building non-debug versions.
+	var dex2oatBinary string
+	if ctx.Config().Getenv("USE_DEX2OAT_DEBUG") == "false" {
+		dex2oatBinary = "dex2oat"
+	} else {
+		dex2oatBinary = "dex2oatd"
 	}
 
 	return GlobalSoongConfig{
 		Profman:          ctx.Config().HostToolPath(ctx, "profman"),
-		Dex2oat:          dex2oatPathFromDep(ctx),
+		Dex2oat:          ctx.Config().HostToolPath(ctx, dex2oatBinary),
 		Aapt:             ctx.Config().HostToolPath(ctx, "aapt"),
 		SoongZip:         ctx.Config().HostToolPath(ctx, "soong_zip"),
 		Zip2zip:          ctx.Config().HostToolPath(ctx, "zip2zip"),
@@ -387,44 +276,6 @@
 	}
 }
 
-// The main reason for this Once cache for GlobalSoongConfig is to make the
-// dex2oat path available to singletons. In ordinary modules we get it through a
-// dex2oatDepTag dependency, but in singletons there's no simple way to do the
-// same thing and ensure the right variant is selected, hence this cache to make
-// the resolved path available to singletons. This means we depend on there
-// being at least one ordinary module with a dex2oatDepTag dependency.
-//
-// TODO(b/147613152): Implement a way to deal with dependencies from singletons,
-// and then possibly remove this cache altogether (but the use in
-// GlobalSoongConfigForTests also needs to be rethought).
-var globalSoongConfigOnceKey = android.NewOnceKey("DexpreoptGlobalSoongConfig")
-
-// GetGlobalSoongConfig creates a GlobalSoongConfig the first time it's called,
-// and later returns the same cached instance.
-func GetGlobalSoongConfig(ctx android.ModuleContext) GlobalSoongConfig {
-	globalSoong := ctx.Config().Once(globalSoongConfigOnceKey, func() interface{} {
-		return createGlobalSoongConfig(ctx)
-	}).(GlobalSoongConfig)
-
-	// Always resolve the tool path from the dependency, to ensure that every
-	// module has the dependency added properly.
-	myDex2oat := dex2oatPathFromDep(ctx)
-	if myDex2oat != globalSoong.Dex2oat {
-		panic(fmt.Sprintf("Inconsistent dex2oat path in cached config: expected %s, got %s", globalSoong.Dex2oat, myDex2oat))
-	}
-
-	return globalSoong
-}
-
-// GetCachedGlobalSoongConfig returns a cached GlobalSoongConfig created by an
-// earlier GetGlobalSoongConfig call. This function works with any context
-// compatible with a basic PathContext, since it doesn't try to create a
-// GlobalSoongConfig (which requires a full ModuleContext). It will panic if
-// called before the first GetGlobalSoongConfig call.
-func GetCachedGlobalSoongConfig(ctx android.PathContext) GlobalSoongConfig {
-	return ctx.Config().Get(globalSoongConfigOnceKey).(GlobalSoongConfig)
-}
-
 type globalJsonSoongConfig struct {
 	Profman          string
 	Dex2oat          string
@@ -435,10 +286,9 @@
 	ConstructContext string
 }
 
-// ParseGlobalSoongConfig parses the given data assumed to be read from the
-// global dexpreopt_soong.config file into a GlobalSoongConfig struct. It is
-// only used in dexpreopt_gen.
-func ParseGlobalSoongConfig(ctx android.PathContext, data []byte) (GlobalSoongConfig, error) {
+// LoadGlobalSoongConfig reads the dexpreopt_soong.config file into a
+// GlobalSoongConfig struct. It is only used in dexpreopt_gen.
+func LoadGlobalSoongConfig(ctx android.PathContext, data []byte) (GlobalSoongConfig, error) {
 	var jc globalJsonSoongConfig
 
 	err := json.Unmarshal(data, &jc)
@@ -460,11 +310,7 @@
 }
 
 func (s *globalSoongConfigSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	if GetGlobalConfig(ctx).DisablePreopt {
-		return
-	}
-
-	config := GetCachedGlobalSoongConfig(ctx)
+	config := CreateGlobalSoongConfig(ctx)
 	jc := globalJsonSoongConfig{
 		Profman:          config.Profman.String(),
 		Dex2oat:          config.Dex2oat.String(),
@@ -491,11 +337,7 @@
 }
 
 func (s *globalSoongConfigSingleton) MakeVars(ctx android.MakeVarsContext) {
-	if GetGlobalConfig(ctx).DisablePreopt {
-		return
-	}
-
-	config := GetCachedGlobalSoongConfig(ctx)
+	config := CreateGlobalSoongConfig(ctx)
 
 	ctx.Strict("DEX2OAT", config.Dex2oat.String())
 	ctx.Strict("DEXPREOPT_GEN_DEPS", strings.Join([]string{
@@ -548,14 +390,7 @@
 		BootFlags:                          "",
 		Dex2oatImageXmx:                    "",
 		Dex2oatImageXms:                    "",
-	}
-}
-
-func GlobalSoongConfigForTests(config android.Config) GlobalSoongConfig {
-	// Install the test GlobalSoongConfig in the Once cache so that later calls to
-	// Get(Cached)GlobalSoongConfig returns it without trying to create a real one.
-	return config.Once(globalSoongConfigOnceKey, func() interface{} {
-		return GlobalSoongConfig{
+		SoongConfig: GlobalSoongConfig{
 			Profman:          android.PathForTesting("profman"),
 			Dex2oat:          android.PathForTesting("dex2oat"),
 			Aapt:             android.PathForTesting("aapt"),
@@ -563,6 +398,6 @@
 			Zip2zip:          android.PathForTesting("zip2zip"),
 			ManifestCheck:    android.PathForTesting("manifest_check"),
 			ConstructContext: android.PathForTesting("construct_context.sh"),
-		}
-	}).(GlobalSoongConfig)
+		},
+	}
 }
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index a69498a..ac5b691 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -49,7 +49,7 @@
 
 // GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a
 // ModuleConfig.  The produced files and their install locations will be available through rule.Installs().
-func GenerateDexpreoptRule(ctx android.PathContext, globalSoong GlobalSoongConfig,
+func GenerateDexpreoptRule(ctx android.PathContext,
 	global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) {
 
 	defer func() {
@@ -72,10 +72,10 @@
 
 	var profile android.WritablePath
 	if generateProfile {
-		profile = profileCommand(ctx, globalSoong, global, module, rule)
+		profile = profileCommand(ctx, global, module, rule)
 	}
 	if generateBootProfile {
-		bootProfileCommand(ctx, globalSoong, global, module, rule)
+		bootProfileCommand(ctx, global, module, rule)
 	}
 
 	if !dexpreoptDisabled(global, module) {
@@ -87,7 +87,7 @@
 			generateDM := shouldGenerateDM(module, global)
 
 			for archIdx, _ := range module.Archs {
-				dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, profile, appImage, generateDM)
+				dexpreoptCommand(ctx, global, module, rule, archIdx, profile, appImage, generateDM)
 			}
 		}
 	}
@@ -119,8 +119,8 @@
 	return false
 }
 
-func profileCommand(ctx android.PathContext, globalSoong GlobalSoongConfig, global GlobalConfig,
-	module ModuleConfig, rule *android.RuleBuilder) android.WritablePath {
+func profileCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig,
+	rule *android.RuleBuilder) android.WritablePath {
 
 	profilePath := module.BuildPath.InSameDir(ctx, "profile.prof")
 	profileInstalledPath := module.DexLocation + ".prof"
@@ -131,7 +131,7 @@
 
 	cmd := rule.Command().
 		Text(`ANDROID_LOG_TAGS="*:e"`).
-		Tool(globalSoong.Profman)
+		Tool(global.SoongConfig.Profman)
 
 	if module.ProfileIsTextListing {
 		// The profile is a test listing of classes (used for framework jars).
@@ -158,8 +158,8 @@
 	return profilePath
 }
 
-func bootProfileCommand(ctx android.PathContext, globalSoong GlobalSoongConfig, global GlobalConfig,
-	module ModuleConfig, rule *android.RuleBuilder) android.WritablePath {
+func bootProfileCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig,
+	rule *android.RuleBuilder) android.WritablePath {
 
 	profilePath := module.BuildPath.InSameDir(ctx, "profile.bprof")
 	profileInstalledPath := module.DexLocation + ".bprof"
@@ -170,7 +170,7 @@
 
 	cmd := rule.Command().
 		Text(`ANDROID_LOG_TAGS="*:e"`).
-		Tool(globalSoong.Profman)
+		Tool(global.SoongConfig.Profman)
 
 	// The profile is a test listing of methods.
 	// We need to generate the actual binary profile.
@@ -190,9 +190,8 @@
 	return profilePath
 }
 
-func dexpreoptCommand(ctx android.PathContext, globalSoong GlobalSoongConfig, global GlobalConfig,
-	module ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath,
-	appImage bool, generateDM bool) {
+func dexpreoptCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder,
+	archIdx int, profile android.WritablePath, appImage bool, generateDM bool) {
 
 	arch := module.Archs[archIdx]
 
@@ -300,14 +299,14 @@
 	if module.EnforceUsesLibraries {
 		if module.ManifestPath != nil {
 			rule.Command().Text(`target_sdk_version="$(`).
-				Tool(globalSoong.ManifestCheck).
+				Tool(global.SoongConfig.ManifestCheck).
 				Flag("--extract-target-sdk-version").
 				Input(module.ManifestPath).
 				Text(`)"`)
 		} else {
 			// No manifest to extract targetSdkVersion from, hope that DexJar is an APK
 			rule.Command().Text(`target_sdk_version="$(`).
-				Tool(globalSoong.Aapt).
+				Tool(global.SoongConfig.Aapt).
 				Flag("dump badging").
 				Input(module.DexPath).
 				Text(`| grep "targetSdkVersion" | sed -n "s/targetSdkVersion:'\(.*\)'/\1/p"`).
@@ -328,7 +327,7 @@
 			Implicits(conditionalClassLoaderContextHost29)
 		rule.Command().Textf(`conditional_target_libs_29="%s"`,
 			strings.Join(conditionalClassLoaderContextTarget29, " "))
-		rule.Command().Text("source").Tool(globalSoong.ConstructContext).Input(module.DexPath)
+		rule.Command().Text("source").Tool(global.SoongConfig.ConstructContext).Input(module.DexPath)
 	}
 
 	// Devices that do not have a product partition use a symlink from /product to /system/product.
@@ -341,7 +340,7 @@
 
 	cmd := rule.Command().
 		Text(`ANDROID_LOG_TAGS="*:e"`).
-		Tool(globalSoong.Dex2oat).
+		Tool(global.SoongConfig.Dex2oat).
 		Flag("--avoid-storing-invocation").
 		FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath).
 		Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatXms).
@@ -410,7 +409,7 @@
 		dmInstalledPath := pathtools.ReplaceExtension(module.DexLocation, "dm")
 		tmpPath := module.BuildPath.InSameDir(ctx, "primary.vdex")
 		rule.Command().Text("cp -f").Input(vdexPath).Output(tmpPath)
-		rule.Command().Tool(globalSoong.SoongZip).
+		rule.Command().Tool(global.SoongConfig.SoongZip).
 			FlagWithArg("-L", "9").
 			FlagWithOutput("-o", dmPath).
 			Flag("-j").
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
index 4da003e..e2818bb 100644
--- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -80,13 +80,13 @@
 
 	globalSoongConfigData, err := ioutil.ReadFile(*globalSoongConfigPath)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error reading global Soong config %q: %s\n", *globalSoongConfigPath, err)
+		fmt.Fprintf(os.Stderr, "error reading global config %q: %s\n", *globalSoongConfigPath, err)
 		os.Exit(2)
 	}
 
-	globalSoongConfig, err := dexpreopt.ParseGlobalSoongConfig(ctx, globalSoongConfigData)
+	globalSoongConfig, err := dexpreopt.LoadGlobalSoongConfig(ctx, globalSoongConfigData)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error parsing global Soong config %q: %s\n", *globalSoongConfigPath, err)
+		fmt.Fprintf(os.Stderr, "error loading global config %q: %s\n", *globalSoongConfigPath, err)
 		os.Exit(2)
 	}
 
@@ -96,9 +96,9 @@
 		os.Exit(2)
 	}
 
-	globalConfig, err := dexpreopt.ParseGlobalConfig(ctx, globalConfigData)
+	globalConfig, err := dexpreopt.LoadGlobalConfig(ctx, globalConfigData, globalSoongConfig)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error parsing global config %q: %s\n", *globalConfigPath, err)
+		fmt.Fprintf(os.Stderr, "error parse global config %q: %s\n", *globalConfigPath, err)
 		os.Exit(2)
 	}
 
@@ -108,9 +108,9 @@
 		os.Exit(2)
 	}
 
-	moduleConfig, err := dexpreopt.ParseModuleConfig(ctx, moduleConfigData)
+	moduleConfig, err := dexpreopt.LoadModuleConfig(ctx, moduleConfigData)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error parsing module config %q: %s\n", *moduleConfigPath, err)
+		fmt.Fprintf(os.Stderr, "error loading module config %q: %s\n", *moduleConfigPath, err)
 		os.Exit(2)
 	}
 
@@ -130,12 +130,12 @@
 		}
 	}()
 
-	writeScripts(ctx, globalSoongConfig, globalConfig, moduleConfig, *dexpreoptScriptPath)
+	writeScripts(ctx, globalConfig, moduleConfig, *dexpreoptScriptPath)
 }
 
-func writeScripts(ctx android.PathContext, globalSoong dexpreopt.GlobalSoongConfig,
-	global dexpreopt.GlobalConfig, module dexpreopt.ModuleConfig, dexpreoptScriptPath string) {
-	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, module)
+func writeScripts(ctx android.PathContext, global dexpreopt.GlobalConfig, module dexpreopt.ModuleConfig,
+	dexpreoptScriptPath string) {
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, global, module)
 	if err != nil {
 		panic(err)
 	}
@@ -150,7 +150,7 @@
 		dexpreoptRule.Command().Text("mkdir -p").Flag(filepath.Dir(installPath.String()))
 		dexpreoptRule.Command().Text("cp -f").Input(install.From).Output(installPath)
 	}
-	dexpreoptRule.Command().Tool(globalSoong.SoongZip).
+	dexpreoptRule.Command().Tool(global.SoongConfig.SoongZip).
 		FlagWithArg("-o ", "$2").
 		FlagWithArg("-C ", installDir.String()).
 		FlagWithArg("-D ", installDir.String())
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 44bbbc2..a128dc0 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -61,13 +61,10 @@
 }
 
 func TestDexPreopt(t *testing.T) {
-	config := android.TestConfig("out", nil, "", nil)
-	ctx := android.PathContextForTesting(config)
-	globalSoong := GlobalSoongConfigForTests(config)
-	global := GlobalConfigForTests(ctx)
-	module := testSystemModuleConfig(ctx, "test")
+	ctx := android.PathContextForTesting(android.TestConfig("out", nil, "", nil))
+	global, module := GlobalConfigForTests(ctx), testSystemModuleConfig(ctx, "test")
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, global, module)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -83,9 +80,7 @@
 }
 
 func TestDexPreoptSystemOther(t *testing.T) {
-	config := android.TestConfig("out", nil, "", nil)
-	ctx := android.PathContextForTesting(config)
-	globalSoong := GlobalSoongConfigForTests(config)
+	ctx := android.PathContextForTesting(android.TestConfig("out", nil, "", nil))
 	global := GlobalConfigForTests(ctx)
 	systemModule := testSystemModuleConfig(ctx, "Stest")
 	systemProductModule := testSystemProductModuleConfig(ctx, "SPtest")
@@ -123,7 +118,7 @@
 	for _, test := range tests {
 		global.PatternsOnSystemOther = test.patterns
 		for _, mt := range test.moduleTests {
-			rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, mt.module)
+			rule, err := GenerateDexpreoptRule(ctx, global, mt.module)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -143,15 +138,12 @@
 }
 
 func TestDexPreoptProfile(t *testing.T) {
-	config := android.TestConfig("out", nil, "", nil)
-	ctx := android.PathContextForTesting(config)
-	globalSoong := GlobalSoongConfigForTests(config)
-	global := GlobalConfigForTests(ctx)
-	module := testSystemModuleConfig(ctx, "test")
+	ctx := android.PathContextForTesting(android.TestConfig("out", nil, "", nil))
+	global, module := GlobalConfigForTests(ctx), testSystemModuleConfig(ctx, "test")
 
 	module.ProfileClassListing = android.OptionalPathForPath(android.PathForTesting("profile"))
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, global, module)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/dexpreopt/testing.go b/dexpreopt/testing.go
deleted file mode 100644
index b572eb3..0000000
--- a/dexpreopt/testing.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package dexpreopt
-
-import (
-	"android/soong/android"
-)
-
-type dummyToolBinary struct {
-	android.ModuleBase
-}
-
-func (m *dummyToolBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
-
-func (m *dummyToolBinary) HostToolPath() android.OptionalPath {
-	return android.OptionalPathForPath(android.PathForTesting("dex2oat"))
-}
-
-func dummyToolBinaryFactory() android.Module {
-	module := &dummyToolBinary{}
-	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
-	return module
-}
-
-func RegisterToolModulesForTest(ctx *android.TestContext) {
-	ctx.RegisterModuleType("dummy_tool_binary", dummyToolBinaryFactory)
-}
-
-func BpToolModulesForTest() string {
-	return `
-		dummy_tool_binary {
-			name: "dex2oatd",
-		}
-	`
-}
diff --git a/java/app.go b/java/app.go
index 19995d5..2ed3d4d 100755
--- a/java/app.go
+++ b/java/app.go
@@ -27,7 +27,6 @@
 
 	"android/soong/android"
 	"android/soong/cc"
-	"android/soong/dexpreopt"
 	"android/soong/tradefed"
 )
 
@@ -851,7 +850,6 @@
 	android.ModuleBase
 	android.DefaultableModuleBase
 	prebuilt android.Prebuilt
-	dexpreopt.DexPreoptModule
 
 	properties   AndroidAppImportProperties
 	dpiVariants  interface{}
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 0fd1f28..da68660 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -59,7 +59,7 @@
 }
 
 func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool {
-	global := dexpreopt.GetGlobalConfig(ctx)
+	global := dexpreoptGlobalConfig(ctx)
 
 	if global.DisablePreopt {
 		return true
@@ -96,7 +96,7 @@
 }
 
 func odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
-	return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
+	return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreoptGlobalConfig(ctx))
 }
 
 func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.ModuleOutPath) android.ModuleOutPath {
@@ -104,8 +104,7 @@
 		return dexJarFile
 	}
 
-	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
-	global := dexpreopt.GetGlobalConfig(ctx)
+	global := dexpreoptGlobalConfig(ctx)
 	bootImage := defaultBootImageConfig(ctx)
 	if global.UseApexImage {
 		bootImage = frameworkJZBootImageConfig(ctx)
@@ -190,7 +189,7 @@
 		PresignedPrebuilt: d.isPresignedPrebuilt,
 	}
 
-	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, dexpreoptConfig)
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, global, dexpreoptConfig)
 	if err != nil {
 		ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error())
 		return dexJarFile
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 0082d03..c6aa7fe 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -162,7 +162,7 @@
 }
 
 func skipDexpreoptBootJars(ctx android.PathContext) bool {
-	if dexpreopt.GetGlobalConfig(ctx).DisablePreopt {
+	if dexpreoptGlobalConfig(ctx).DisablePreopt {
 		return true
 	}
 
@@ -195,7 +195,7 @@
 	files := artBootImageConfig(ctx).imagesDeps
 
 	// For JIT-zygote config, also include dexpreopt files for the primary JIT-zygote image.
-	if dexpreopt.GetGlobalConfig(ctx).UseApexImage {
+	if dexpreoptGlobalConfig(ctx).UseApexImage {
 		for arch, paths := range artJZBootImageConfig(ctx).imagesDeps {
 			files[arch] = append(files[arch], paths...)
 		}
@@ -213,7 +213,7 @@
 	d.dexpreoptConfigForMake = android.PathForOutput(ctx, ctx.Config().DeviceName(), "dexpreopt.config")
 	writeGlobalConfigForMake(ctx, d.dexpreoptConfigForMake)
 
-	global := dexpreopt.GetGlobalConfig(ctx)
+	global := dexpreoptGlobalConfig(ctx)
 
 	// Skip recompiling the boot image for the second sanitization phase. We'll get separate paths
 	// and invalidate first-stage artifacts which are crucial to SANITIZE_LITE builds.
@@ -304,8 +304,7 @@
 func buildBootImageRuleForArch(ctx android.SingletonContext, image *bootImage,
 	arch android.ArchType, profile android.Path, missingDeps []string) android.WritablePaths {
 
-	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
-	global := dexpreopt.GetGlobalConfig(ctx)
+	global := dexpreoptGlobalConfig(ctx)
 
 	symbolsDir := image.symbolsDir.Join(ctx, image.installSubdir, arch.String())
 	symbolsFile := symbolsDir.Join(ctx, image.stem+".oat")
@@ -340,7 +339,7 @@
 
 	invocationPath := outputPath.ReplaceExtension(ctx, "invocation")
 
-	cmd.Tool(globalSoong.Dex2oat).
+	cmd.Tool(global.SoongConfig.Dex2oat).
 		Flag("--avoid-storing-invocation").
 		FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath).
 		Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatImageXms).
@@ -443,8 +442,7 @@
 Rebuild with ART_BOOT_IMAGE_EXTRA_ARGS="--runtime-arg -verbose:verifier" to see verification errors.`
 
 func bootImageProfileRule(ctx android.SingletonContext, image *bootImage, missingDeps []string) android.WritablePath {
-	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
-	global := dexpreopt.GetGlobalConfig(ctx)
+	global := dexpreoptGlobalConfig(ctx)
 
 	if global.DisableGenerateProfile || ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() {
 		return nil
@@ -475,7 +473,7 @@
 
 		rule.Command().
 			Text(`ANDROID_LOG_TAGS="*:e"`).
-			Tool(globalSoong.Profman).
+			Tool(global.SoongConfig.Profman).
 			FlagWithInput("--create-profile-from=", bootImageProfile).
 			FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
 			FlagForEachArg("--dex-location=", image.dexLocationsDeps).
@@ -498,8 +496,7 @@
 var bootImageProfileRuleKey = android.NewOnceKey("bootImageProfileRule")
 
 func bootFrameworkProfileRule(ctx android.SingletonContext, image *bootImage, missingDeps []string) android.WritablePath {
-	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
-	global := dexpreopt.GetGlobalConfig(ctx)
+	global := dexpreoptGlobalConfig(ctx)
 
 	if global.DisableGenerateProfile || ctx.Config().IsPdkBuild() || ctx.Config().UnbundledBuild() {
 		return nil
@@ -525,7 +522,7 @@
 
 		rule.Command().
 			Text(`ANDROID_LOG_TAGS="*:e"`).
-			Tool(globalSoong.Profman).
+			Tool(global.SoongConfig.Profman).
 			Flag("--generate-boot-profile").
 			FlagWithInput("--create-profile-from=", bootFrameworkProfile).
 			FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
@@ -587,7 +584,7 @@
 }
 
 func writeGlobalConfigForMake(ctx android.SingletonContext, path android.WritablePath) {
-	data := dexpreopt.GetGlobalConfigRawData(ctx)
+	data := dexpreoptGlobalConfigRaw(ctx).data
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.WriteFile,
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index c3b2133..4ce30f6 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -49,7 +49,7 @@
 	pathCtx := android.PathContextForTesting(config)
 	dexpreoptConfig := dexpreopt.GlobalConfigForTests(pathCtx)
 	dexpreoptConfig.BootJars = []string{"foo", "bar", "baz"}
-	dexpreopt.SetTestGlobalConfig(config, dexpreoptConfig)
+	setDexpreoptTestGlobalConfig(config, dexpreoptConfig)
 
 	ctx := testContext()
 
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index a1a9a76..31bec93 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -22,12 +22,57 @@
 	"android/soong/dexpreopt"
 )
 
+// dexpreoptGlobalConfig returns the global dexpreopt.config.  It is loaded once the first time it is called for any
+// ctx.Config(), and returns the same data for all future calls with the same ctx.Config().  A value can be inserted
+// for tests using setDexpreoptTestGlobalConfig.
+func dexpreoptGlobalConfig(ctx android.PathContext) dexpreopt.GlobalConfig {
+	return dexpreoptGlobalConfigRaw(ctx).global
+}
+
+type globalConfigAndRaw struct {
+	global dexpreopt.GlobalConfig
+	data   []byte
+}
+
+func dexpreoptGlobalConfigRaw(ctx android.PathContext) globalConfigAndRaw {
+	return ctx.Config().Once(dexpreoptGlobalConfigKey, func() interface{} {
+		if data, err := ctx.Config().DexpreoptGlobalConfig(ctx); err != nil {
+			panic(err)
+		} else if data != nil {
+			soongConfig := dexpreopt.CreateGlobalSoongConfig(ctx)
+			globalConfig, err := dexpreopt.LoadGlobalConfig(ctx, data, soongConfig)
+			if err != nil {
+				panic(err)
+			}
+			return globalConfigAndRaw{globalConfig, data}
+		}
+
+		// No global config filename set, see if there is a test config set
+		return ctx.Config().Once(dexpreoptTestGlobalConfigKey, func() interface{} {
+			// Nope, return a config with preopting disabled
+			return globalConfigAndRaw{dexpreopt.GlobalConfig{
+				DisablePreopt:          true,
+				DisableGenerateProfile: true,
+			}, nil}
+		})
+	}).(globalConfigAndRaw)
+}
+
+// setDexpreoptTestGlobalConfig sets a GlobalConfig that future calls to dexpreoptGlobalConfig will return.  It must
+// be called before the first call to dexpreoptGlobalConfig for the config.
+func setDexpreoptTestGlobalConfig(config android.Config, globalConfig dexpreopt.GlobalConfig) {
+	config.Once(dexpreoptTestGlobalConfigKey, func() interface{} { return globalConfigAndRaw{globalConfig, nil} })
+}
+
+var dexpreoptGlobalConfigKey = android.NewOnceKey("DexpreoptGlobalConfig")
+var dexpreoptTestGlobalConfigKey = android.NewOnceKey("TestDexpreoptGlobalConfig")
+
 // systemServerClasspath returns the on-device locations of the modules in the system server classpath.  It is computed
 // once the first time it is called for any ctx.Config(), and returns the same slice for all future calls with the same
 // ctx.Config().
 func systemServerClasspath(ctx android.PathContext) []string {
 	return ctx.Config().OnceStringSlice(systemServerClasspathKey, func() []string {
-		global := dexpreopt.GetGlobalConfig(ctx)
+		global := dexpreoptGlobalConfig(ctx)
 
 		var systemServerClasspathLocations []string
 		for _, m := range global.SystemServerJars {
@@ -88,7 +133,7 @@
 func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig {
 	return ctx.Config().Once(bootImageConfigKey, func() interface{} {
 
-		global := dexpreopt.GetGlobalConfig(ctx)
+		global := dexpreoptGlobalConfig(ctx)
 		targets := dexpreoptTargets(ctx)
 		deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName())
 
@@ -229,7 +274,7 @@
 
 func defaultBootclasspath(ctx android.PathContext) []string {
 	return ctx.Config().OnceStringSlice(defaultBootclasspathKey, func() []string {
-		global := dexpreopt.GetGlobalConfig(ctx)
+		global := dexpreoptGlobalConfig(ctx)
 		image := defaultBootImageConfig(ctx)
 
 		updatableBootclasspath := make([]string, len(global.UpdatableBootJars))
diff --git a/java/java.go b/java/java.go
index a4f191d..794ee68 100644
--- a/java/java.go
+++ b/java/java.go
@@ -29,7 +29,6 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
-	"android/soong/dexpreopt"
 	"android/soong/java/config"
 	"android/soong/tradefed"
 )
@@ -80,8 +79,6 @@
 	ctx.RegisterModuleType("java_host_for_device", HostForDeviceFactory)
 	ctx.RegisterModuleType("dex_import", DexImportFactory)
 
-	ctx.FinalDepsMutators(dexpreopt.RegisterToolDepsMutator)
-
 	ctx.RegisterSingletonType("logtags", LogtagsSingleton)
 	ctx.RegisterSingletonType("kythe_java_extract", kytheExtractJavaFactory)
 }
@@ -337,7 +334,6 @@
 	android.DefaultableModuleBase
 	android.ApexModuleBase
 	android.SdkBase
-	dexpreopt.DexPreoptModule
 
 	properties       CompilerProperties
 	protoProperties  android.ProtoProperties
@@ -1515,16 +1511,6 @@
 		}
 	} else {
 		outputFile = implementationAndResourcesJar
-
-		// dexpreopt.GetGlobalSoongConfig needs to be called at least once even if
-		// no module actually is dexpreopted, to ensure there's a cached
-		// GlobalSoongConfig for the dexpreopt singletons, which will run
-		// regardless.
-		// TODO(b/147613152): Remove when the singletons no longer rely on the
-		// cached GlobalSoongConfig.
-		if !dexpreopt.GetGlobalConfig(ctx).DisablePreopt {
-			_ = dexpreopt.GetGlobalSoongConfig(ctx)
-		}
 	}
 
 	ctx.CheckbuildFile(outputFile)
@@ -2275,7 +2261,6 @@
 	android.ApexModuleBase
 	prebuilt android.Prebuilt
 	android.SdkBase
-	dexpreopt.DexPreoptModule
 
 	properties ImportProperties
 
@@ -2478,7 +2463,6 @@
 	android.DefaultableModuleBase
 	android.ApexModuleBase
 	prebuilt android.Prebuilt
-	dexpreopt.DexPreoptModule
 
 	properties DexImportProperties
 
diff --git a/java/java_test.go b/java/java_test.go
index b724b4d..a2788cb 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -57,15 +57,7 @@
 }
 
 func testConfig(env map[string]string, bp string, fs map[string][]byte) android.Config {
-	bp += dexpreopt.BpToolModulesForTest()
-
-	config := TestConfig(buildDir, env, bp, fs)
-
-	// Set up the global Once cache used for dexpreopt.GlobalSoongConfig, so that
-	// it doesn't create a real one, which would fail.
-	_ = dexpreopt.GlobalSoongConfigForTests(config)
-
-	return config
+	return TestConfig(buildDir, env, bp, fs)
 }
 
 func testContext() *android.TestContext {
@@ -94,8 +86,6 @@
 	cc.RegisterRequiredBuildComponentsForTest(ctx)
 	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", cc.NdkPrebuiltSharedStlFactory)
 
-	dexpreopt.RegisterToolModulesForTest(ctx)
-
 	return ctx
 }
 
@@ -103,7 +93,7 @@
 	t.Helper()
 
 	pathCtx := android.PathContextForTesting(config)
-	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
+	setDexpreoptTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
 
 	ctx.Register(config)
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
@@ -122,7 +112,7 @@
 	ctx := testContext()
 
 	pathCtx := android.PathContextForTesting(config)
-	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
+	setDexpreoptTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
 
 	ctx.Register(config)
 	_, errs := ctx.ParseBlueprintsFiles("Android.bp")