• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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