Merge "convert flag options and values in the same line"
diff --git a/android/module.go b/android/module.go
index 196b095..126d629 100644
--- a/android/module.go
+++ b/android/module.go
@@ -2812,30 +2812,57 @@
return m.bp
}
-// SrcIsModule decodes module references in the format ":name" into the module name, or empty string if the input
-// was not a module reference.
+// SrcIsModule decodes module references in the format ":unqualified-name" or "//namespace:name"
+// into the module name, or empty string if the input was not a module reference.
func SrcIsModule(s string) (module string) {
- if len(s) > 1 && s[0] == ':' {
- return s[1:]
+ if len(s) > 1 {
+ if s[0] == ':' {
+ module = s[1:]
+ if !isUnqualifiedModuleName(module) {
+ // The module name should be unqualified but is not so do not treat it as a module.
+ module = ""
+ }
+ } else if s[0] == '/' && s[1] == '/' {
+ module = s
+ }
}
- return ""
+ return module
}
-// SrcIsModule decodes module references in the format ":name{.tag}" into the module name and tag, ":name" into the
-// module name and an empty string for the tag, or empty strings if the input was not a module reference.
+// SrcIsModule decodes module references in the format ":unqualified-name{.tag}" or
+// "//namespace:name{.tag}" into the module name and an empty string for the tag, or empty strings
+// if the input was not a module reference.
func SrcIsModuleWithTag(s string) (module, tag string) {
- if len(s) > 1 && s[0] == ':' {
- module = s[1:]
- if tagStart := strings.IndexByte(module, '{'); tagStart > 0 {
- if module[len(module)-1] == '}' {
- tag = module[tagStart+1 : len(module)-1]
- module = module[:tagStart]
- return module, tag
+ if len(s) > 1 {
+ if s[0] == ':' {
+ module = s[1:]
+ } else if s[0] == '/' && s[1] == '/' {
+ module = s
+ }
+
+ if module != "" {
+ if tagStart := strings.IndexByte(module, '{'); tagStart > 0 {
+ if module[len(module)-1] == '}' {
+ tag = module[tagStart+1 : len(module)-1]
+ module = module[:tagStart]
+ }
+ }
+
+ if s[0] == ':' && !isUnqualifiedModuleName(module) {
+ // The module name should be unqualified but is not so do not treat it as a module.
+ module = ""
+ tag = ""
}
}
- return module, ""
}
- return "", ""
+
+ return module, tag
+}
+
+// isUnqualifiedModuleName makes sure that the supplied module is an unqualified module name, i.e.
+// does not contain any /.
+func isUnqualifiedModuleName(module string) bool {
+ return strings.IndexByte(module, '/') == -1
}
type sourceOrOutputDependencyTag struct {
diff --git a/android/module_test.go b/android/module_test.go
index 9ac9291..9e2b0ca 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -55,6 +55,27 @@
},
wantModule: "foo:bar",
},
+ {
+ name: "fully qualified",
+ args: args{
+ s: "//foo:bar",
+ },
+ wantModule: "//foo:bar",
+ },
+ {
+ name: "fully qualified with tag",
+ args: args{
+ s: "//foo:bar{.tag}",
+ },
+ wantModule: "//foo:bar{.tag}",
+ },
+ {
+ name: "invalid unqualified name",
+ args: args{
+ s: ":foo/bar",
+ },
+ wantModule: "",
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -128,6 +149,35 @@
},
wantModule: "foo.bar}",
},
+ {
+ name: "fully qualified",
+ args: args{
+ s: "//foo:bar",
+ },
+ wantModule: "//foo:bar",
+ },
+ {
+ name: "fully qualified with tag",
+ args: args{
+ s: "//foo:bar{.tag}",
+ },
+ wantModule: "//foo:bar",
+ wantTag: ".tag",
+ },
+ {
+ name: "invalid unqualified name",
+ args: args{
+ s: ":foo/bar",
+ },
+ wantModule: "",
+ },
+ {
+ name: "invalid unqualified name with tag",
+ args: args{
+ s: ":foo/bar{.tag}",
+ },
+ wantModule: "",
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/android/paths.go b/android/paths.go
index c5e4806..99d5ba7 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1268,10 +1268,11 @@
// PathForModuleSrc returns a Path representing the paths... under the
// module's local source directory.
func PathForModuleSrc(ctx ModuleMissingDepsPathContext, pathComponents ...string) Path {
- p, err := validatePath(pathComponents...)
- if err != nil {
- reportPathError(ctx, err)
- }
+ // Just join the components textually just to make sure that it does not corrupt a fully qualified
+ // module reference, e.g. if the pathComponents is "://other:foo" then using filepath.Join() or
+ // validatePath() will corrupt it, e.g. replace "//" with "/". If the path is not a module
+ // reference then it will be validated by expandOneSrcPath anyway when it calls expandOneSrcPath.
+ p := strings.Join(pathComponents, string(filepath.Separator))
paths, err := expandOneSrcPath(ctx, p, nil)
if err != nil {
if depErr, ok := err.(missingDependencyError); ok {
diff --git a/android/paths_test.go b/android/paths_test.go
index 6f5d79e..7675905 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -1125,6 +1125,12 @@
rels []string
src string
rel string
+
+ // Make test specific preparations to the test fixture.
+ preparer FixturePreparer
+
+ // A test specific error handler.
+ errorHandler FixtureErrorHandler
}
func testPathForModuleSrc(t *testing.T, tests []pathForModuleSrcTestCase) {
@@ -1157,14 +1163,23 @@
"foo/src_special/$": nil,
}
+ errorHandler := test.errorHandler
+ if errorHandler == nil {
+ errorHandler = FixtureExpectsNoErrors
+ }
+
result := GroupFixturePreparers(
FixtureRegisterWithContext(func(ctx RegistrationContext) {
ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory)
- ctx.RegisterModuleType("filegroup", FileGroupFactory)
}),
+ PrepareForTestWithFilegroup,
+ PrepareForTestWithNamespace,
mockFS.AddToFixture(),
- ).RunTest(t)
+ OptionalFixturePreparer(test.preparer),
+ ).
+ ExtendWithErrorHandler(errorHandler).
+ RunTest(t)
m := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
@@ -1333,6 +1348,74 @@
src: "foo/src_special/$",
rel: "src_special/$",
},
+ {
+ // This test makes sure that an unqualified module name cannot contain characters that make
+ // it appear as a qualified module name.
+ name: "output file provider, invalid fully qualified name",
+ bp: `
+ test {
+ name: "foo",
+ src: "://other:b",
+ srcs: ["://other:c"],
+ }`,
+ preparer: FixtureAddTextFile("other/Android.bp", `
+ soong_namespace {}
+
+ output_file_provider {
+ name: "b",
+ outs: ["gen/b"],
+ }
+
+ output_file_provider {
+ name: "c",
+ outs: ["gen/c"],
+ }
+ `),
+ src: "foo/:/other:b",
+ rel: ":/other:b",
+ srcs: []string{"foo/:/other:c"},
+ rels: []string{":/other:c"},
+ },
+ {
+ name: "output file provider, missing fully qualified name",
+ bp: `
+ test {
+ name: "foo",
+ src: "//other:b",
+ srcs: ["//other:c"],
+ }`,
+ errorHandler: FixtureExpectsAllErrorsToMatchAPattern([]string{
+ `"foo" depends on undefined module "//other:b"`,
+ `"foo" depends on undefined module "//other:c"`,
+ }),
+ },
+ {
+ // TODO(b/193228441): Fix broken test.
+ name: "output file provider, fully qualified name",
+ bp: `
+ test {
+ name: "foo",
+ src: "//other:b",
+ srcs: ["//other:c"],
+ }`,
+ preparer: FixtureAddTextFile("other/Android.bp", `
+ soong_namespace {}
+
+ output_file_provider {
+ name: "b",
+ outs: ["gen/b"],
+ }
+
+ output_file_provider {
+ name: "c",
+ outs: ["gen/c"],
+ }
+ `),
+ errorHandler: FixtureExpectsAllErrorsToMatchAPattern([]string{
+ `"foo": missing dependencies: //other:b, is the property annotated with android:"path"`,
+ `"foo": missing dependency on "//other:c", is the property annotated with android:"path"`,
+ }),
+ },
}
testPathForModuleSrc(t, tests)
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 84141e2..6232efb 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -125,6 +125,7 @@
IsPrebuilt() bool
Toc() android.OptionalPath
+ Device() bool
Host() bool
InRamdisk() bool
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")
+}