Simplify vendor conditionals

Support vendor conditionals with no Go code.

Test: TestSoongConfigModule
Change-Id: I42546e7f17324921ada80f4d8e1cd399830f8dfc
diff --git a/android/config.go b/android/config.go
index 5c4f0a8..769119a 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
@@ -66,19 +68,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
@@ -128,8 +118,6 @@
 	OncePer
 }
 
-type vendorConfig map[string]string
-
 type jsonConfigurable interface {
 	SetDefaultConfig()
 }
@@ -1137,21 +1125,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 05115d6..0ac2135 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{})
@@ -519,9 +520,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)