Sasha Smundak | 1471eb2 | 2021-03-10 12:10:29 -0800 | [diff] [blame] | 1 | // Copyright 2021 Google LLC |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package mk2rbc |
| 16 | |
| 17 | import ( |
| 18 | "bytes" |
| 19 | "fmt" |
| 20 | "io/ioutil" |
| 21 | "os" |
| 22 | "regexp" |
| 23 | "strings" |
| 24 | |
| 25 | mkparser "android/soong/androidmk/parser" |
| 26 | ) |
| 27 | |
| 28 | type context struct { |
| 29 | includeFileScope mkparser.Scope |
| 30 | registrar variableRegistrar |
| 31 | } |
| 32 | |
| 33 | // Scans the makefile Soong uses to generate soong.variables file, |
| 34 | // collecting variable names and types from the lines that look like this: |
| 35 | // $(call add_json_XXX, <...>, $(VAR)) |
| 36 | // |
| 37 | func FindSoongVariables(mkFile string, includeFileScope mkparser.Scope, registrar variableRegistrar) error { |
| 38 | ctx := context{includeFileScope, registrar} |
| 39 | return ctx.doFind(mkFile) |
| 40 | } |
| 41 | |
| 42 | func (ctx *context) doFind(mkFile string) error { |
| 43 | mkContents, err := ioutil.ReadFile(mkFile) |
| 44 | if err != nil { |
| 45 | return err |
| 46 | } |
| 47 | parser := mkparser.NewParser(mkFile, bytes.NewBuffer(mkContents)) |
| 48 | nodes, errs := parser.Parse() |
| 49 | if len(errs) > 0 { |
| 50 | for _, e := range errs { |
| 51 | fmt.Fprintln(os.Stderr, "ERROR:", e) |
| 52 | } |
| 53 | return fmt.Errorf("cannot parse %s", mkFile) |
| 54 | } |
| 55 | for _, node := range nodes { |
| 56 | switch t := node.(type) { |
| 57 | case *mkparser.Variable: |
| 58 | ctx.handleVariable(t) |
| 59 | case *mkparser.Directive: |
| 60 | ctx.handleInclude(t) |
| 61 | } |
| 62 | } |
| 63 | return nil |
| 64 | } |
| 65 | |
| 66 | func (ctx context) NewSoongVariable(name, typeString string) { |
| 67 | var valueType starlarkType |
| 68 | switch typeString { |
| 69 | case "bool": |
| 70 | valueType = starlarkTypeBool |
| 71 | case "csv": |
| 72 | // Only PLATFORM_VERSION_ALL_CODENAMES, and it's a list |
| 73 | valueType = starlarkTypeList |
| 74 | case "list": |
| 75 | valueType = starlarkTypeList |
| 76 | case "str": |
| 77 | valueType = starlarkTypeString |
| 78 | case "val": |
| 79 | // Only PLATFORM_SDK_VERSION uses this, and it's integer |
| 80 | valueType = starlarkTypeInt |
| 81 | default: |
| 82 | panic(fmt.Errorf("unknown Soong variable type %s", typeString)) |
| 83 | } |
| 84 | |
| 85 | ctx.registrar.NewVariable(name, VarClassSoong, valueType) |
| 86 | } |
| 87 | |
| 88 | func (ctx context) handleInclude(t *mkparser.Directive) { |
| 89 | if t.Name != "include" && t.Name != "-include" { |
| 90 | return |
| 91 | } |
| 92 | includedPath := t.Args.Value(ctx.includeFileScope) |
| 93 | err := ctx.doFind(includedPath) |
| 94 | if err != nil && t.Name == "include" { |
| 95 | fmt.Fprintf(os.Stderr, "cannot include %s: %s", includedPath, err) |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | var callFuncRex = regexp.MustCompile("^call +add_json_(str|val|bool|csv|list) *,") |
| 100 | |
| 101 | func (ctx context) handleVariable(t *mkparser.Variable) { |
| 102 | // From the variable reference looking as follows: |
| 103 | // $(call json_add_TYPE,arg1,$(VAR)) |
| 104 | // we infer that the type of $(VAR) is TYPE |
| 105 | // VAR can be a simple variable name, or another call |
| 106 | // (e.g., $(call invert_bool, $(X)), from which we can infer |
| 107 | // that the type of X is bool |
| 108 | if prefix, v, ok := prefixedVariable(t.Name); ok && strings.HasPrefix(prefix, "call add_json") { |
| 109 | if match := callFuncRex.FindStringSubmatch(prefix); match != nil { |
| 110 | ctx.inferSoongVariableType(match[1], v) |
| 111 | // NOTE(asmundak): sometimes arg1 (the name of the Soong variable defined |
| 112 | // in this statement) may indicate that there is a Make counterpart. E.g, from |
| 113 | // $(call add_json_bool, DisablePreopt, $(call invert_bool,$(ENABLE_PREOPT))) |
| 114 | // it may be inferred that there is a Make boolean variable DISABLE_PREOPT. |
| 115 | // Unfortunately, Soong variable names have no 1:1 correspondence to Make variables, |
| 116 | // for instance, |
| 117 | // $(call add_json_list, PatternsOnSystemOther, $(SYSTEM_OTHER_ODEX_FILTER)) |
| 118 | // does not mean that there is PATTERNS_ON_SYSTEM_OTHER |
| 119 | // Our main interest lies in finding the variables whose values are lists, and |
| 120 | // so far there are none that can be found this way, so it is not important. |
| 121 | } else { |
| 122 | panic(fmt.Errorf("cannot match the call: %s", prefix)) |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | var ( |
| 128 | callInvertBoolRex = regexp.MustCompile("^call +invert_bool *, *$") |
| 129 | callFilterBoolRex = regexp.MustCompile("^(filter|filter-out) +(true|false), *$") |
| 130 | ) |
| 131 | |
| 132 | func (ctx context) inferSoongVariableType(vType string, n *mkparser.MakeString) { |
| 133 | if n.Const() { |
| 134 | ctx.NewSoongVariable(n.Strings[0], vType) |
| 135 | return |
| 136 | } |
| 137 | if prefix, v, ok := prefixedVariable(n); ok { |
| 138 | if callInvertBoolRex.MatchString(prefix) || callFilterBoolRex.MatchString(prefix) { |
| 139 | // It is $(call invert_bool, $(VAR)) or $(filter[-out] [false|true],$(VAR)) |
| 140 | ctx.inferSoongVariableType("bool", v) |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | // If MakeString is foo$(BAR), returns 'foo', BAR(as *MakeString) and true |
| 146 | func prefixedVariable(s *mkparser.MakeString) (string, *mkparser.MakeString, bool) { |
| 147 | if len(s.Strings) != 2 || s.Strings[1] != "" { |
| 148 | return "", nil, false |
| 149 | } |
| 150 | return s.Strings[0], s.Variables[0].Name, true |
| 151 | } |