Platform mapping-based product config

This allows us to set product variables as build settings instead
of loading them from a target's provider, which further allows us
to read product config variables in transitions.

Bug: 287539062
Bug: 269577299
Test: Presubmits
Change-Id: I8497703f706162572ceb3486240e1eb02a37f5f6
diff --git a/starlark_import/unmarshal.go b/starlark_import/unmarshal.go
index 1b54437..0e6f130 100644
--- a/starlark_import/unmarshal.go
+++ b/starlark_import/unmarshal.go
@@ -25,12 +25,14 @@
 )
 
 func Unmarshal[T any](value starlark.Value) (T, error) {
-	var zero T
-	x, err := UnmarshalReflect(value, reflect.TypeOf(zero))
+	x, err := UnmarshalReflect(value, reflect.TypeOf((*T)(nil)).Elem())
 	return x.Interface().(T), err
 }
 
 func UnmarshalReflect(value starlark.Value, ty reflect.Type) (reflect.Value, error) {
+	if ty == reflect.TypeOf((*starlark.Value)(nil)).Elem() {
+		return reflect.ValueOf(value), nil
+	}
 	zero := reflect.Zero(ty)
 	var result reflect.Value
 	if ty.Kind() == reflect.Interface {
@@ -286,3 +288,17 @@
 		return nil, fmt.Errorf("unimplemented starlark type: %s", value.Type())
 	}
 }
+
+// NoneableString converts a starlark.Value to a string pointer. If the starlark.Value is NoneType,
+// a nil pointer will be returned instead. All other types of starlark values are errors.
+func NoneableString(value starlark.Value) (*string, error) {
+	switch v := value.(type) {
+	case starlark.String:
+		result := v.GoString()
+		return &result, nil
+	case starlark.NoneType:
+		return nil, nil
+	default:
+		return nil, fmt.Errorf("expected string or none, got %q", value.Type())
+	}
+}
diff --git a/starlark_import/unmarshal_test.go b/starlark_import/unmarshal_test.go
index ee7a9e3..bc0ea4c 100644
--- a/starlark_import/unmarshal_test.go
+++ b/starlark_import/unmarshal_test.go
@@ -30,7 +30,7 @@
 	return result["x"]
 }
 
-func TestUnmarshallConcreteType(t *testing.T) {
+func TestUnmarshalConcreteType(t *testing.T) {
 	x, err := Unmarshal[string](createStarlarkValue(t, `"foo"`))
 	if err != nil {
 		t.Error(err)
@@ -41,7 +41,7 @@
 	}
 }
 
-func TestUnmarshallConcreteTypeWithInterfaces(t *testing.T) {
+func TestUnmarshalConcreteTypeWithInterfaces(t *testing.T) {
 	x, err := Unmarshal[map[string]map[string]interface{}](createStarlarkValue(t,
 		`{"foo": {"foo2": "foo3"}, "bar": {"bar2": ["bar3"]}}`))
 	if err != nil {
@@ -57,7 +57,22 @@
 	}
 }
 
-func TestUnmarshall(t *testing.T) {
+func TestUnmarshalToStarlarkValue(t *testing.T) {
+	x, err := Unmarshal[map[string]starlark.Value](createStarlarkValue(t,
+		`{"foo": "Hi", "bar": None}`))
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if x["foo"].(starlark.String).GoString() != "Hi" {
+		t.Errorf("Expected \"Hi\", got: %q", x["foo"].(starlark.String).GoString())
+	}
+	if x["bar"].Type() != "NoneType" {
+		t.Errorf("Expected \"NoneType\", got: %q", x["bar"].Type())
+	}
+}
+
+func TestUnmarshal(t *testing.T) {
 	testCases := []struct {
 		input    string
 		expected interface{}