| // 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 |
| } |