Product config makefiles to Starlark converter
Test: treehugger; internal tests in mk2rbc_test.go Bug: 172923994 Change-Id: I43120b9c181ef2b8d9453e743233811b0fec268b
This commit is contained in:
parent
e04058f291
commit
b051c4ede3
11 changed files with 3885 additions and 1 deletions
|
@ -278,6 +278,15 @@ func (ms *MakeString) ReplaceLiteral(input string, output string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If MakeString is $(var) after trimming, returns var
|
||||||
|
func (ms *MakeString) SingleVariable() (*MakeString, bool) {
|
||||||
|
if len(ms.Strings) != 2 || strings.TrimSpace(ms.Strings[0]) != "" ||
|
||||||
|
strings.TrimSpace(ms.Strings[1]) != "" {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return ms.Variables[0].Name, true
|
||||||
|
}
|
||||||
|
|
||||||
func splitAnyN(s, sep string, n int) []string {
|
func splitAnyN(s, sep string, n int) []string {
|
||||||
ret := []string{}
|
ret := []string{}
|
||||||
for n == -1 || n > 1 {
|
for n == -1 || n > 1 {
|
||||||
|
|
|
@ -216,13 +216,14 @@ func (p *parser) parseDirective() bool {
|
||||||
// Nothing
|
// Nothing
|
||||||
case "else":
|
case "else":
|
||||||
p.ignoreSpaces()
|
p.ignoreSpaces()
|
||||||
if p.tok != '\n' {
|
if p.tok != '\n' && p.tok != '#' {
|
||||||
d = p.scanner.TokenText()
|
d = p.scanner.TokenText()
|
||||||
p.accept(scanner.Ident)
|
p.accept(scanner.Ident)
|
||||||
if d == "ifdef" || d == "ifndef" || d == "ifeq" || d == "ifneq" {
|
if d == "ifdef" || d == "ifndef" || d == "ifeq" || d == "ifneq" {
|
||||||
d = "el" + d
|
d = "el" + d
|
||||||
p.ignoreSpaces()
|
p.ignoreSpaces()
|
||||||
expression = p.parseExpression()
|
expression = p.parseExpression()
|
||||||
|
expression.TrimRightSpaces()
|
||||||
} else {
|
} else {
|
||||||
p.errorf("expected ifdef/ifndef/ifeq/ifneq, found %s", d)
|
p.errorf("expected ifdef/ifndef/ifeq/ifneq, found %s", d)
|
||||||
}
|
}
|
||||||
|
|
39
mk2rbc/Android.bp
Normal file
39
mk2rbc/Android.bp
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// Copyright (C) 2021 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
blueprint_go_binary {
|
||||||
|
name: "mk2rbc",
|
||||||
|
srcs: ["cmd/mk2rbc.go"],
|
||||||
|
deps: [
|
||||||
|
"mk2rbc-lib",
|
||||||
|
"androidmk-parser",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap_go_package {
|
||||||
|
name: "mk2rbc-lib",
|
||||||
|
pkgPath: "android/soong/mk2rbc",
|
||||||
|
srcs: [
|
||||||
|
"android_products.go",
|
||||||
|
"config_variables.go",
|
||||||
|
"expr.go",
|
||||||
|
"mk2rbc.go",
|
||||||
|
"node.go",
|
||||||
|
"soong_variables.go",
|
||||||
|
"types.go",
|
||||||
|
"variable.go",
|
||||||
|
],
|
||||||
|
deps: ["androidmk-parser"],
|
||||||
|
}
|
14
mk2rbc/TODO
Normal file
14
mk2rbc/TODO
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
* Checking filter/filter-out results is incorrect if pattern contains '%'
|
||||||
|
* Need heuristics to recognize that a variable is local. Propose to use lowercase.
|
||||||
|
* Need heuristics for the local variable type. Propose '_list' suffix
|
||||||
|
* Internal source tree has variables in the inherit-product macro argument. Handle it
|
||||||
|
* Enumerate all environment variables that configuration files use.
|
||||||
|
* Break mk2rbc.go into multiple files.
|
||||||
|
* If variable's type is not yet known, try to divine it from the value assigned to it
|
||||||
|
(it may be a variable of the known type, or a function result)
|
||||||
|
* ifneq (,$(VAR)) should translate to
|
||||||
|
if getattr(<>, "VAR", <default>):
|
||||||
|
* Launcher file needs to have same suffix as the rest of the generated files
|
||||||
|
* Implement $(shell) function
|
||||||
|
* Write execution tests
|
||||||
|
* Review all TODOs in mk2rbc.go
|
498
mk2rbc/cmd/mk2rbc.go
Normal file
498
mk2rbc/cmd/mk2rbc.go
Normal file
|
@ -0,0 +1,498 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// The application to convert product configuration makefiles to Starlark.
|
||||||
|
// Converts either given list of files (and optionally the dependent files
|
||||||
|
// of the same kind), or all all product configuration makefiles in the
|
||||||
|
// given source tree.
|
||||||
|
// Previous version of a converted file can be backed up.
|
||||||
|
// Optionally prints detailed statistics at the end.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime/debug"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"android/soong/androidmk/parser"
|
||||||
|
"android/soong/mk2rbc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
rootDir = flag.String("root", ".", "the value of // for load paths")
|
||||||
|
// TODO(asmundak): remove this option once there is a consensus on suffix
|
||||||
|
suffix = flag.String("suffix", ".rbc", "generated files' suffix")
|
||||||
|
dryRun = flag.Bool("dry_run", false, "dry run")
|
||||||
|
recurse = flag.Bool("convert_dependents", false, "convert all dependent files")
|
||||||
|
mode = flag.String("mode", "", `"backup" to back up existing files, "write" to overwrite them`)
|
||||||
|
warn = flag.Bool("warnings", false, "warn about partially failed conversions")
|
||||||
|
verbose = flag.Bool("v", false, "print summary")
|
||||||
|
errstat = flag.Bool("error_stat", false, "print error statistics")
|
||||||
|
traceVar = flag.String("trace", "", "comma-separated list of variables to trace")
|
||||||
|
// TODO(asmundak): this option is for debugging
|
||||||
|
allInSource = flag.Bool("all", false, "convert all product config makefiles in the tree under //")
|
||||||
|
outputTop = flag.String("outdir", "", "write output files into this directory hierarchy")
|
||||||
|
launcher = flag.String("launcher", "", "generated launcher path. If set, the non-flag argument is _product_name_")
|
||||||
|
printProductConfigMap = flag.Bool("print_product_config_map", false, "print product config map and exit")
|
||||||
|
traceCalls = flag.Bool("trace_calls", false, "trace function calls")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Poor man's flag aliasing: works, but the usage string is ugly and
|
||||||
|
// both flag and its alias can be present on the command line
|
||||||
|
flagAlias := func(target string, alias string) {
|
||||||
|
if f := flag.Lookup(target); f != nil {
|
||||||
|
flag.Var(f.Value, alias, "alias for --"+f.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
quit("cannot alias unknown flag " + target)
|
||||||
|
}
|
||||||
|
flagAlias("suffix", "s")
|
||||||
|
flagAlias("root", "d")
|
||||||
|
flagAlias("dry_run", "n")
|
||||||
|
flagAlias("convert_dependents", "r")
|
||||||
|
flagAlias("warnings", "w")
|
||||||
|
flagAlias("error_stat", "e")
|
||||||
|
}
|
||||||
|
|
||||||
|
var backupSuffix string
|
||||||
|
var tracedVariables []string
|
||||||
|
var errorLogger = errorsByType{data: make(map[string]datum)}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Usage = func() {
|
||||||
|
cmd := filepath.Base(os.Args[0])
|
||||||
|
fmt.Fprintf(flag.CommandLine.Output(),
|
||||||
|
"Usage: %[1]s flags file...\n"+
|
||||||
|
"or: %[1]s flags --launcher=PATH PRODUCT\n", cmd)
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Delouse
|
||||||
|
if *suffix == ".mk" {
|
||||||
|
quit("cannot use .mk as generated file suffix")
|
||||||
|
}
|
||||||
|
if *suffix == "" {
|
||||||
|
quit("suffix cannot be empty")
|
||||||
|
}
|
||||||
|
if *outputTop != "" {
|
||||||
|
if err := os.MkdirAll(*outputTop, os.ModeDir+os.ModePerm); err != nil {
|
||||||
|
quit(err)
|
||||||
|
}
|
||||||
|
s, err := filepath.Abs(*outputTop)
|
||||||
|
if err != nil {
|
||||||
|
quit(err)
|
||||||
|
}
|
||||||
|
*outputTop = s
|
||||||
|
}
|
||||||
|
if *allInSource && len(flag.Args()) > 0 {
|
||||||
|
quit("file list cannot be specified when -all is present")
|
||||||
|
}
|
||||||
|
if *allInSource && *launcher != "" {
|
||||||
|
quit("--all and --launcher are mutually exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag-driven adjustments
|
||||||
|
if (*suffix)[0] != '.' {
|
||||||
|
*suffix = "." + *suffix
|
||||||
|
}
|
||||||
|
if *mode == "backup" {
|
||||||
|
backupSuffix = time.Now().Format("20060102150405")
|
||||||
|
}
|
||||||
|
if *traceVar != "" {
|
||||||
|
tracedVariables = strings.Split(*traceVar, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find out global variables
|
||||||
|
getConfigVariables()
|
||||||
|
getSoongVariables()
|
||||||
|
|
||||||
|
if *printProductConfigMap {
|
||||||
|
productConfigMap := buildProductConfigMap()
|
||||||
|
var products []string
|
||||||
|
for p := range productConfigMap {
|
||||||
|
products = append(products, p)
|
||||||
|
}
|
||||||
|
sort.Strings(products)
|
||||||
|
for _, p := range products {
|
||||||
|
fmt.Println(p, productConfigMap[p])
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
if len(flag.Args()) == 0 {
|
||||||
|
flag.Usage()
|
||||||
|
}
|
||||||
|
// Convert!
|
||||||
|
ok := true
|
||||||
|
if *launcher != "" {
|
||||||
|
if len(flag.Args()) != 1 {
|
||||||
|
quit(fmt.Errorf("a launcher can be generated only for a single product"))
|
||||||
|
}
|
||||||
|
product := flag.Args()[0]
|
||||||
|
productConfigMap := buildProductConfigMap()
|
||||||
|
path, found := productConfigMap[product]
|
||||||
|
if !found {
|
||||||
|
quit(fmt.Errorf("cannot generate configuration launcher for %s, it is not a known product",
|
||||||
|
product))
|
||||||
|
}
|
||||||
|
ok = convertOne(path) && ok
|
||||||
|
err := writeGenerated(*launcher, mk2rbc.Launcher(outputFilePath(path), mk2rbc.MakePath2ModuleName(path)))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s:%s", path, err)
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
files := flag.Args()
|
||||||
|
if *allInSource {
|
||||||
|
productConfigMap := buildProductConfigMap()
|
||||||
|
for _, path := range productConfigMap {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, mkFile := range files {
|
||||||
|
ok = convertOne(mkFile) && ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printStats()
|
||||||
|
if *errstat {
|
||||||
|
errorLogger.printStatistics()
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func quit(s interface{}) {
|
||||||
|
fmt.Fprintln(os.Stderr, s)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildProductConfigMap() map[string]string {
|
||||||
|
const androidProductsMk = "AndroidProducts.mk"
|
||||||
|
// Build the list of AndroidProducts.mk files: it's
|
||||||
|
// build/make/target/product/AndroidProducts.mk plus
|
||||||
|
// device/**/AndroidProducts.mk
|
||||||
|
targetAndroidProductsFile := filepath.Join(*rootDir, "build", "make", "target", "product", androidProductsMk)
|
||||||
|
if _, err := os.Stat(targetAndroidProductsFile); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %s\n(hint: %s is not a source tree root)\n",
|
||||||
|
targetAndroidProductsFile, err, *rootDir)
|
||||||
|
}
|
||||||
|
productConfigMap := make(map[string]string)
|
||||||
|
if err := mk2rbc.UpdateProductConfigMap(productConfigMap, targetAndroidProductsFile); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %s\n", targetAndroidProductsFile, err)
|
||||||
|
}
|
||||||
|
_ = filepath.Walk(filepath.Join(*rootDir, "device"),
|
||||||
|
func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info.IsDir() || filepath.Base(path) != androidProductsMk {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err2 := mk2rbc.UpdateProductConfigMap(productConfigMap, path); err2 != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %s\n", path, err)
|
||||||
|
// Keep going, we want to find all such errors in a single run
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return productConfigMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfigVariables() {
|
||||||
|
path := filepath.Join(*rootDir, "build", "make", "core", "product.mk")
|
||||||
|
if err := mk2rbc.FindConfigVariables(path, mk2rbc.KnownVariables); err != nil {
|
||||||
|
quit(fmt.Errorf("%s\n(check --root[=%s], it should point to the source root)",
|
||||||
|
err, *rootDir))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements mkparser.Scope, to be used by mkparser.Value.Value()
|
||||||
|
type fileNameScope struct {
|
||||||
|
mk2rbc.ScopeBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s fileNameScope) Get(name string) string {
|
||||||
|
if name != "BUILD_SYSTEM" {
|
||||||
|
return fmt.Sprintf("$(%s)", name)
|
||||||
|
}
|
||||||
|
return filepath.Join(*rootDir, "build", "make", "core")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSoongVariables() {
|
||||||
|
path := filepath.Join(*rootDir, "build", "make", "core", "soong_config.mk")
|
||||||
|
err := mk2rbc.FindSoongVariables(path, fileNameScope{}, mk2rbc.KnownVariables)
|
||||||
|
if err != nil {
|
||||||
|
quit(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var converted = make(map[string]*mk2rbc.StarlarkScript)
|
||||||
|
|
||||||
|
//goland:noinspection RegExpRepeatedSpace
|
||||||
|
var cpNormalizer = regexp.MustCompile(
|
||||||
|
"# Copyright \\(C\\) 20.. The Android Open Source Project")
|
||||||
|
|
||||||
|
const cpNormalizedCopyright = "# Copyright (C) 20xx The Android Open Source Project"
|
||||||
|
const copyright = `#
|
||||||
|
# Copyright (C) 20xx The Android Open Source Project
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
`
|
||||||
|
|
||||||
|
// Convert a single file.
|
||||||
|
// Write the result either to the same directory, to the same place in
|
||||||
|
// the output hierarchy, or to the stdout.
|
||||||
|
// Optionally, recursively convert the files this one includes by
|
||||||
|
// $(call inherit-product) or an include statement.
|
||||||
|
func convertOne(mkFile string) (ok bool) {
|
||||||
|
if v, ok := converted[mkFile]; ok {
|
||||||
|
return v != nil
|
||||||
|
}
|
||||||
|
converted[mkFile] = nil
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ok = false
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: panic while converting: %s\n%s\n", mkFile, r, debug.Stack())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
mk2starRequest := mk2rbc.Request{
|
||||||
|
MkFile: mkFile,
|
||||||
|
Reader: nil,
|
||||||
|
RootDir: *rootDir,
|
||||||
|
OutputDir: *outputTop,
|
||||||
|
OutputSuffix: *suffix,
|
||||||
|
TracedVariables: tracedVariables,
|
||||||
|
TraceCalls: *traceCalls,
|
||||||
|
WarnPartialSuccess: *warn,
|
||||||
|
}
|
||||||
|
if *errstat {
|
||||||
|
mk2starRequest.ErrorLogger = errorLogger
|
||||||
|
}
|
||||||
|
ss, err := mk2rbc.Convert(mk2starRequest)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, mkFile, ": ", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
script := ss.String()
|
||||||
|
outputPath := outputFilePath(mkFile)
|
||||||
|
|
||||||
|
if *dryRun {
|
||||||
|
fmt.Printf("==== %s ====\n", outputPath)
|
||||||
|
// Print generated script after removing the copyright header
|
||||||
|
outText := cpNormalizer.ReplaceAllString(script, cpNormalizedCopyright)
|
||||||
|
fmt.Println(strings.TrimPrefix(outText, copyright))
|
||||||
|
} else {
|
||||||
|
if err := maybeBackup(outputPath); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := writeGenerated(outputPath, script); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok = true
|
||||||
|
if *recurse {
|
||||||
|
for _, sub := range ss.SubConfigFiles() {
|
||||||
|
// File may be absent if it is a conditional load
|
||||||
|
if _, err := os.Stat(sub); os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ok = convertOne(sub) && ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
converted[mkFile] = ss
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally saves the previous version of the generated file
|
||||||
|
func maybeBackup(filename string) error {
|
||||||
|
stat, err := os.Stat(filename)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !stat.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("%s exists and is not a regular file", filename)
|
||||||
|
}
|
||||||
|
switch *mode {
|
||||||
|
case "backup":
|
||||||
|
return os.Rename(filename, filename+backupSuffix)
|
||||||
|
case "write":
|
||||||
|
return os.Remove(filename)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s already exists, use --mode option", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputFilePath(mkFile string) string {
|
||||||
|
path := strings.TrimSuffix(mkFile, filepath.Ext(mkFile)) + *suffix
|
||||||
|
if *outputTop != "" {
|
||||||
|
path = filepath.Join(*outputTop, path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeGenerated(path string, contents string) error {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(path, []byte(contents), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printStats() {
|
||||||
|
var sortedFiles []string
|
||||||
|
if !*warn && !*verbose {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for p := range converted {
|
||||||
|
sortedFiles = append(sortedFiles, p)
|
||||||
|
}
|
||||||
|
sort.Strings(sortedFiles)
|
||||||
|
|
||||||
|
nOk, nPartial, nFailed := 0, 0, 0
|
||||||
|
for _, f := range sortedFiles {
|
||||||
|
if converted[f] == nil {
|
||||||
|
nFailed++
|
||||||
|
} else if converted[f].HasErrors() {
|
||||||
|
nPartial++
|
||||||
|
} else {
|
||||||
|
nOk++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *warn {
|
||||||
|
if nPartial > 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Conversion was partially successful for:\n")
|
||||||
|
for _, f := range sortedFiles {
|
||||||
|
if ss := converted[f]; ss != nil && ss.HasErrors() {
|
||||||
|
fmt.Fprintln(os.Stderr, " ", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nFailed > 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Conversion failed for files:\n")
|
||||||
|
for _, f := range sortedFiles {
|
||||||
|
if converted[f] == nil {
|
||||||
|
fmt.Fprintln(os.Stderr, " ", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *verbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "%-16s%5d\n", "Succeeded:", nOk)
|
||||||
|
fmt.Fprintf(os.Stderr, "%-16s%5d\n", "Partial:", nPartial)
|
||||||
|
fmt.Fprintf(os.Stderr, "%-16s%5d\n", "Failed:", nFailed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type datum struct {
|
||||||
|
count int
|
||||||
|
formattingArgs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorsByType struct {
|
||||||
|
data map[string]datum
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ebt errorsByType) NewError(message string, node parser.Node, args ...interface{}) {
|
||||||
|
v, exists := ebt.data[message]
|
||||||
|
if exists {
|
||||||
|
v.count++
|
||||||
|
} else {
|
||||||
|
v = datum{1, nil}
|
||||||
|
}
|
||||||
|
if strings.Contains(message, "%s") {
|
||||||
|
var newArg1 string
|
||||||
|
if len(args) == 0 {
|
||||||
|
panic(fmt.Errorf(`%s has %%s but args are missing`, message))
|
||||||
|
}
|
||||||
|
newArg1 = fmt.Sprint(args[0])
|
||||||
|
if message == "unsupported line" {
|
||||||
|
newArg1 = node.Dump()
|
||||||
|
} else if message == "unsupported directive %s" {
|
||||||
|
if newArg1 == "include" || newArg1 == "-include" {
|
||||||
|
newArg1 = node.Dump()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.formattingArgs = append(v.formattingArgs, newArg1)
|
||||||
|
}
|
||||||
|
ebt.data[message] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ebt errorsByType) printStatistics() {
|
||||||
|
if len(ebt.data) > 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error counts:")
|
||||||
|
}
|
||||||
|
for message, data := range ebt.data {
|
||||||
|
if len(data.formattingArgs) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "%4d %s\n", data.count, message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
itemsByFreq, count := stringsWithFreq(data.formattingArgs, 30)
|
||||||
|
fmt.Fprintf(os.Stderr, "%4d %s [%d unique items]:\n", data.count, message, count)
|
||||||
|
fmt.Fprintln(os.Stderr, " ", itemsByFreq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringsWithFreq(items []string, topN int) (string, int) {
|
||||||
|
freq := make(map[string]int)
|
||||||
|
for _, item := range items {
|
||||||
|
freq[strings.TrimPrefix(strings.TrimSuffix(item, "]"), "[")]++
|
||||||
|
}
|
||||||
|
var sorted []string
|
||||||
|
for item := range freq {
|
||||||
|
sorted = append(sorted, item)
|
||||||
|
}
|
||||||
|
sort.Slice(sorted, func(i int, j int) bool {
|
||||||
|
return freq[sorted[i]] > freq[sorted[j]]
|
||||||
|
})
|
||||||
|
sep := ""
|
||||||
|
res := ""
|
||||||
|
for i, item := range sorted {
|
||||||
|
if i >= topN {
|
||||||
|
res += " ..."
|
||||||
|
break
|
||||||
|
}
|
||||||
|
count := freq[item]
|
||||||
|
if count > 1 {
|
||||||
|
res += fmt.Sprintf("%s%s(%d)", sep, item, count)
|
||||||
|
} else {
|
||||||
|
res += fmt.Sprintf("%s%s", sep, item)
|
||||||
|
}
|
||||||
|
sep = ", "
|
||||||
|
}
|
||||||
|
return res, len(sorted)
|
||||||
|
}
|
580
mk2rbc/expr.go
Normal file
580
mk2rbc/expr.go
Normal file
|
@ -0,0 +1,580 @@
|
||||||
|
// 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"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
mkparser "android/soong/androidmk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Represents an expression in the Starlark code. An expression has
|
||||||
|
// a type, and it can be evaluated.
|
||||||
|
type starlarkExpr interface {
|
||||||
|
starlarkNode
|
||||||
|
typ() starlarkType
|
||||||
|
// Try to substitute variable values. Return substitution result
|
||||||
|
// and whether it is the same as the original expression.
|
||||||
|
eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool)
|
||||||
|
// Emit the code to copy the expression, otherwise we will end up
|
||||||
|
// with source and target pointing to the same list.
|
||||||
|
emitListVarCopy(gctx *generationContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeString(expr starlarkExpr) (string, bool) {
|
||||||
|
if x, ok := expr.(*stringLiteralExpr); ok {
|
||||||
|
return x.literal, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringLiteralExpr struct {
|
||||||
|
literal string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringLiteralExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
res = s
|
||||||
|
same = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringLiteralExpr) emit(gctx *generationContext) {
|
||||||
|
gctx.writef("%q", s.literal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *stringLiteralExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringLiteralExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
s.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integer literal
|
||||||
|
type intLiteralExpr struct {
|
||||||
|
literal int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *intLiteralExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
res = s
|
||||||
|
same = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *intLiteralExpr) emit(gctx *generationContext) {
|
||||||
|
gctx.writef("%d", s.literal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *intLiteralExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeInt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *intLiteralExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
s.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolateExpr represents Starlark's interpolation operator <string> % list
|
||||||
|
// we break <string> into a list of chunks, i.e., "first%second%third" % (X, Y)
|
||||||
|
// will have chunks = ["first", "second", "third"] and args = [X, Y]
|
||||||
|
type interpolateExpr struct {
|
||||||
|
chunks []string // string chunks, separated by '%'
|
||||||
|
args []starlarkExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (xi *interpolateExpr) emit(gctx *generationContext) {
|
||||||
|
if len(xi.chunks) != len(xi.args)+1 {
|
||||||
|
panic(fmt.Errorf("malformed interpolateExpr: #chunks(%d) != #args(%d)+1",
|
||||||
|
len(xi.chunks), len(xi.args)))
|
||||||
|
}
|
||||||
|
// Generate format as join of chunks, but first escape '%' in them
|
||||||
|
format := strings.ReplaceAll(xi.chunks[0], "%", "%%")
|
||||||
|
for _, chunk := range xi.chunks[1:] {
|
||||||
|
format += "%s" + strings.ReplaceAll(chunk, "%", "%%")
|
||||||
|
}
|
||||||
|
gctx.writef("%q %% ", format)
|
||||||
|
emitarg := func(arg starlarkExpr) {
|
||||||
|
if arg.typ() == starlarkTypeList {
|
||||||
|
gctx.write(`" ".join(`)
|
||||||
|
arg.emit(gctx)
|
||||||
|
gctx.write(`)`)
|
||||||
|
} else {
|
||||||
|
arg.emit(gctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(xi.args) == 1 {
|
||||||
|
emitarg(xi.args[0])
|
||||||
|
} else {
|
||||||
|
sep := "("
|
||||||
|
for _, arg := range xi.args {
|
||||||
|
gctx.write(sep)
|
||||||
|
emitarg(arg)
|
||||||
|
sep = ", "
|
||||||
|
}
|
||||||
|
gctx.write(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (xi *interpolateExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
same = true
|
||||||
|
newChunks := []string{xi.chunks[0]}
|
||||||
|
var newArgs []starlarkExpr
|
||||||
|
for i, arg := range xi.args {
|
||||||
|
newArg, sameArg := arg.eval(valueMap)
|
||||||
|
same = same && sameArg
|
||||||
|
switch x := newArg.(type) {
|
||||||
|
case *stringLiteralExpr:
|
||||||
|
newChunks[len(newChunks)-1] += x.literal + xi.chunks[i+1]
|
||||||
|
same = false
|
||||||
|
continue
|
||||||
|
case *intLiteralExpr:
|
||||||
|
newChunks[len(newChunks)-1] += strconv.Itoa(x.literal) + xi.chunks[i+1]
|
||||||
|
same = false
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
newChunks = append(newChunks, xi.chunks[i+1])
|
||||||
|
newArgs = append(newArgs, newArg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if same {
|
||||||
|
res = xi
|
||||||
|
} else if len(newChunks) == 1 {
|
||||||
|
res = &stringLiteralExpr{newChunks[0]}
|
||||||
|
} else {
|
||||||
|
res = &interpolateExpr{chunks: newChunks, args: newArgs}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *interpolateExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (xi *interpolateExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
xi.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type variableRefExpr struct {
|
||||||
|
ref variable
|
||||||
|
isDefined bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *variableRefExpr) eval(map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
predefined, ok := v.ref.(*predefinedVariable)
|
||||||
|
if same = !ok; same {
|
||||||
|
res = v
|
||||||
|
} else {
|
||||||
|
res = predefined.value
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *variableRefExpr) emit(gctx *generationContext) {
|
||||||
|
v.ref.emitGet(gctx, v.isDefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *variableRefExpr) typ() starlarkType {
|
||||||
|
return v.ref.valueType()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *variableRefExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
v.emit(gctx)
|
||||||
|
if v.typ() == starlarkTypeList {
|
||||||
|
gctx.write("[:]") // this will copy the list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type notExpr struct {
|
||||||
|
expr starlarkExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
if x, same := n.expr.eval(valueMap); same {
|
||||||
|
res = n
|
||||||
|
} else {
|
||||||
|
res = ¬Expr{expr: x}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notExpr) emit(ctx *generationContext) {
|
||||||
|
ctx.write("not ")
|
||||||
|
n.expr.emit(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *notExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeBool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
n.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type eqExpr struct {
|
||||||
|
left, right starlarkExpr
|
||||||
|
isEq bool // if false, it's !=
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eq *eqExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
xLeft, sameLeft := eq.left.eval(valueMap)
|
||||||
|
xRight, sameRight := eq.right.eval(valueMap)
|
||||||
|
if same = sameLeft && sameRight; same {
|
||||||
|
res = eq
|
||||||
|
} else {
|
||||||
|
res = &eqExpr{left: xLeft, right: xRight, isEq: eq.isEq}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eq *eqExpr) emit(gctx *generationContext) {
|
||||||
|
// Are we checking that a variable is empty?
|
||||||
|
var varRef *variableRefExpr
|
||||||
|
if s, ok := maybeString(eq.left); ok && s == "" {
|
||||||
|
varRef, ok = eq.right.(*variableRefExpr)
|
||||||
|
} else if s, ok := maybeString(eq.right); ok && s == "" {
|
||||||
|
varRef, ok = eq.left.(*variableRefExpr)
|
||||||
|
}
|
||||||
|
if varRef != nil {
|
||||||
|
// Yes.
|
||||||
|
if eq.isEq {
|
||||||
|
gctx.write("not ")
|
||||||
|
}
|
||||||
|
varRef.emit(gctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// General case
|
||||||
|
eq.left.emit(gctx)
|
||||||
|
if eq.isEq {
|
||||||
|
gctx.write(" == ")
|
||||||
|
} else {
|
||||||
|
gctx.write(" != ")
|
||||||
|
}
|
||||||
|
eq.right.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *eqExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeBool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eq *eqExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
eq.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// variableDefinedExpr corresponds to Make's ifdef VAR
|
||||||
|
type variableDefinedExpr struct {
|
||||||
|
v variable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *variableDefinedExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
res = v
|
||||||
|
same = true
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *variableDefinedExpr) emit(gctx *generationContext) {
|
||||||
|
if v.v != nil {
|
||||||
|
v.v.emitDefined(gctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gctx.writef("%s(%q)", cfnWarning, "TODO(VAR)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *variableDefinedExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeBool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *variableDefinedExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
v.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type listExpr struct {
|
||||||
|
items []starlarkExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *listExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
newItems := make([]starlarkExpr, len(l.items))
|
||||||
|
same = true
|
||||||
|
for i, item := range l.items {
|
||||||
|
var sameItem bool
|
||||||
|
newItems[i], sameItem = item.eval(valueMap)
|
||||||
|
same = same && sameItem
|
||||||
|
}
|
||||||
|
if same {
|
||||||
|
res = l
|
||||||
|
} else {
|
||||||
|
res = &listExpr{newItems}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *listExpr) emit(gctx *generationContext) {
|
||||||
|
if !gctx.inAssignment || len(l.items) < 2 {
|
||||||
|
gctx.write("[")
|
||||||
|
sep := ""
|
||||||
|
for _, item := range l.items {
|
||||||
|
gctx.write(sep)
|
||||||
|
item.emit(gctx)
|
||||||
|
sep = ", "
|
||||||
|
}
|
||||||
|
gctx.write("]")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx.write("[")
|
||||||
|
gctx.indentLevel += 2
|
||||||
|
|
||||||
|
for _, item := range l.items {
|
||||||
|
gctx.newLine()
|
||||||
|
item.emit(gctx)
|
||||||
|
gctx.write(",")
|
||||||
|
}
|
||||||
|
gctx.indentLevel -= 2
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.write("]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *listExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *listExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
l.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStringListExpr(items []string) *listExpr {
|
||||||
|
v := listExpr{}
|
||||||
|
for _, item := range items {
|
||||||
|
v.items = append(v.items, &stringLiteralExpr{item})
|
||||||
|
}
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// concatExpr generates epxr1 + expr2 + ... + exprN in Starlark.
|
||||||
|
type concatExpr struct {
|
||||||
|
items []starlarkExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *concatExpr) emit(gctx *generationContext) {
|
||||||
|
if len(c.items) == 1 {
|
||||||
|
c.items[0].emit(gctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gctx.inAssignment {
|
||||||
|
c.items[0].emit(gctx)
|
||||||
|
for _, item := range c.items[1:] {
|
||||||
|
gctx.write(" + ")
|
||||||
|
item.emit(gctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gctx.write("(")
|
||||||
|
c.items[0].emit(gctx)
|
||||||
|
gctx.indentLevel += 2
|
||||||
|
for _, item := range c.items[1:] {
|
||||||
|
gctx.write(" +")
|
||||||
|
gctx.newLine()
|
||||||
|
item.emit(gctx)
|
||||||
|
}
|
||||||
|
gctx.write(")")
|
||||||
|
gctx.indentLevel -= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *concatExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
same = true
|
||||||
|
xConcat := &concatExpr{items: make([]starlarkExpr, len(c.items))}
|
||||||
|
for i, item := range c.items {
|
||||||
|
var sameItem bool
|
||||||
|
xConcat.items[i], sameItem = item.eval(valueMap)
|
||||||
|
same = same && sameItem
|
||||||
|
}
|
||||||
|
if same {
|
||||||
|
res = c
|
||||||
|
} else {
|
||||||
|
res = xConcat
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *concatExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *concatExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
c.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inExpr generates <expr> [not] in <list>
|
||||||
|
type inExpr struct {
|
||||||
|
expr starlarkExpr
|
||||||
|
list starlarkExpr
|
||||||
|
isNot bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *inExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
x := &inExpr{isNot: i.isNot}
|
||||||
|
var sameExpr, sameList bool
|
||||||
|
x.expr, sameExpr = i.expr.eval(valueMap)
|
||||||
|
x.list, sameList = i.list.eval(valueMap)
|
||||||
|
if same = sameExpr && sameList; same {
|
||||||
|
res = i
|
||||||
|
} else {
|
||||||
|
res = x
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *inExpr) emit(gctx *generationContext) {
|
||||||
|
i.expr.emit(gctx)
|
||||||
|
if i.isNot {
|
||||||
|
gctx.write(" not in ")
|
||||||
|
} else {
|
||||||
|
gctx.write(" in ")
|
||||||
|
}
|
||||||
|
i.list.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *inExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeBool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *inExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
i.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexExpr struct {
|
||||||
|
array starlarkExpr
|
||||||
|
index starlarkExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ix indexExpr) emit(gctx *generationContext) {
|
||||||
|
ix.array.emit(gctx)
|
||||||
|
gctx.write("[")
|
||||||
|
ix.index.emit(gctx)
|
||||||
|
gctx.write("]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ix indexExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ix indexExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
newArray, isSameArray := ix.array.eval(valueMap)
|
||||||
|
newIndex, isSameIndex := ix.index.eval(valueMap)
|
||||||
|
if same = isSameArray && isSameIndex; same {
|
||||||
|
res = ix
|
||||||
|
} else {
|
||||||
|
res = &indexExpr{newArray, newIndex}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ix indexExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
ix.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type callExpr struct {
|
||||||
|
object starlarkExpr // nil if static call
|
||||||
|
name string
|
||||||
|
args []starlarkExpr
|
||||||
|
returnType starlarkType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cx *callExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
newCallExpr := &callExpr{name: cx.name, args: make([]starlarkExpr, len(cx.args)),
|
||||||
|
returnType: cx.returnType}
|
||||||
|
if cx.object != nil {
|
||||||
|
newCallExpr.object, same = cx.object.eval(valueMap)
|
||||||
|
} else {
|
||||||
|
same = true
|
||||||
|
}
|
||||||
|
for i, args := range cx.args {
|
||||||
|
var s bool
|
||||||
|
newCallExpr.args[i], s = args.eval(valueMap)
|
||||||
|
same = same && s
|
||||||
|
}
|
||||||
|
if same {
|
||||||
|
res = cx
|
||||||
|
} else {
|
||||||
|
res = newCallExpr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cx *callExpr) emit(gctx *generationContext) {
|
||||||
|
if cx.object != nil {
|
||||||
|
gctx.write("(")
|
||||||
|
cx.object.emit(gctx)
|
||||||
|
gctx.write(")")
|
||||||
|
gctx.write(".", cx.name, "(")
|
||||||
|
} else {
|
||||||
|
kf, found := knownFunctions[cx.name]
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Errorf("callExpr with unknown function %q", cx.name))
|
||||||
|
}
|
||||||
|
if kf.runtimeName[0] == '!' {
|
||||||
|
panic(fmt.Errorf("callExpr for %q should not be there", cx.name))
|
||||||
|
}
|
||||||
|
gctx.write(kf.runtimeName, "(")
|
||||||
|
}
|
||||||
|
sep := ""
|
||||||
|
for _, arg := range cx.args {
|
||||||
|
gctx.write(sep)
|
||||||
|
arg.emit(gctx)
|
||||||
|
sep = ", "
|
||||||
|
}
|
||||||
|
gctx.write(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cx *callExpr) typ() starlarkType {
|
||||||
|
return cx.returnType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cx *callExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
cx.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type badExpr struct {
|
||||||
|
node mkparser.Node
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *badExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
|
||||||
|
res = b
|
||||||
|
same = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *badExpr) emit(_ *generationContext) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *badExpr) typ() starlarkType {
|
||||||
|
return starlarkTypeUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *badExpr) emitListVarCopy(gctx *generationContext) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeConvertToStringList(expr starlarkExpr) starlarkExpr {
|
||||||
|
if xString, ok := expr.(*stringLiteralExpr); ok {
|
||||||
|
return newStringListExpr(strings.Fields(xString.literal))
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
}
|
1344
mk2rbc/mk2rbc.go
Normal file
1344
mk2rbc/mk2rbc.go
Normal file
File diff suppressed because it is too large
Load diff
857
mk2rbc/mk2rbc_test.go
Normal file
857
mk2rbc/mk2rbc_test.go
Normal file
|
@ -0,0 +1,857 @@
|
||||||
|
// 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"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testCases = []struct {
|
||||||
|
desc string
|
||||||
|
mkname string
|
||||||
|
in string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Comment",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
# Comment
|
||||||
|
# FOO= a\
|
||||||
|
b
|
||||||
|
`,
|
||||||
|
expected: `# Comment
|
||||||
|
# FOO= a
|
||||||
|
# b
|
||||||
|
load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Name conversion",
|
||||||
|
mkname: "path/bar-baz.mk",
|
||||||
|
in: `
|
||||||
|
# Comment
|
||||||
|
`,
|
||||||
|
expected: `# Comment
|
||||||
|
load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Item variable",
|
||||||
|
mkname: "pixel3.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_NAME := Pixel 3
|
||||||
|
PRODUCT_MODEL :=
|
||||||
|
local_var = foo
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_NAME"] = "Pixel 3"
|
||||||
|
cfg["PRODUCT_MODEL"] = ""
|
||||||
|
_local_var = "foo"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "List variable",
|
||||||
|
mkname: "pixel4.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_PACKAGES = package1 package2
|
||||||
|
PRODUCT_COPY_FILES += file2:target
|
||||||
|
PRODUCT_PACKAGES += package3
|
||||||
|
PRODUCT_COPY_FILES =
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_PACKAGES"] = [
|
||||||
|
"package1",
|
||||||
|
"package2",
|
||||||
|
]
|
||||||
|
rblf.setdefault(handle, "PRODUCT_COPY_FILES")
|
||||||
|
cfg["PRODUCT_COPY_FILES"] += ["file2:target"]
|
||||||
|
cfg["PRODUCT_PACKAGES"] += ["package3"]
|
||||||
|
cfg["PRODUCT_COPY_FILES"] = []
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Unknown function",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_NAME := $(call foo, bar)
|
||||||
|
`,
|
||||||
|
expected: `# MK2RBC TRANSLATION ERROR: cannot handle invoking foo
|
||||||
|
# PRODUCT_NAME := $(call foo, bar)
|
||||||
|
load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
rblf.warning("product.mk", "partially successful conversion")
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Inherit configuration always",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifdef PRODUCT_NAME
|
||||||
|
$(call inherit-product, part.mk)
|
||||||
|
else # Comment
|
||||||
|
$(call inherit-product, $(LOCAL_PATH)/part.mk)
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
load(":part.star", _part_init = "init")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g.get("PRODUCT_NAME") != None:
|
||||||
|
rblf.inherit(handle, "part", _part_init)
|
||||||
|
else:
|
||||||
|
# Comment
|
||||||
|
rblf.inherit(handle, "./part", _part_init)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Inherit configuration if it exists",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
$(call inherit-product-if-exists, part.mk)
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
load(":part.star|init", _part_init = "init")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if _part_init != None:
|
||||||
|
rblf.inherit(handle, "part", _part_init)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: "Include configuration",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifdef PRODUCT_NAME
|
||||||
|
include part.mk
|
||||||
|
else
|
||||||
|
-include $(LOCAL_PATH)/part.mk)
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
load(":part.star", _part_init = "init")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g.get("PRODUCT_NAME") != None:
|
||||||
|
_part_init(g, handle)
|
||||||
|
else:
|
||||||
|
if _part_init != None:
|
||||||
|
_part_init(g, handle)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
desc: "Synonymous inherited configurations",
|
||||||
|
mkname: "path/product.mk",
|
||||||
|
in: `
|
||||||
|
$(call inherit-product, foo/font.mk)
|
||||||
|
$(call inherit-product, bar/font.mk)
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
load("//foo:font.star", _font_init = "init")
|
||||||
|
load("//bar:font.star", _font1_init = "init")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
rblf.inherit(handle, "foo/font", _font_init)
|
||||||
|
rblf.inherit(handle, "bar/font", _font1_init)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Directive define",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
define some-macro
|
||||||
|
$(info foo)
|
||||||
|
endef
|
||||||
|
`,
|
||||||
|
expected: `# MK2RBC TRANSLATION ERROR: define is not supported: some-macro
|
||||||
|
# define some-macro
|
||||||
|
# $(info foo)
|
||||||
|
# endef
|
||||||
|
load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
rblf.warning("product.mk", "partially successful conversion")
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Ifdef",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifdef PRODUCT_NAME
|
||||||
|
PRODUCT_NAME = gizmo
|
||||||
|
else
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g.get("PRODUCT_NAME") != None:
|
||||||
|
cfg["PRODUCT_NAME"] = "gizmo"
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Simple functions",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
$(warning this is the warning)
|
||||||
|
$(warning)
|
||||||
|
$(info this is the info)
|
||||||
|
$(error this is the error)
|
||||||
|
PRODUCT_NAME:=$(shell echo *)
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
rblf.mkwarning("product.mk", "this is the warning")
|
||||||
|
rblf.mkwarning("product.mk", "")
|
||||||
|
rblf.mkinfo("product.mk", "this is the info")
|
||||||
|
rblf.mkerror("product.mk", "this is the error")
|
||||||
|
cfg["PRODUCT_NAME"] = rblf.shell("echo *")
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Empty if",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifdef PRODUCT_NAME
|
||||||
|
# Comment
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g.get("PRODUCT_NAME") != None:
|
||||||
|
# Comment
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "if/else/endif",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifndef PRODUCT_NAME
|
||||||
|
PRODUCT_NAME=gizmo1
|
||||||
|
else
|
||||||
|
PRODUCT_NAME=gizmo2
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if not g.get("PRODUCT_NAME") != None:
|
||||||
|
cfg["PRODUCT_NAME"] = "gizmo1"
|
||||||
|
else:
|
||||||
|
cfg["PRODUCT_NAME"] = "gizmo2"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "else if",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifdef PRODUCT_NAME
|
||||||
|
PRODUCT_NAME = gizmo
|
||||||
|
else ifndef PRODUCT_PACKAGES # Comment
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g.get("PRODUCT_NAME") != None:
|
||||||
|
cfg["PRODUCT_NAME"] = "gizmo"
|
||||||
|
elif not g.get("PRODUCT_PACKAGES") != None:
|
||||||
|
# Comment
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ifeq / ifneq",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifeq (aosp_arm, $(TARGET_PRODUCT))
|
||||||
|
PRODUCT_MODEL = pix2
|
||||||
|
else
|
||||||
|
PRODUCT_MODEL = pix21
|
||||||
|
endif
|
||||||
|
ifneq (aosp_x86, $(TARGET_PRODUCT))
|
||||||
|
PRODUCT_MODEL = pix3
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if "aosp_arm" == g["TARGET_PRODUCT"]:
|
||||||
|
cfg["PRODUCT_MODEL"] = "pix2"
|
||||||
|
else:
|
||||||
|
cfg["PRODUCT_MODEL"] = "pix21"
|
||||||
|
if "aosp_x86" != g["TARGET_PRODUCT"]:
|
||||||
|
cfg["PRODUCT_MODEL"] = "pix3"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Check filter result",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifeq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
|
||||||
|
endif
|
||||||
|
ifneq (,$(filter userdebug,$(TARGET_BUILD_VARIANT))
|
||||||
|
endif
|
||||||
|
ifneq (,$(filter plaf,$(PLATFORM_LIST)))
|
||||||
|
endif
|
||||||
|
ifeq ($(TARGET_BUILD_VARIANT), $(filter $(TARGET_BUILD_VARIANT), userdebug eng))
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g["TARGET_BUILD_VARIANT"] not in ["userdebug", "eng"]:
|
||||||
|
pass
|
||||||
|
if g["TARGET_BUILD_VARIANT"] in ["userdebug"]:
|
||||||
|
pass
|
||||||
|
if "plaf" in g.get("PLATFORM_LIST", []):
|
||||||
|
pass
|
||||||
|
if g["TARGET_BUILD_VARIANT"] in ["userdebug", "eng"]:
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Get filter result",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_LIST2=$(filter-out %/foo.ko,$(wildcard path/*.ko))
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_LIST2"] = rblf.filter_out("%/foo.ko", rblf.expand_wildcard("path/*.ko"))
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "filter $(VAR), values",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifeq (,$(filter $(TARGET_PRODUCT), yukawa_gms yukawa_sei510_gms)
|
||||||
|
ifneq (,$(filter $(TARGET_PRODUCT), yukawa_gms)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g["TARGET_PRODUCT"] not in ["yukawa_gms", "yukawa_sei510_gms"]:
|
||||||
|
if g["TARGET_PRODUCT"] in ["yukawa_gms"]:
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ifeq",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifeq (aosp, $(TARGET_PRODUCT)) # Comment
|
||||||
|
else ifneq (, $(TARGET_PRODUCT))
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if "aosp" == g["TARGET_PRODUCT"]:
|
||||||
|
# Comment
|
||||||
|
pass
|
||||||
|
elif g["TARGET_PRODUCT"]:
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Nested if",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifdef PRODUCT_NAME
|
||||||
|
PRODUCT_PACKAGES = pack-if0
|
||||||
|
ifdef PRODUCT_MODEL
|
||||||
|
PRODUCT_PACKAGES = pack-if-if
|
||||||
|
else ifdef PRODUCT_NAME
|
||||||
|
PRODUCT_PACKAGES = pack-if-elif
|
||||||
|
else
|
||||||
|
PRODUCT_PACKAGES = pack-if-else
|
||||||
|
endif
|
||||||
|
PRODUCT_PACKAGES = pack-if
|
||||||
|
else ifneq (,$(TARGET_PRODUCT))
|
||||||
|
PRODUCT_PACKAGES = pack-elif
|
||||||
|
else
|
||||||
|
PRODUCT_PACKAGES = pack-else
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g.get("PRODUCT_NAME") != None:
|
||||||
|
cfg["PRODUCT_PACKAGES"] = ["pack-if0"]
|
||||||
|
if g.get("PRODUCT_MODEL") != None:
|
||||||
|
cfg["PRODUCT_PACKAGES"] = ["pack-if-if"]
|
||||||
|
elif g.get("PRODUCT_NAME") != None:
|
||||||
|
cfg["PRODUCT_PACKAGES"] = ["pack-if-elif"]
|
||||||
|
else:
|
||||||
|
cfg["PRODUCT_PACKAGES"] = ["pack-if-else"]
|
||||||
|
cfg["PRODUCT_PACKAGES"] = ["pack-if"]
|
||||||
|
elif g["TARGET_PRODUCT"]:
|
||||||
|
cfg["PRODUCT_PACKAGES"] = ["pack-elif"]
|
||||||
|
else:
|
||||||
|
cfg["PRODUCT_PACKAGES"] = ["pack-else"]
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Wildcard",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifeq (,$(wildcard foo.mk))
|
||||||
|
endif
|
||||||
|
ifneq (,$(wildcard foo*.mk))
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if not rblf.file_exists("foo.mk"):
|
||||||
|
pass
|
||||||
|
if rblf.file_wildcard_exists("foo*.mk"):
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ifneq $(X),true",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifneq ($(VARIABLE),true)
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g.get("VARIABLE", "") != "true":
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Const neq",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifneq (1,0)
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if "1" != "0":
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "is-board calls",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifeq ($(call is-board-platform-in-list,msm8998), true)
|
||||||
|
else ifneq ($(call is-board-platform,copper),true)
|
||||||
|
else ifneq ($(call is-vendor-board-platform,QCOM),true)
|
||||||
|
else ifeq ($(call is-product-in-list, $(PLATFORM_LIST)), true)
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if g.get("TARGET_BOARD_PLATFORM", "") in ["msm8998"]:
|
||||||
|
pass
|
||||||
|
elif g.get("TARGET_BOARD_PLATFORM", "") != "copper":
|
||||||
|
pass
|
||||||
|
elif g.get("TARGET_BOARD_PLATFORM", "") not in g["QCOM_BOARD_PLATFORMS"]:
|
||||||
|
pass
|
||||||
|
elif g["TARGET_PRODUCT"] in g.get("PLATFORM_LIST", []):
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "findstring call",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifneq ($(findstring foo,$(PRODUCT_PACKAGES)),)
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") != -1:
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "rhs call",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_COPY_FILES = $(call add-to-product-copy-files-if-exists, path:distpath) \
|
||||||
|
$(call find-copy-subdir-files, *, fromdir, todir) $(wildcard foo.*)
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_COPY_FILES"] = (rblf.copy_if_exists("path:distpath") +
|
||||||
|
rblf.find_and_copy("*", "fromdir", "todir") +
|
||||||
|
rblf.expand_wildcard("foo.*"))
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "inferred type",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
HIKEY_MODS := $(wildcard foo/*.ko)
|
||||||
|
BOARD_VENDOR_KERNEL_MODULES += $(HIKEY_MODS)
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
g["HIKEY_MODS"] = rblf.expand_wildcard("foo/*.ko")
|
||||||
|
g.setdefault("BOARD_VENDOR_KERNEL_MODULES", [])
|
||||||
|
g["BOARD_VENDOR_KERNEL_MODULES"] += g["HIKEY_MODS"]
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "list with vars",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_COPY_FILES += path1:$(TARGET_PRODUCT)/path1 $(PRODUCT_MODEL)/path2:$(TARGET_PRODUCT)/path2
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
rblf.setdefault(handle, "PRODUCT_COPY_FILES")
|
||||||
|
cfg["PRODUCT_COPY_FILES"] += (("path1:%s/path1" % g["TARGET_PRODUCT"]).split() +
|
||||||
|
("%s/path2:%s/path2" % (cfg.get("PRODUCT_MODEL", ""), g["TARGET_PRODUCT"])).split())
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "misc calls",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
$(call enforce-product-packages-exist,)
|
||||||
|
$(call enforce-product-packages-exist, foo)
|
||||||
|
$(call require-artifacts-in-path, foo, bar)
|
||||||
|
$(call require-artifacts-in-path-relaxed, foo, bar)
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
rblf.enforce_product_packages_exist("")
|
||||||
|
rblf.enforce_product_packages_exist("foo")
|
||||||
|
rblf.require_artifacts_in_path("foo", "bar")
|
||||||
|
rblf.require_artifacts_in_path_relaxed("foo", "bar")
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "list with functions",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_COPY_FILES := $(call find-copy-subdir-files,*.kl,from1,to1) \
|
||||||
|
$(call find-copy-subdir-files,*.kc,from2,to2) \
|
||||||
|
foo bar
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_COPY_FILES"] = (rblf.find_and_copy("*.kl", "from1", "to1") +
|
||||||
|
rblf.find_and_copy("*.kc", "from2", "to2") +
|
||||||
|
[
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
])
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Text functions",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_COPY_FILES := $(addprefix pfx-,a b c)
|
||||||
|
PRODUCT_COPY_FILES := $(addsuffix .sff, a b c)
|
||||||
|
PRODUCT_NAME := $(word 1, $(subst ., ,$(TARGET_BOARD_PLATFORM)))
|
||||||
|
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_COPY_FILES"] = rblf.addprefix("pfx-", "a b c")
|
||||||
|
cfg["PRODUCT_COPY_FILES"] = rblf.addsuffix(".sff", "a b c")
|
||||||
|
cfg["PRODUCT_NAME"] = ((g.get("TARGET_BOARD_PLATFORM", "")).replace(".", " ")).split()[0]
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "assignment flavors",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_LIST1 := a
|
||||||
|
PRODUCT_LIST2 += a
|
||||||
|
PRODUCT_LIST1 += b
|
||||||
|
PRODUCT_LIST2 += b
|
||||||
|
PRODUCT_LIST3 ?= a
|
||||||
|
PRODUCT_LIST1 = c
|
||||||
|
PLATFORM_LIST += x
|
||||||
|
PRODUCT_PACKAGES := $(PLATFORM_LIST)
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_LIST1"] = ["a"]
|
||||||
|
rblf.setdefault(handle, "PRODUCT_LIST2")
|
||||||
|
cfg["PRODUCT_LIST2"] += ["a"]
|
||||||
|
cfg["PRODUCT_LIST1"] += ["b"]
|
||||||
|
cfg["PRODUCT_LIST2"] += ["b"]
|
||||||
|
if cfg.get("PRODUCT_LIST3") == None:
|
||||||
|
cfg["PRODUCT_LIST3"] = ["a"]
|
||||||
|
cfg["PRODUCT_LIST1"] = ["c"]
|
||||||
|
g.setdefault("PLATFORM_LIST", [])
|
||||||
|
g["PLATFORM_LIST"] += ["x"]
|
||||||
|
cfg["PRODUCT_PACKAGES"] = g["PLATFORM_LIST"][:]
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "assigment flavors2",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_LIST1 = a
|
||||||
|
ifeq (0,1)
|
||||||
|
PRODUCT_LIST1 += b
|
||||||
|
PRODUCT_LIST2 += b
|
||||||
|
endif
|
||||||
|
PRODUCT_LIST1 += c
|
||||||
|
PRODUCT_LIST2 += c
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_LIST1"] = ["a"]
|
||||||
|
if "0" == "1":
|
||||||
|
cfg["PRODUCT_LIST1"] += ["b"]
|
||||||
|
rblf.setdefault(handle, "PRODUCT_LIST2")
|
||||||
|
cfg["PRODUCT_LIST2"] += ["b"]
|
||||||
|
cfg["PRODUCT_LIST1"] += ["c"]
|
||||||
|
rblf.setdefault(handle, "PRODUCT_LIST2")
|
||||||
|
cfg["PRODUCT_LIST2"] += ["c"]
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "string split",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_LIST1 = a
|
||||||
|
local = b
|
||||||
|
local += c
|
||||||
|
FOO = d
|
||||||
|
FOO += e
|
||||||
|
PRODUCT_LIST1 += $(local)
|
||||||
|
PRODUCT_LIST1 += $(FOO)
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_LIST1"] = ["a"]
|
||||||
|
_local = "b"
|
||||||
|
_local += " " + "c"
|
||||||
|
g["FOO"] = "d"
|
||||||
|
g["FOO"] += " " + "e"
|
||||||
|
cfg["PRODUCT_LIST1"] += (_local).split()
|
||||||
|
cfg["PRODUCT_LIST1"] += (g["FOO"]).split()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "apex_jars",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_BOOT_JARS := $(ART_APEX_JARS) framework-minus-apex
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
cfg["PRODUCT_BOOT_JARS"] = (g.get("ART_APEX_JARS", []) +
|
||||||
|
["framework-minus-apex"])
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "strip function",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifeq ($(filter hwaddress,$(PRODUCT_PACKAGES)),)
|
||||||
|
PRODUCT_PACKAGES := $(strip $(PRODUCT_PACKAGES) hwaddress)
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if "hwaddress" not in cfg.get("PRODUCT_PACKAGES", []):
|
||||||
|
cfg["PRODUCT_PACKAGES"] = (rblf.mkstrip("%s hwaddress" % " ".join(cfg.get("PRODUCT_PACKAGES", [])))).split()
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "strip func in condition",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
ifneq ($(strip $(TARGET_VENDOR)),)
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
if rblf.mkstrip(g.get("TARGET_VENDOR", "")) != "":
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ref after set",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
PRODUCT_ADB_KEYS:=value
|
||||||
|
FOO := $(PRODUCT_ADB_KEYS)
|
||||||
|
ifneq (,$(PRODUCT_ADB_KEYS))
|
||||||
|
endif
|
||||||
|
`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
g["PRODUCT_ADB_KEYS"] = "value"
|
||||||
|
g["FOO"] = g["PRODUCT_ADB_KEYS"]
|
||||||
|
if g["PRODUCT_ADB_KEYS"]:
|
||||||
|
pass
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ref before set",
|
||||||
|
mkname: "product.mk",
|
||||||
|
in: `
|
||||||
|
V1 := $(PRODUCT_ADB_KEYS)
|
||||||
|
ifeq (,$(PRODUCT_ADB_KEYS))
|
||||||
|
V2 := $(PRODUCT_ADB_KEYS)
|
||||||
|
PRODUCT_ADB_KEYS:=foo
|
||||||
|
V3 := $(PRODUCT_ADB_KEYS)
|
||||||
|
endif`,
|
||||||
|
expected: `load("//build/make/core:product_config.rbc", "rblf")
|
||||||
|
|
||||||
|
def init(g, handle):
|
||||||
|
cfg = rblf.cfg(handle)
|
||||||
|
g["V1"] = g.get("PRODUCT_ADB_KEYS", "")
|
||||||
|
if not g.get("PRODUCT_ADB_KEYS", ""):
|
||||||
|
g["V2"] = g.get("PRODUCT_ADB_KEYS", "")
|
||||||
|
g["PRODUCT_ADB_KEYS"] = "foo"
|
||||||
|
g["V3"] = g["PRODUCT_ADB_KEYS"]
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var known_variables = []struct {
|
||||||
|
name string
|
||||||
|
class varClass
|
||||||
|
starlarkType
|
||||||
|
}{
|
||||||
|
{"PRODUCT_NAME", VarClassConfig, starlarkTypeString},
|
||||||
|
{"PRODUCT_MODEL", VarClassConfig, starlarkTypeString},
|
||||||
|
{"PRODUCT_PACKAGES", VarClassConfig, starlarkTypeList},
|
||||||
|
{"PRODUCT_BOOT_JARS", VarClassConfig, starlarkTypeList},
|
||||||
|
{"PRODUCT_COPY_FILES", VarClassConfig, starlarkTypeList},
|
||||||
|
{"PRODUCT_IS_64BIT", VarClassConfig, starlarkTypeString},
|
||||||
|
{"PRODUCT_LIST1", VarClassConfig, starlarkTypeList},
|
||||||
|
{"PRODUCT_LIST2", VarClassConfig, starlarkTypeList},
|
||||||
|
{"PRODUCT_LIST3", VarClassConfig, starlarkTypeList},
|
||||||
|
{"TARGET_PRODUCT", VarClassSoong, starlarkTypeString},
|
||||||
|
{"TARGET_BUILD_VARIANT", VarClassSoong, starlarkTypeString},
|
||||||
|
{"TARGET_BOARD_PLATFORM", VarClassSoong, starlarkTypeString},
|
||||||
|
{"QCOM_BOARD_PLATFORMS", VarClassSoong, starlarkTypeString},
|
||||||
|
{"PLATFORM_LIST", VarClassSoong, starlarkTypeList}, // TODO(asmundak): make it local instead of soong
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGood(t *testing.T) {
|
||||||
|
for _, v := range known_variables {
|
||||||
|
KnownVariables.NewVariable(v.name, v.class, v.starlarkType)
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc,
|
||||||
|
func(t *testing.T) {
|
||||||
|
ss, err := Convert(Request{
|
||||||
|
MkFile: test.mkname,
|
||||||
|
Reader: bytes.NewBufferString(test.in),
|
||||||
|
RootDir: ".",
|
||||||
|
OutputSuffix: ".star",
|
||||||
|
WarnPartialSuccess: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got := ss.String()
|
||||||
|
if got != test.expected {
|
||||||
|
t.Errorf("%q failed\nExpected:\n%s\nActual:\n%s\n", test.desc,
|
||||||
|
strings.ReplaceAll(test.expected, "\n", "\n"),
|
||||||
|
strings.ReplaceAll(got, "\n", "\n"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
237
mk2rbc/node.go
Normal file
237
mk2rbc/node.go
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
// 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"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
mkparser "android/soong/androidmk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A parsed node for which starlark code will be generated
|
||||||
|
// by calling emit().
|
||||||
|
type starlarkNode interface {
|
||||||
|
emit(ctx *generationContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types used to keep processed makefile data:
|
||||||
|
type commentNode struct {
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commentNode) emit(gctx *generationContext) {
|
||||||
|
chunks := strings.Split(c.text, "\\\n")
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.write(chunks[0]) // It has '#' at the beginning already.
|
||||||
|
for _, chunk := range chunks[1:] {
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.write("#", chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type inheritedModule struct {
|
||||||
|
path string // Converted Starlark file path
|
||||||
|
originalPath string // Makefile file path
|
||||||
|
moduleName string
|
||||||
|
moduleLocalName string
|
||||||
|
loadAlways bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im inheritedModule) name() string {
|
||||||
|
return MakePath2ModuleName(im.originalPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im inheritedModule) entryName() string {
|
||||||
|
return im.moduleLocalName + "_init"
|
||||||
|
}
|
||||||
|
|
||||||
|
type inheritNode struct {
|
||||||
|
*inheritedModule
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inn *inheritNode) emit(gctx *generationContext) {
|
||||||
|
// Unconditional case:
|
||||||
|
// rblf.inherit(handle, <module>, module_init)
|
||||||
|
// Conditional case:
|
||||||
|
// if <module>_init != None:
|
||||||
|
// same as above
|
||||||
|
gctx.newLine()
|
||||||
|
if inn.loadAlways {
|
||||||
|
gctx.writef("%s(handle, %q, %s)", cfnInherit, inn.name(), inn.entryName())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gctx.writef("if %s != None:", inn.entryName())
|
||||||
|
gctx.indentLevel++
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.writef("%s(handle, %q, %s)", cfnInherit, inn.name(), inn.entryName())
|
||||||
|
gctx.indentLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
type includeNode struct {
|
||||||
|
*inheritedModule
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inn *includeNode) emit(gctx *generationContext) {
|
||||||
|
gctx.newLine()
|
||||||
|
if inn.loadAlways {
|
||||||
|
gctx.writef("%s(g, handle)", inn.entryName())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gctx.writef("if %s != None:", inn.entryName())
|
||||||
|
gctx.indentLevel++
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.writef("%s(g, handle)", inn.entryName())
|
||||||
|
gctx.indentLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
type assignmentFlavor int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Assignment flavors
|
||||||
|
asgnSet assignmentFlavor = iota // := or =
|
||||||
|
asgnMaybeSet assignmentFlavor = iota // ?= and variable may be unset
|
||||||
|
asgnAppend assignmentFlavor = iota // += and variable has been set before
|
||||||
|
asgnMaybeAppend assignmentFlavor = iota // += and variable may be unset
|
||||||
|
)
|
||||||
|
|
||||||
|
type assignmentNode struct {
|
||||||
|
lhs variable
|
||||||
|
value starlarkExpr
|
||||||
|
mkValue *mkparser.MakeString
|
||||||
|
flavor assignmentFlavor
|
||||||
|
isTraced bool
|
||||||
|
previous *assignmentNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asgn *assignmentNode) emit(gctx *generationContext) {
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.inAssignment = true
|
||||||
|
asgn.lhs.emitSet(gctx, asgn)
|
||||||
|
gctx.inAssignment = false
|
||||||
|
|
||||||
|
if asgn.isTraced {
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.tracedCount++
|
||||||
|
gctx.writef(`print("%s.%d: %s := ", `, gctx.starScript.mkFile, gctx.tracedCount, asgn.lhs.name())
|
||||||
|
asgn.lhs.emitGet(gctx, true)
|
||||||
|
gctx.writef(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type exprNode struct {
|
||||||
|
expr starlarkExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (exn *exprNode) emit(gctx *generationContext) {
|
||||||
|
gctx.newLine()
|
||||||
|
exn.expr.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifNode struct {
|
||||||
|
isElif bool // true if this is 'elif' statement
|
||||||
|
expr starlarkExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *ifNode) emit(gctx *generationContext) {
|
||||||
|
ifElif := "if "
|
||||||
|
if in.isElif {
|
||||||
|
ifElif = "elif "
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx.newLine()
|
||||||
|
if bad, ok := in.expr.(*badExpr); ok {
|
||||||
|
gctx.write("# MK2STAR ERROR converting:")
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.writef("# %s", bad.node.Dump())
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.writef("# %s", bad.message)
|
||||||
|
gctx.newLine()
|
||||||
|
// The init function emits a warning if the conversion was not
|
||||||
|
// fullly successful, so here we (arbitrarily) take the false path.
|
||||||
|
gctx.writef("%sFalse:", ifElif)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gctx.write(ifElif)
|
||||||
|
in.expr.emit(gctx)
|
||||||
|
gctx.write(":")
|
||||||
|
}
|
||||||
|
|
||||||
|
type elseNode struct{}
|
||||||
|
|
||||||
|
func (br *elseNode) emit(gctx *generationContext) {
|
||||||
|
gctx.newLine()
|
||||||
|
gctx.write("else:")
|
||||||
|
}
|
||||||
|
|
||||||
|
// switchCase represents as single if/elseif/else branch. All the necessary
|
||||||
|
// info about flavor (if/elseif/else) is supposed to be kept in `gate`.
|
||||||
|
type switchCase struct {
|
||||||
|
gate starlarkNode
|
||||||
|
nodes []starlarkNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *switchCase) newNode(node starlarkNode) {
|
||||||
|
cb.nodes = append(cb.nodes, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *switchCase) emit(gctx *generationContext) {
|
||||||
|
cb.gate.emit(gctx)
|
||||||
|
gctx.indentLevel++
|
||||||
|
hasStatements := false
|
||||||
|
emitNode := func(node starlarkNode) {
|
||||||
|
if _, ok := node.(*commentNode); !ok {
|
||||||
|
hasStatements = true
|
||||||
|
}
|
||||||
|
node.emit(gctx)
|
||||||
|
}
|
||||||
|
if len(cb.nodes) > 0 {
|
||||||
|
emitNode(cb.nodes[0])
|
||||||
|
for _, node := range cb.nodes[1:] {
|
||||||
|
emitNode(node)
|
||||||
|
}
|
||||||
|
if !hasStatements {
|
||||||
|
gctx.emitPass()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gctx.emitPass()
|
||||||
|
}
|
||||||
|
gctx.indentLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single complete if ... elseif ... else ... endif sequences
|
||||||
|
type switchNode struct {
|
||||||
|
ssCases []*switchCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ssw *switchNode) newNode(node starlarkNode) {
|
||||||
|
switch br := node.(type) {
|
||||||
|
case *switchCase:
|
||||||
|
ssw.ssCases = append(ssw.ssCases, br)
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("expected switchCase node, got %t", br))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ssw *switchNode) emit(gctx *generationContext) {
|
||||||
|
if len(ssw.ssCases) == 0 {
|
||||||
|
gctx.emitPass()
|
||||||
|
} else {
|
||||||
|
ssw.ssCases[0].emit(gctx)
|
||||||
|
for _, ssCase := range ssw.ssCases[1:] {
|
||||||
|
ssCase.emit(gctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,11 @@ package mk2rbc
|
||||||
type starlarkType int
|
type starlarkType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// Variable types. Initially we only know the types of the product
|
||||||
|
// configuration variables that are lists, and the types of some
|
||||||
|
// hardwired variables. The remaining variables are first entered as
|
||||||
|
// having an unknown type and treated as strings, but sometimes we
|
||||||
|
// can infer variable's type from the value assigned to it.
|
||||||
starlarkTypeUnknown starlarkType = iota
|
starlarkTypeUnknown starlarkType = iota
|
||||||
starlarkTypeList starlarkType = iota
|
starlarkTypeList starlarkType = iota
|
||||||
starlarkTypeString starlarkType = iota
|
starlarkTypeString starlarkType = iota
|
||||||
|
|
300
mk2rbc/variable.go
Normal file
300
mk2rbc/variable.go
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
// 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"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type variable interface {
|
||||||
|
name() string
|
||||||
|
emitGet(gctx *generationContext, isDefined bool)
|
||||||
|
emitSet(gctx *generationContext, asgn *assignmentNode)
|
||||||
|
emitDefined(gctx *generationContext)
|
||||||
|
valueType() starlarkType
|
||||||
|
defaultValueString() string
|
||||||
|
isPreset() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseVariable struct {
|
||||||
|
nam string
|
||||||
|
typ starlarkType
|
||||||
|
preset bool // true if it has been initialized at startup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v baseVariable) name() string {
|
||||||
|
return v.nam
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v baseVariable) valueType() starlarkType {
|
||||||
|
return v.typ
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v baseVariable) isPreset() bool {
|
||||||
|
return v.preset
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultValuesByType = map[starlarkType]string{
|
||||||
|
starlarkTypeUnknown: `""`,
|
||||||
|
starlarkTypeList: "[]",
|
||||||
|
starlarkTypeString: `""`,
|
||||||
|
starlarkTypeInt: "0",
|
||||||
|
starlarkTypeBool: "False",
|
||||||
|
starlarkTypeVoid: "None",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v baseVariable) defaultValueString() string {
|
||||||
|
if v, ok := defaultValuesByType[v.valueType()]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf("%s has unknown type %q", v.name(), v.valueType()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type productConfigVariable struct {
|
||||||
|
baseVariable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pcv productConfigVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
|
||||||
|
emitAssignment := func() {
|
||||||
|
pcv.emitGet(gctx, true)
|
||||||
|
gctx.write(" = ")
|
||||||
|
asgn.value.emitListVarCopy(gctx)
|
||||||
|
}
|
||||||
|
emitAppend := func() {
|
||||||
|
pcv.emitGet(gctx, true)
|
||||||
|
gctx.write(" += ")
|
||||||
|
if pcv.valueType() == starlarkTypeString {
|
||||||
|
gctx.writef(`" " + `)
|
||||||
|
}
|
||||||
|
asgn.value.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch asgn.flavor {
|
||||||
|
case asgnSet:
|
||||||
|
emitAssignment()
|
||||||
|
case asgnAppend:
|
||||||
|
emitAppend()
|
||||||
|
case asgnMaybeAppend:
|
||||||
|
// If we are not sure variable has been assigned before, emit setdefault
|
||||||
|
if pcv.typ == starlarkTypeList {
|
||||||
|
gctx.writef("%s(handle, %q)", cfnSetListDefault, pcv.name())
|
||||||
|
} else {
|
||||||
|
gctx.writef("cfg.setdefault(%q, %s)", pcv.name(), pcv.defaultValueString())
|
||||||
|
}
|
||||||
|
gctx.newLine()
|
||||||
|
emitAppend()
|
||||||
|
case asgnMaybeSet:
|
||||||
|
gctx.writef("if cfg.get(%q) == None:", pcv.nam)
|
||||||
|
gctx.indentLevel++
|
||||||
|
gctx.newLine()
|
||||||
|
emitAssignment()
|
||||||
|
gctx.indentLevel--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pcv productConfigVariable) emitGet(gctx *generationContext, isDefined bool) {
|
||||||
|
if isDefined || pcv.isPreset() {
|
||||||
|
gctx.writef("cfg[%q]", pcv.nam)
|
||||||
|
} else {
|
||||||
|
gctx.writef("cfg.get(%q, %s)", pcv.nam, pcv.defaultValueString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pcv productConfigVariable) emitDefined(gctx *generationContext) {
|
||||||
|
gctx.writef("g.get(%q) != None", pcv.name())
|
||||||
|
}
|
||||||
|
|
||||||
|
type otherGlobalVariable struct {
|
||||||
|
baseVariable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scv otherGlobalVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
|
||||||
|
emitAssignment := func() {
|
||||||
|
scv.emitGet(gctx, true)
|
||||||
|
gctx.write(" = ")
|
||||||
|
asgn.value.emitListVarCopy(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
emitAppend := func() {
|
||||||
|
scv.emitGet(gctx, true)
|
||||||
|
gctx.write(" += ")
|
||||||
|
if scv.valueType() == starlarkTypeString {
|
||||||
|
gctx.writef(`" " + `)
|
||||||
|
}
|
||||||
|
asgn.value.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch asgn.flavor {
|
||||||
|
case asgnSet:
|
||||||
|
emitAssignment()
|
||||||
|
case asgnAppend:
|
||||||
|
emitAppend()
|
||||||
|
case asgnMaybeAppend:
|
||||||
|
// If we are not sure variable has been assigned before, emit setdefault
|
||||||
|
gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString())
|
||||||
|
gctx.newLine()
|
||||||
|
emitAppend()
|
||||||
|
case asgnMaybeSet:
|
||||||
|
gctx.writef("if g.get(%q) == None:", scv.nam)
|
||||||
|
gctx.indentLevel++
|
||||||
|
gctx.newLine()
|
||||||
|
emitAssignment()
|
||||||
|
gctx.indentLevel--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scv otherGlobalVariable) emitGet(gctx *generationContext, isDefined bool) {
|
||||||
|
if isDefined || scv.isPreset() {
|
||||||
|
gctx.writef("g[%q]", scv.nam)
|
||||||
|
} else {
|
||||||
|
gctx.writef("g.get(%q, %s)", scv.nam, scv.defaultValueString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scv otherGlobalVariable) emitDefined(gctx *generationContext) {
|
||||||
|
gctx.writef("g.get(%q) != None", scv.name())
|
||||||
|
}
|
||||||
|
|
||||||
|
type localVariable struct {
|
||||||
|
baseVariable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lv localVariable) emitDefined(_ *generationContext) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lv localVariable) String() string {
|
||||||
|
return "_" + lv.nam
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lv localVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
|
||||||
|
switch asgn.flavor {
|
||||||
|
case asgnSet:
|
||||||
|
gctx.writef("%s = ", lv)
|
||||||
|
asgn.value.emitListVarCopy(gctx)
|
||||||
|
case asgnAppend:
|
||||||
|
lv.emitGet(gctx, false)
|
||||||
|
gctx.write(" += ")
|
||||||
|
if lv.valueType() == starlarkTypeString {
|
||||||
|
gctx.writef(`" " + `)
|
||||||
|
}
|
||||||
|
asgn.value.emit(gctx)
|
||||||
|
case asgnMaybeAppend:
|
||||||
|
gctx.writef("%s(%q, ", cfnLocalAppend, lv)
|
||||||
|
asgn.value.emit(gctx)
|
||||||
|
gctx.write(")")
|
||||||
|
case asgnMaybeSet:
|
||||||
|
gctx.writef("%s(%q, ", cfnLocalSetDefault, lv)
|
||||||
|
asgn.value.emit(gctx)
|
||||||
|
gctx.write(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lv localVariable) emitGet(gctx *generationContext, _ bool) {
|
||||||
|
gctx.writef("%s", lv)
|
||||||
|
}
|
||||||
|
|
||||||
|
type predefinedVariable struct {
|
||||||
|
baseVariable
|
||||||
|
value starlarkExpr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pv predefinedVariable) emitGet(gctx *generationContext, _ bool) {
|
||||||
|
pv.value.emit(gctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pv predefinedVariable) emitSet(_ *generationContext, asgn *assignmentNode) {
|
||||||
|
if expectedValue, ok1 := maybeString(pv.value); ok1 {
|
||||||
|
actualValue, ok2 := maybeString(asgn.value)
|
||||||
|
if ok2 {
|
||||||
|
if actualValue == expectedValue {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "cannot set predefined variable %s to %q, its value should be %q",
|
||||||
|
pv.name(), actualValue, expectedValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf("cannot set predefined variable %s to %q", pv.name(), asgn.mkValue.Dump()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pv predefinedVariable) emitDefined(gctx *generationContext) {
|
||||||
|
gctx.write("True")
|
||||||
|
}
|
||||||
|
|
||||||
|
var localProductConfigVariables = map[string]string{
|
||||||
|
"LOCAL_AUDIO_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
|
||||||
|
"LOCAL_AUDIO_PRODUCT_COPY_FILES": "PRODUCT_COPY_FILES",
|
||||||
|
"LOCAL_AUDIO_DEVICE_PACKAGE_OVERLAYS": "DEVICE_PACKAGE_OVERLAYS",
|
||||||
|
"LOCAL_DUMPSTATE_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
|
||||||
|
"LOCAL_GATEKEEPER_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
|
||||||
|
"LOCAL_HEALTH_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
|
||||||
|
"LOCAL_SENSOR_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
|
||||||
|
"LOCAL_KEYMASTER_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
|
||||||
|
"LOCAL_KEYMINT_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
|
||||||
|
}
|
||||||
|
|
||||||
|
var presetVariables = map[string]bool{
|
||||||
|
"BUILD_ID": true,
|
||||||
|
"HOST_ARCH": true,
|
||||||
|
"HOST_OS": true,
|
||||||
|
"HOST_BUILD_TYPE": true,
|
||||||
|
"OUT_DIR": true,
|
||||||
|
"PLATFORM_VERSION_CODENAME": true,
|
||||||
|
"PLATFORM_VERSION": true,
|
||||||
|
"TARGET_ARCH": true,
|
||||||
|
"TARGET_ARCH_VARIANT": true,
|
||||||
|
"TARGET_BUILD_TYPE": true,
|
||||||
|
"TARGET_BUILD_VARIANT": true,
|
||||||
|
"TARGET_PRODUCT": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// addVariable returns a variable with a given name. A variable is
|
||||||
|
// added if it does not exist yet.
|
||||||
|
func (ctx *parseContext) addVariable(name string) variable {
|
||||||
|
v, found := ctx.variables[name]
|
||||||
|
if !found {
|
||||||
|
_, preset := presetVariables[name]
|
||||||
|
if vi, found := KnownVariables[name]; found {
|
||||||
|
switch vi.class {
|
||||||
|
case VarClassConfig:
|
||||||
|
v = &productConfigVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}}
|
||||||
|
case VarClassSoong:
|
||||||
|
v = &otherGlobalVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}}
|
||||||
|
}
|
||||||
|
} else if name == strings.ToLower(name) {
|
||||||
|
// Heuristics: if variable's name is all lowercase, consider it local
|
||||||
|
// string variable.
|
||||||
|
v = &localVariable{baseVariable{nam: name, typ: starlarkTypeString}}
|
||||||
|
} else {
|
||||||
|
vt := starlarkTypeUnknown
|
||||||
|
if strings.HasPrefix(name, "LOCAL_") {
|
||||||
|
// Heuristics: local variables that contribute to corresponding config variables
|
||||||
|
if cfgVarName, found := localProductConfigVariables[name]; found {
|
||||||
|
vi, found2 := KnownVariables[cfgVarName]
|
||||||
|
if !found2 {
|
||||||
|
panic(fmt.Errorf("unknown config variable %s for %s", cfgVarName, name))
|
||||||
|
}
|
||||||
|
vt = vi.valueType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = &otherGlobalVariable{baseVariable{nam: name, typ: vt}}
|
||||||
|
}
|
||||||
|
ctx.variables[name] = v
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
Loading…
Reference in a new issue