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// $(call add_json_XXX, <...>, $(VAR)) 36// 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 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 88func (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 99var callFuncRex = regexp.MustCompile("^call +add_json_(str|val|bool|csv|list) *,") 100 101func (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 127var ( 128 callInvertBoolRex = regexp.MustCompile("^call +invert_bool *, *$") 129 callFilterBoolRex = regexp.MustCompile("^(filter|filter-out) +(true|false), *$") 130) 131 132func (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 146func 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} 152