Merge "Use bazel syntax for fully qualified name in path property"
diff --git a/android/module.go b/android/module.go
index e9ad987..126d629 100644
--- a/android/module.go
+++ b/android/module.go
@@ -483,6 +483,7 @@
InitRc() Paths
VintfFragments() Paths
NoticeFiles() Paths
+ EffectiveLicenseFiles() Paths
AddProperties(props ...interface{})
GetProperties() []interface{}
@@ -1515,6 +1516,10 @@
return m.commonProperties.NamespaceExportedToMake
}
+func (m *ModuleBase) EffectiveLicenseFiles() Paths {
+ return m.commonProperties.Effective_license_text
+}
+
// computeInstallDeps finds the installed paths of all dependencies that have a dependency
// tag that is annotated as needing installation via the IsInstallDepNeeded method.
func (m *ModuleBase) computeInstallDeps(ctx ModuleContext) ([]*installPathsDepSet, []*packagingSpecsDepSet) {
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index d4384b2..adb0a7b 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -130,7 +130,11 @@
},
{
Name: "removePdkProperty",
- Fix: runPatchListMod(removePdkProperty),
+ Fix: runPatchListMod(removeObsoleteProperty("product_variables.pdk")),
+ },
+ {
+ Name: "removeScudoProperty",
+ Fix: runPatchListMod(removeObsoleteProperty("sanitize.scudo")),
},
}
@@ -863,7 +867,9 @@
}
}
-func runPatchListMod(modFunc func(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error) func(*Fixer) error {
+type patchListModFunction func(*parser.Module, []byte, *parser.PatchList) error
+
+func runPatchListMod(modFunc patchListModFunction) func(*Fixer) error {
return func(f *Fixer) error {
// Make sure all the offsets are accurate
buf, err := f.reparse()
@@ -1033,23 +1039,63 @@
return patchlist.Add(prop.Pos().Offset, prop.End().Offset+2, replaceStr)
}
-func removePdkProperty(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error {
- prop, ok := mod.GetProperty("product_variables")
- if !ok {
- return nil
+type propertyProvider interface {
+ GetProperty(string) (*parser.Property, bool)
+ RemoveProperty(string) bool
+}
+
+func removeNestedProperty(mod *parser.Module, patchList *parser.PatchList, propName string) error {
+ propNames := strings.Split(propName, ".")
+
+ var propProvider, toRemoveFrom propertyProvider
+ propProvider = mod
+
+ var propToRemove *parser.Property
+ for i, name := range propNames {
+ p, ok := propProvider.GetProperty(name)
+ if !ok {
+ return nil
+ }
+ // if this is the inner most element, it's time to delete
+ if i == len(propNames)-1 {
+ if propToRemove == nil {
+ // if we cannot remove the properties that the current property is nested in,
+ // remove only the current property
+ propToRemove = p
+ toRemoveFrom = propProvider
+ }
+
+ // remove the property from the list, in case we remove other properties in this list
+ toRemoveFrom.RemoveProperty(propToRemove.Name)
+ // only removing the property would leave blank line(s), remove with a patch
+ if err := patchList.Add(propToRemove.Pos().Offset, propToRemove.End().Offset+2, ""); err != nil {
+ return err
+ }
+ } else {
+ propMap, ok := p.Value.(*parser.Map)
+ if !ok {
+ return nil
+ }
+ if len(propMap.Properties) > 1 {
+ // if there are other properties in this struct, we need to keep this struct
+ toRemoveFrom = nil
+ propToRemove = nil
+ } else if propToRemove == nil {
+ // otherwise, we can remove the empty struct entirely
+ toRemoveFrom = propProvider
+ propToRemove = p
+ }
+ propProvider = propMap
+ }
}
- propMap, ok := prop.Value.(*parser.Map)
- if !ok {
- return nil
+
+ return nil
+}
+
+func removeObsoleteProperty(propName string) patchListModFunction {
+ return func(mod *parser.Module, buf []byte, patchList *parser.PatchList) error {
+ return removeNestedProperty(mod, patchList, propName)
}
- pdkProp, ok := propMap.GetProperty("pdk")
- if !ok {
- return nil
- }
- if len(propMap.Properties) > 1 {
- return patchlist.Add(pdkProp.Pos().Offset, pdkProp.End().Offset+2, "")
- }
- return patchlist.Add(prop.Pos().Offset, prop.End().Offset+2, "")
}
func mergeMatchingModuleProperties(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error {
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
index f502229..b994e25 100644
--- a/bpfix/bpfix/bpfix_test.go
+++ b/bpfix/bpfix/bpfix_test.go
@@ -999,7 +999,171 @@
}
}
-func TestRemovePdkProperty(t *testing.T) {
+func TestRemoveNestedProperty(t *testing.T) {
+ tests := []struct {
+ name string
+ in string
+ out string
+ propertyName string
+ }{
+ {
+ name: "remove no nesting",
+ in: `
+cc_library {
+ name: "foo",
+ foo: true,
+}`,
+ out: `
+cc_library {
+ name: "foo",
+}
+`,
+ propertyName: "foo",
+ },
+ {
+ name: "remove one nest",
+ in: `
+cc_library {
+ name: "foo",
+ foo: {
+ bar: true,
+ },
+}`,
+ out: `
+cc_library {
+ name: "foo",
+}
+`,
+ propertyName: "foo.bar",
+ },
+ {
+ name: "remove one nest, multiple props",
+ in: `
+cc_library {
+ name: "foo",
+ foo: {
+ bar: true,
+ baz: false,
+ },
+}`,
+ out: `
+cc_library {
+ name: "foo",
+ foo: {
+ baz: false,
+ },
+}
+`,
+ propertyName: "foo.bar",
+ },
+ {
+ name: "remove multiple nest",
+ in: `
+cc_library {
+ name: "foo",
+ foo: {
+ bar: {
+ baz: {
+ a: true,
+ }
+ },
+ },
+}`,
+ out: `
+cc_library {
+ name: "foo",
+}
+`,
+ propertyName: "foo.bar.baz.a",
+ },
+ {
+ name: "remove multiple nest, outer non-empty",
+ in: `
+cc_library {
+ name: "foo",
+ foo: {
+ bar: {
+ baz: {
+ a: true,
+ }
+ },
+ other: true,
+ },
+}`,
+ out: `
+cc_library {
+ name: "foo",
+ foo: {
+ other: true,
+ },
+}
+`,
+ propertyName: "foo.bar.baz.a",
+ },
+ {
+ name: "remove multiple nest, inner non-empty",
+ in: `
+cc_library {
+ name: "foo",
+ foo: {
+ bar: {
+ baz: {
+ a: true,
+ },
+ other: true,
+ },
+ },
+}`,
+ out: `
+cc_library {
+ name: "foo",
+ foo: {
+ bar: {
+ other: true,
+ },
+ },
+}
+`,
+ propertyName: "foo.bar.baz.a",
+ },
+ {
+ name: "remove multiple nest, inner-most non-empty",
+ in: `
+cc_library {
+ name: "foo",
+ foo: {
+ bar: {
+ baz: {
+ a: true,
+ other: true,
+ },
+ },
+ },
+}`,
+ out: `
+cc_library {
+ name: "foo",
+ foo: {
+ bar: {
+ baz: {
+ other: true,
+ },
+ },
+ },
+}
+`,
+ propertyName: "foo.bar.baz.a",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ runPass(t, test.in, test.out, runPatchListMod(removeObsoleteProperty(test.propertyName)))
+ })
+ }
+}
+
+func TestRemoveObsoleteProperties(t *testing.T) {
tests := []struct {
name string
in string
@@ -1052,7 +1216,7 @@
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
- runPass(t, test.in, test.out, runPatchListMod(removePdkProperty))
+ runPass(t, test.in, test.out, runPatchListMod(removeObsoleteProperty("product_variables.pdk")))
})
}
}
diff --git a/cc/cc.go b/cc/cc.go
index 90c0237..aeebaef 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -2014,9 +2014,9 @@
}
func GetSnapshot(c LinkableInterface, snapshotInfo **SnapshotInfo, actx android.BottomUpMutatorContext) SnapshotInfo {
- // Only modules with BOARD_VNDK_VERSION uses snapshot. Others use the zero value of
+ // Only device modules with BOARD_VNDK_VERSION uses snapshot. Others use the zero value of
// SnapshotInfo, which provides no mappings.
- if *snapshotInfo == nil {
+ if *snapshotInfo == nil && c.Device() {
// Only retrieve the snapshot on demand in order to avoid circular dependencies
// between the modules in the snapshot and the snapshot itself.
var snapshotModule []blueprint.Module
@@ -2025,16 +2025,16 @@
} else if recoverySnapshotVersion := actx.DeviceConfig().RecoverySnapshotVersion(); recoverySnapshotVersion != "current" && recoverySnapshotVersion != "" && c.InRecovery() {
snapshotModule = actx.AddVariationDependencies(nil, nil, "recovery_snapshot")
}
- if len(snapshotModule) > 0 {
+ if len(snapshotModule) > 0 && snapshotModule[0] != nil {
snapshot := actx.OtherModuleProvider(snapshotModule[0], SnapshotInfoProvider).(SnapshotInfo)
*snapshotInfo = &snapshot
// republish the snapshot for use in later mutators on this module
actx.SetProvider(SnapshotInfoProvider, snapshot)
- } else {
- *snapshotInfo = &SnapshotInfo{}
}
}
-
+ if *snapshotInfo == nil {
+ *snapshotInfo = &SnapshotInfo{}
+ }
return **snapshotInfo
}
diff --git a/cc/linkable.go b/cc/linkable.go
index c42c875..6232efb 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -83,6 +83,9 @@
// SnapshotLibrary returns true if this module is a snapshot library.
IsSnapshotLibrary() bool
+ // EffectiveLicenseFiles returns the list of License files for this module.
+ EffectiveLicenseFiles() android.Paths
+
// SnapshotRuntimeLibs returns a list of libraries needed by this module at runtime but which aren't build dependencies.
SnapshotRuntimeLibs() []string
@@ -122,6 +125,7 @@
IsPrebuilt() bool
Toc() android.OptionalPath
+ Device() bool
Host() bool
InRamdisk() bool
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 7ba2363..003b7c9 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -501,13 +501,13 @@
headers = append(headers, m.SnapshotHeaders()...)
}
- if len(m.NoticeFiles()) > 0 {
+ if len(m.EffectiveLicenseFiles()) > 0 {
noticeName := ctx.ModuleName(m) + ".txt"
noticeOut := filepath.Join(noticeDir, noticeName)
// skip already copied notice file
if !installedNotices[noticeOut] {
installedNotices[noticeOut] = true
- snapshotOutputs = append(snapshotOutputs, combineNoticesRule(ctx, m.NoticeFiles(), noticeOut))
+ snapshotOutputs = append(snapshotOutputs, combineNoticesRule(ctx, m.EffectiveLicenseFiles(), noticeOut))
}
}
})
diff --git a/java/lint.go b/java/lint.go
index dd5e4fb..fe3218e 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -391,8 +391,9 @@
rule.Command().Text("rm -f").Output(html).Output(text).Output(xml)
var apiVersionsName, apiVersionsPrebuilt string
- if l.compileSdkKind == android.SdkModule {
- // When compiling an SDK module we use the filtered database because otherwise lint's
+ if l.compileSdkKind == android.SdkModule || l.compileSdkKind == android.SdkSystemServer {
+ // When compiling an SDK module (or system server) we use the filtered
+ // database because otherwise lint's
// NewApi check produces too many false positives; This database excludes information
// about classes created in mainline modules hence removing those false positives.
apiVersionsName = "api_versions_public_filtered.xml"
diff --git a/java/lint_test.go b/java/lint_test.go
index 9cf1c33..456e6ba 100644
--- a/java/lint_test.go
+++ b/java/lint_test.go
@@ -261,6 +261,9 @@
}
func TestJavaLintDatabaseSelectionPublicFiltered(t *testing.T) {
+ testCases := []string{
+ "module_current", "system_server_current",
+ }
bp := `
java_library {
name: "foo",
@@ -274,17 +277,20 @@
},
}
`
- result := android.GroupFixturePreparers(PrepareForTestWithJavaDefaultModules).
- RunTestWithBp(t, bp)
+ for _, testCase := range testCases {
+ thisBp := strings.Replace(bp, "XXX", testCase, 1)
+ result := android.GroupFixturePreparers(PrepareForTestWithJavaDefaultModules).
+ RunTestWithBp(t, thisBp)
- foo := result.ModuleForTests("foo", "android_common")
- sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
- if !strings.Contains(*sboxProto.Commands[0].Command,
- "/api_versions_public_filtered.xml") {
- t.Error("did not use public-filtered lint api database", *sboxProto.Commands[0].Command)
- }
- if strings.Contains(*sboxProto.Commands[0].Command,
- "/api_versions.xml") {
- t.Error("used full api database")
+ foo := result.ModuleForTests("foo", "android_common")
+ sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
+ if !strings.Contains(*sboxProto.Commands[0].Command,
+ "/api_versions_public_filtered.xml") {
+ t.Error("did not use public-filtered lint api database for case", testCase)
+ }
+ if strings.Contains(*sboxProto.Commands[0].Command,
+ "/api_versions.xml") {
+ t.Error("used full api database for case", testCase)
+ }
}
}
diff --git a/mk2rbc/android_products.go b/mk2rbc/android_products.go
new file mode 100644
index 0000000..de38391
--- /dev/null
+++ b/mk2rbc/android_products.go
@@ -0,0 +1,110 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ mkparser "android/soong/androidmk/parser"
+)
+
+// Implements mkparser.Scope, to be used by mkparser.Value.Value()
+type localDirEval struct {
+ localDir string
+ hasErrors bool
+}
+
+func (l *localDirEval) Get(name string) string {
+ if name == "LOCAL_DIR" {
+ return l.localDir
+ }
+ l.hasErrors = true
+ return fmt.Sprintf("$(%s)", name)
+}
+
+func (l *localDirEval) Set(_, _ string) {
+}
+
+func (l *localDirEval) Call(_ string, _ []string) []string {
+ l.hasErrors = true
+ return []string{"$(call ...)"}
+}
+
+func (l *localDirEval) SetFunc(_ string, _ func([]string) []string) {
+}
+
+// UpdateProductConfigMap builds product configuration map.
+// The product configuration map maps a product name (i.e., the value of the
+// TARGET_PRODUCT variable) to the top-level configuration file.
+// In the Android's Make-based build machinery, the equivalent of the
+// product configuration map is $(PRODUCT_MAKEFILES), which is the list
+// of <product>:<configuration makefile> pairs (if <product>: is missing,
+// <product> is the basename of the configuration makefile).
+// UpdateProductConfigMap emulates this build logic by processing the
+// assignments to PRODUCT_MAKEFILES in the file passed to it.
+func UpdateProductConfigMap(configMap map[string]string, configMakefile string) error {
+ contents, err := ioutil.ReadFile(configMakefile)
+ if err != nil {
+ return err
+ }
+ parser := mkparser.NewParser(configMakefile, bytes.NewBuffer(contents))
+ nodes, errs := parser.Parse()
+ if len(errs) > 0 {
+ for _, e := range errs {
+ fmt.Fprintln(os.Stderr, "ERROR:", e)
+ }
+ return fmt.Errorf("cannot parse %s", configMakefile)
+ }
+
+ ldEval := &localDirEval{localDir: filepath.Dir(configMakefile)}
+
+ for _, node := range nodes {
+ // We are interested in assignments to 'PRODUCT_MAKEFILES'
+ asgn, ok := node.(*mkparser.Assignment)
+ if !ok {
+ continue
+ }
+ if !(asgn.Name.Const() && asgn.Name.Strings[0] == "PRODUCT_MAKEFILES") {
+ continue
+ }
+
+ // Resolve the references to $(LOCAL_DIR) in $(PRODUCT_MAKEFILES).
+ ldEval.hasErrors = false
+ value := asgn.Value.Value(ldEval)
+ if ldEval.hasErrors {
+ return fmt.Errorf("cannot evaluate %s", asgn.Value.Dump())
+ }
+ // Each item is either <product>:<configuration makefile>, or
+ // just <configuration makefile>
+ for _, token := range strings.Fields(value) {
+ var product, config_path string
+ if n := strings.Index(token, ":"); n >= 0 {
+ product = token[0:n]
+ config_path = token[n+1:]
+ } else {
+ config_path = token
+ product = filepath.Base(config_path)
+ product = strings.TrimSuffix(product, filepath.Ext(product))
+ }
+ configMap[product] = config_path
+ }
+ }
+ return nil
+}
diff --git a/mk2rbc/android_products_test.go b/mk2rbc/android_products_test.go
new file mode 100644
index 0000000..f8c930a
--- /dev/null
+++ b/mk2rbc/android_products_test.go
@@ -0,0 +1,38 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+func TestProductsMakefile(t *testing.T) {
+ testDir := getTestDirectory()
+ abspath := func(relPath string) string { return filepath.Join(testDir, relPath) }
+ actualProducts := make(map[string]string)
+ if err := UpdateProductConfigMap(actualProducts, abspath("android_products.mk.test")); err != nil {
+ t.Fatal(err)
+ }
+ expectedProducts := map[string]string{
+ "aosp_cf_x86_tv": abspath("vsoc_x86/tv/device.mk"),
+ "aosp_tv_arm": abspath("aosp_tv_arm.mk"),
+ "aosp_tv_arm64": abspath("aosp_tv_arm64.mk"),
+ }
+ if !reflect.DeepEqual(actualProducts, expectedProducts) {
+ t.Errorf("\nExpected: %v\n Actual: %v", expectedProducts, actualProducts)
+ }
+}
diff --git a/mk2rbc/config_variables.go b/mk2rbc/config_variables.go
new file mode 100644
index 0000000..dac509c
--- /dev/null
+++ b/mk2rbc/config_variables.go
@@ -0,0 +1,67 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strings"
+
+ mkparser "android/soong/androidmk/parser"
+)
+
+// Extracts the list of product config variables from a file, calling
+// given registrar for each variable.
+func FindConfigVariables(mkFile string, vr variableRegistrar) error {
+ mkContents, err := ioutil.ReadFile(mkFile)
+ if err != nil {
+ return err
+ }
+ parser := mkparser.NewParser(mkFile, bytes.NewBuffer(mkContents))
+ nodes, errs := parser.Parse()
+ if len(errs) > 0 {
+ for _, e := range errs {
+ fmt.Fprintln(os.Stderr, "ERROR:", e)
+ }
+ return fmt.Errorf("cannot parse %s", mkFile)
+ }
+ for _, node := range nodes {
+ asgn, ok := node.(*mkparser.Assignment)
+ if !ok {
+ continue
+ }
+ // We are looking for a variable called '_product_list_vars'
+ // or '_product_single_value_vars'.
+ if !asgn.Name.Const() {
+ continue
+ }
+ varName := asgn.Name.Strings[0]
+ var starType starlarkType
+ if varName == "_product_list_vars" {
+ starType = starlarkTypeList
+ } else if varName == "_product_single_value_vars" {
+ starType = starlarkTypeUnknown
+ } else {
+ continue
+ }
+ for _, name := range strings.Fields(asgn.Value.Dump()) {
+ vr.NewVariable(name, VarClassConfig, starType)
+ }
+
+ }
+ return nil
+}
diff --git a/mk2rbc/config_variables_test.go b/mk2rbc/config_variables_test.go
new file mode 100644
index 0000000..f5a5180
--- /dev/null
+++ b/mk2rbc/config_variables_test.go
@@ -0,0 +1,60 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "testing"
+)
+
+type testVar struct {
+ name string
+ cl varClass
+ ty starlarkType
+}
+
+type testVariables struct {
+ v []testVar
+}
+
+func (v *testVariables) NewVariable(name string, varClass varClass, valueType starlarkType) {
+ v.v = append(v.v, testVar{name, varClass, valueType})
+}
+
+// getTestDirectory returns the test directory, which should be the test/ subdirectory
+func getTestDirectory() string {
+ _, myFile, _, _ := runtime.Caller(1)
+ return filepath.Join(filepath.Dir(myFile), "test")
+}
+
+func TestConfigVariables(t *testing.T) {
+ testFile := filepath.Join(getTestDirectory(), "config_variables.mk.test")
+ var actual testVariables
+ if err := FindConfigVariables(testFile, &actual); err != nil {
+ t.Fatal(err)
+ }
+ expected := testVariables{[]testVar{
+ {"PRODUCT_NAME", VarClassConfig, starlarkTypeUnknown},
+ {"PRODUCT_MODEL", VarClassConfig, starlarkTypeUnknown},
+ {"PRODUCT_LOCALES", VarClassConfig, starlarkTypeList},
+ {"PRODUCT_AAPT_CONFIG", VarClassConfig, starlarkTypeList},
+ {"PRODUCT_AAPT_PREF_CONFIG", VarClassConfig, starlarkTypeUnknown},
+ }}
+ if !reflect.DeepEqual(expected, actual) {
+ t.Errorf("\nExpected: %v\n Actual: %v", expected, actual)
+ }
+}
diff --git a/mk2rbc/soong_variables.go b/mk2rbc/soong_variables.go
new file mode 100644
index 0000000..de46925
--- /dev/null
+++ b/mk2rbc/soong_variables.go
@@ -0,0 +1,151 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "regexp"
+ "strings"
+
+ mkparser "android/soong/androidmk/parser"
+)
+
+type context struct {
+ includeFileScope mkparser.Scope
+ registrar variableRegistrar
+}
+
+// Scans the makefile Soong uses to generate soong.variables file,
+// collecting variable names and types from the lines that look like this:
+// $(call add_json_XXX, <...>, $(VAR))
+//
+func FindSoongVariables(mkFile string, includeFileScope mkparser.Scope, registrar variableRegistrar) error {
+ ctx := context{includeFileScope, registrar}
+ return ctx.doFind(mkFile)
+}
+
+func (ctx *context) doFind(mkFile string) error {
+ mkContents, err := ioutil.ReadFile(mkFile)
+ if err != nil {
+ return err
+ }
+ parser := mkparser.NewParser(mkFile, bytes.NewBuffer(mkContents))
+ nodes, errs := parser.Parse()
+ if len(errs) > 0 {
+ for _, e := range errs {
+ fmt.Fprintln(os.Stderr, "ERROR:", e)
+ }
+ return fmt.Errorf("cannot parse %s", mkFile)
+ }
+ for _, node := range nodes {
+ switch t := node.(type) {
+ case *mkparser.Variable:
+ ctx.handleVariable(t)
+ case *mkparser.Directive:
+ ctx.handleInclude(t)
+ }
+ }
+ return nil
+}
+
+func (ctx context) NewSoongVariable(name, typeString string) {
+ var valueType starlarkType
+ switch typeString {
+ case "bool":
+ valueType = starlarkTypeBool
+ case "csv":
+ // Only PLATFORM_VERSION_ALL_CODENAMES, and it's a list
+ valueType = starlarkTypeList
+ case "list":
+ valueType = starlarkTypeList
+ case "str":
+ valueType = starlarkTypeString
+ case "val":
+ // Only PLATFORM_SDK_VERSION uses this, and it's integer
+ valueType = starlarkTypeInt
+ default:
+ panic(fmt.Errorf("unknown Soong variable type %s", typeString))
+ }
+
+ ctx.registrar.NewVariable(name, VarClassSoong, valueType)
+}
+
+func (ctx context) handleInclude(t *mkparser.Directive) {
+ if t.Name != "include" && t.Name != "-include" {
+ return
+ }
+ includedPath := t.Args.Value(ctx.includeFileScope)
+ err := ctx.doFind(includedPath)
+ if err != nil && t.Name == "include" {
+ fmt.Fprintf(os.Stderr, "cannot include %s: %s", includedPath, err)
+ }
+}
+
+var callFuncRex = regexp.MustCompile("^call +add_json_(str|val|bool|csv|list) *,")
+
+func (ctx context) handleVariable(t *mkparser.Variable) {
+ // From the variable reference looking as follows:
+ // $(call json_add_TYPE,arg1,$(VAR))
+ // we infer that the type of $(VAR) is TYPE
+ // VAR can be a simple variable name, or another call
+ // (e.g., $(call invert_bool, $(X)), from which we can infer
+ // that the type of X is bool
+ if prefix, v, ok := prefixedVariable(t.Name); ok && strings.HasPrefix(prefix, "call add_json") {
+ if match := callFuncRex.FindStringSubmatch(prefix); match != nil {
+ ctx.inferSoongVariableType(match[1], v)
+ // NOTE(asmundak): sometimes arg1 (the name of the Soong variable defined
+ // in this statement) may indicate that there is a Make counterpart. E.g, from
+ // $(call add_json_bool, DisablePreopt, $(call invert_bool,$(ENABLE_PREOPT)))
+ // it may be inferred that there is a Make boolean variable DISABLE_PREOPT.
+ // Unfortunately, Soong variable names have no 1:1 correspondence to Make variables,
+ // for instance,
+ // $(call add_json_list, PatternsOnSystemOther, $(SYSTEM_OTHER_ODEX_FILTER))
+ // does not mean that there is PATTERNS_ON_SYSTEM_OTHER
+ // Our main interest lies in finding the variables whose values are lists, and
+ // so far there are none that can be found this way, so it is not important.
+ } else {
+ panic(fmt.Errorf("cannot match the call: %s", prefix))
+ }
+ }
+}
+
+var (
+ callInvertBoolRex = regexp.MustCompile("^call +invert_bool *, *$")
+ callFilterBoolRex = regexp.MustCompile("^(filter|filter-out) +(true|false), *$")
+)
+
+func (ctx context) inferSoongVariableType(vType string, n *mkparser.MakeString) {
+ if n.Const() {
+ ctx.NewSoongVariable(n.Strings[0], vType)
+ return
+ }
+ if prefix, v, ok := prefixedVariable(n); ok {
+ if callInvertBoolRex.MatchString(prefix) || callFilterBoolRex.MatchString(prefix) {
+ // It is $(call invert_bool, $(VAR)) or $(filter[-out] [false|true],$(VAR))
+ ctx.inferSoongVariableType("bool", v)
+ }
+ }
+}
+
+// If MakeString is foo$(BAR), returns 'foo', BAR(as *MakeString) and true
+func prefixedVariable(s *mkparser.MakeString) (string, *mkparser.MakeString, bool) {
+ if len(s.Strings) != 2 || s.Strings[1] != "" {
+ return "", nil, false
+ }
+ return s.Strings[0], s.Variables[0].Name, true
+}
diff --git a/mk2rbc/soong_variables_test.go b/mk2rbc/soong_variables_test.go
new file mode 100644
index 0000000..c883882
--- /dev/null
+++ b/mk2rbc/soong_variables_test.go
@@ -0,0 +1,51 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+ "fmt"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+type dirResolverForTest struct {
+ ScopeBase
+}
+
+func (t dirResolverForTest) Get(name string) string {
+ if name != "BUILD_SYSTEM" {
+ return fmt.Sprintf("$(%s)", name)
+ }
+ return getTestDirectory()
+}
+
+func TestSoongVariables(t *testing.T) {
+ testFile := filepath.Join(getTestDirectory(), "soong_variables.mk.test")
+ var actual testVariables
+ if err := FindSoongVariables(testFile, dirResolverForTest{}, &actual); err != nil {
+ t.Fatal(err)
+ }
+ expected := testVariables{[]testVar{
+ {"BUILD_ID", VarClassSoong, starlarkTypeString},
+ {"PLATFORM_SDK_VERSION", VarClassSoong, starlarkTypeInt},
+ {"DEVICE_PACKAGE_OVERLAYS", VarClassSoong, starlarkTypeList},
+ {"ENABLE_CFI", VarClassSoong, starlarkTypeBool},
+ {"ENABLE_PREOPT", VarClassSoong, starlarkTypeBool},
+ }}
+ if !reflect.DeepEqual(expected, actual) {
+ t.Errorf("\nExpected: %v\n Actual: %v", expected, actual)
+ }
+}
diff --git a/mk2rbc/test/android_products.mk.test b/mk2rbc/test/android_products.mk.test
new file mode 100644
index 0000000..a2220ed
--- /dev/null
+++ b/mk2rbc/test/android_products.mk.test
@@ -0,0 +1,4 @@
+PRODUCT_MAKEFILES := \
+ $(LOCAL_DIR)/aosp_tv_arm.mk \
+ $(LOCAL_DIR)/aosp_tv_arm64.mk \
+ aosp_cf_x86_tv:$(LOCAL_DIR)/vsoc_x86/tv/device.mk
\ No newline at end of file
diff --git a/mk2rbc/test/config_variables.mk.test b/mk2rbc/test/config_variables.mk.test
new file mode 100644
index 0000000..e5cd0e9
--- /dev/null
+++ b/mk2rbc/test/config_variables.mk.test
@@ -0,0 +1,12 @@
+_product_single_value_vars :=
+
+# Variables that are lists of values.
+_product_list_vars :=
+
+_product_single_value_vars += PRODUCT_NAME
+_product_single_value_vars += PRODUCT_MODEL
+
+# The resoure configuration options to use for this product.
+_product_list_vars += PRODUCT_LOCALES
+_product_list_vars += PRODUCT_AAPT_CONFIG
+_product_single_value_vars += PRODUCT_AAPT_PREF_CONFIG
diff --git a/mk2rbc/test/soong_included.mk.test b/mk2rbc/test/soong_included.mk.test
new file mode 100644
index 0000000..255ecc5
--- /dev/null
+++ b/mk2rbc/test/soong_included.mk.test
@@ -0,0 +1 @@
+ $(call add_json_bool, DisablePreopt, $(call invert_bool,$(ENABLE_PREOPT)))
diff --git a/mk2rbc/test/soong_variables.mk.test b/mk2rbc/test/soong_variables.mk.test
new file mode 100644
index 0000000..ca60c9c
--- /dev/null
+++ b/mk2rbc/test/soong_variables.mk.test
@@ -0,0 +1,5 @@
+$(call add_json_str, BuildId, $(BUILD_ID))
+$(call add_json_val, Platform_sdk_version, $(PLATFORM_SDK_VERSION))
+$(call add_json_list, DeviceResourceOverlays, $(DEVICE_PACKAGE_OVERLAYS))
+$(call add_json_bool, EnableCFI, $(call invert_bool,$(filter false,$(ENABLE_CFI))))
+include $(BUILD_SYSTEM)/soong_included.mk.test
diff --git a/mk2rbc/types.go b/mk2rbc/types.go
new file mode 100644
index 0000000..22c8b58
--- /dev/null
+++ b/mk2rbc/types.go
@@ -0,0 +1,55 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+// Starlark expression types we use
+type starlarkType int
+
+const (
+ starlarkTypeUnknown starlarkType = iota
+ starlarkTypeList starlarkType = iota
+ starlarkTypeString starlarkType = iota
+ starlarkTypeInt starlarkType = iota
+ starlarkTypeBool starlarkType = iota
+ starlarkTypeVoid starlarkType = iota
+)
+
+type varClass int
+
+const (
+ VarClassConfig varClass = iota
+ VarClassSoong varClass = iota
+ VarClassLocal varClass = iota
+)
+
+type variableRegistrar interface {
+ NewVariable(name string, varClass varClass, valueType starlarkType)
+}
+
+// ScopeBase is a dummy implementation of the mkparser.Scope.
+// All our scopes are read-only and resolve only simple variables.
+type ScopeBase struct{}
+
+func (s ScopeBase) Set(_, _ string) {
+ panic("implement me")
+}
+
+func (s ScopeBase) Call(_ string, _ []string) []string {
+ panic("implement me")
+}
+
+func (s ScopeBase) SetFunc(_ string, _ func([]string) []string) {
+ panic("implement me")
+}