blob: 7a6aa5ffae51d2b26c7288cf738810e979dc0c62 [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:
Sasha Smundak1471eb22021-03-10 12:10:29 -080035//
Colin Crossd079e0b2022-08-16 10:27:33 -070036// $(call add_json_XXX, <...>, $(VAR))
Sasha Smundak1471eb22021-03-10 12:10:29 -080037func 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":
Cole Faustc7335de2023-04-12 12:13:28 -070070 // 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
Sasha Smundak1471eb22021-03-10 12:10:29 -080075 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}