blob: de46925282623d91d1f304ad72e12227f307251c [file] [log] [blame]
Sasha Smundak1471eb22021-03-10 12:10:29 -08001// 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}