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 15package mk2rbc 16 17import ( 18 "bytes" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "regexp" 23 "strings" 24 25 mkparser "android/soong/androidmk/parser" 26) 27 28type 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// 36// $(call add_json_XXX, <...>, $(VAR)) 37func FindSoongVariables(mkFile string, includeFileScope mkparser.Scope, registrar variableRegistrar) error { 38 ctx := context{includeFileScope, registrar} 39 return ctx.doFind(mkFile) 40} 41 42func (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 66func (ctx context) NewSoongVariable(name, typeString string) { 67 var valueType starlarkType 68 switch typeString { 69 case "bool": 70 // TODO: We run into several issues later on if we type this as a bool: 71 // - We still assign bool-typed variables to strings 72 // - When emitting the final results as make code, some bool's false values have to 73 // be an empty string, and some have to be false in order to match the make variables. 74 valueType = starlarkTypeString 75 case "csv": 76 // Only PLATFORM_VERSION_ALL_CODENAMES, and it's a list 77 valueType = starlarkTypeList 78 case "list": 79 valueType = starlarkTypeList 80 case "str": 81 valueType = starlarkTypeString 82 case "val": 83 // Only PLATFORM_SDK_VERSION uses this, and it's integer 84 valueType = starlarkTypeInt 85 default: 86 panic(fmt.Errorf("unknown Soong variable type %s", typeString)) 87 } 88 89 ctx.registrar.NewVariable(name, VarClassSoong, valueType) 90} 91 92func (ctx context) handleInclude(t *mkparser.Directive) { 93 if t.Name != "include" && t.Name != "-include" { 94 return 95 } 96 includedPath := t.Args.Value(ctx.includeFileScope) 97 err := ctx.doFind(includedPath) 98 if err != nil && t.Name == "include" { 99 fmt.Fprintf(os.Stderr, "cannot include %s: %s", includedPath, err) 100 } 101} 102 103var callFuncRex = regexp.MustCompile("^call +add_json_(str|val|bool|csv|list) *,") 104 105func (ctx context) handleVariable(t *mkparser.Variable) { 106 // From the variable reference looking as follows: 107 // $(call json_add_TYPE,arg1,$(VAR)) 108 // we infer that the type of $(VAR) is TYPE 109 // VAR can be a simple variable name, or another call 110 // (e.g., $(call invert_bool, $(X)), from which we can infer 111 // that the type of X is bool 112 if prefix, v, ok := prefixedVariable(t.Name); ok && strings.HasPrefix(prefix, "call add_json") { 113 if match := callFuncRex.FindStringSubmatch(prefix); match != nil { 114 ctx.inferSoongVariableType(match[1], v) 115 // NOTE(asmundak): sometimes arg1 (the name of the Soong variable defined 116 // in this statement) may indicate that there is a Make counterpart. E.g, from 117 // $(call add_json_bool, DisablePreopt, $(call invert_bool,$(ENABLE_PREOPT))) 118 // it may be inferred that there is a Make boolean variable DISABLE_PREOPT. 119 // Unfortunately, Soong variable names have no 1:1 correspondence to Make variables, 120 // for instance, 121 // $(call add_json_list, PatternsOnSystemOther, $(SYSTEM_OTHER_ODEX_FILTER)) 122 // does not mean that there is PATTERNS_ON_SYSTEM_OTHER 123 // Our main interest lies in finding the variables whose values are lists, and 124 // so far there are none that can be found this way, so it is not important. 125 } else { 126 panic(fmt.Errorf("cannot match the call: %s", prefix)) 127 } 128 } 129} 130 131var ( 132 callInvertBoolRex = regexp.MustCompile("^call +invert_bool *, *$") 133 callFilterBoolRex = regexp.MustCompile("^(filter|filter-out) +(true|false), *$") 134) 135 136func (ctx context) inferSoongVariableType(vType string, n *mkparser.MakeString) { 137 if n.Const() { 138 ctx.NewSoongVariable(n.Strings[0], vType) 139 return 140 } 141 if prefix, v, ok := prefixedVariable(n); ok { 142 if callInvertBoolRex.MatchString(prefix) || callFilterBoolRex.MatchString(prefix) { 143 // It is $(call invert_bool, $(VAR)) or $(filter[-out] [false|true],$(VAR)) 144 ctx.inferSoongVariableType("bool", v) 145 } 146 } 147} 148 149// If MakeString is foo$(BAR), returns 'foo', BAR(as *MakeString) and true 150func prefixedVariable(s *mkparser.MakeString) (string, *mkparser.MakeString, bool) { 151 if len(s.Strings) != 2 || s.Strings[1] != "" { 152 return "", nil, false 153 } 154 return s.Strings[0], s.Variables[0].Name, true 155} 156