• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2020 The Android Open Source Project
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 main
16
17import (
18	"encoding/json"
19	"flag"
20	"fmt"
21	"io"
22	"os"
23	"strings"
24
25	"github.com/google/blueprint/parser"
26)
27
28type FlatModule struct {
29	Type        string
30	Name        string
31	PropertyMap map[string]interface{}
32}
33
34func expandScalarTypeExpression(value parser.Expression) (scalar interface{}, isScalar bool) {
35	if s, ok := value.(*parser.Bool); ok {
36		return s.Value, true
37	} else if s, ok := value.(*parser.String); ok {
38		return s.Value, true
39	} else if s, ok := value.(*parser.Int64); ok {
40		return s.Value, true
41	}
42	return nil, false
43}
44
45func populatePropertyMap(propMap map[string]interface{}, prefix string, m *parser.Map) {
46	for _, prop := range m.Properties {
47		name := prop.Name
48		if prefix != "" {
49			name = prefix + "." + name
50		}
51		value := prop.Value.Eval()
52		if s, isScalar := expandScalarTypeExpression(value); isScalar {
53			propMap[name] = s
54		} else if list, ok := value.(*parser.List); ok {
55			var l []interface{}
56			for _, v := range list.Values {
57				if s, isScalar := expandScalarTypeExpression(v.Eval()); isScalar {
58					l = append(l, s)
59				}
60			}
61			propMap[name] = l
62		} else if mm, ok := value.(*parser.Map); ok {
63			populatePropertyMap(propMap, name, mm)
64		}
65	}
66}
67
68var anonymousModuleCount int
69
70func flattenModule(module *parser.Module) (flattened FlatModule) {
71	flattened.Type = module.Type
72	if prop, found := module.GetProperty("name"); found {
73		if value, ok := prop.Value.Eval().(*parser.String); ok {
74			flattened.Name = value.Value
75		}
76	} else {
77		flattened.Name = fmt.Sprintf("anonymous@<%d>", anonymousModuleCount)
78		anonymousModuleCount++
79	}
80	flattened.PropertyMap = make(map[string]interface{})
81	populatePropertyMap(flattened.PropertyMap, "", &module.Map)
82	return flattened
83}
84
85func processFile(filename string, in io.Reader) ([]FlatModule, error) {
86	if in == nil {
87		if file, err := os.Open(filename); err != nil {
88			return nil, err
89		} else {
90			defer file.Close()
91			in = file
92		}
93	}
94
95	ast, errs := parser.ParseAndEval(filename, in, &parser.Scope{})
96	if len(errs) > 0 {
97		for _, err := range errs {
98			fmt.Fprintln(os.Stderr, err)
99		}
100		return nil, fmt.Errorf("%d parsing errors", len(errs))
101	}
102
103	var modules []FlatModule
104	for _, def := range ast.Defs {
105		if module, ok := def.(*parser.Module); ok {
106			modules = append(modules, flattenModule(module))
107		}
108	}
109	return modules, nil
110}
111
112func quoteBashString(s string) string {
113	return strings.ReplaceAll(s, "$", "\\$")
114}
115
116func printBash(flatModules []FlatModule, w io.Writer) {
117	var moduleNameList []string
118	if len(flatModules) == 0 {
119		// Early bail out if we have nothing to output
120		return
121	}
122	fmt.Fprintf(w, "declare -a MODULE_NAMES\n")
123	fmt.Fprintf(w, "declare -A MODULE_TYPE_DICT\n")
124	fmt.Fprintf(w, "declare -A MODULE_PROP_KEYS_DICT\n")
125	fmt.Fprintf(w, "declare -A MODULE_PROP_VALUES_DICT\n")
126	fmt.Fprintf(w, "\n")
127	for _, module := range flatModules {
128		name := quoteBashString(module.Name)
129		moduleNameList = append(moduleNameList, name)
130		var modulePropKeys []string
131		for k := range module.PropertyMap {
132			modulePropKeys = append(modulePropKeys, k)
133		}
134		fmt.Fprintf(w, "MODULE_TYPE_DICT[%q]=%q\n", name, quoteBashString(module.Type))
135		fmt.Fprintf(w, "MODULE_PROP_KEYS_DICT[%q]=%q\n", name,
136			quoteBashString(strings.Join(modulePropKeys, " ")))
137		for k, v := range module.PropertyMap {
138			var propValue string
139			if vl, ok := v.([]interface{}); ok {
140				var list []string
141				for _, s := range vl {
142					list = append(list, fmt.Sprintf("%v", s))
143				}
144				propValue = fmt.Sprintf("%s", strings.Join(list, " "))
145			} else {
146				propValue = fmt.Sprintf("%v", v)
147			}
148			key := name + ":" + quoteBashString(k)
149			fmt.Fprintf(w, "MODULE_PROP_VALUES_DICT[%q]=%q\n", key, quoteBashString(propValue))
150		}
151		fmt.Fprintf(w, "\n")
152	}
153	fmt.Fprintf(w, "MODULE_NAMES=(\n")
154	for _, name := range moduleNameList {
155		fmt.Fprintf(w, "  %q\n", name)
156	}
157	fmt.Fprintf(w, ")\n")
158}
159
160var (
161	outputBashFlag = flag.Bool("bash", false, "Output in bash format")
162	outputJsonFlag = flag.Bool("json", false, "Output in json format (this is the default)")
163	helpFlag = flag.Bool("help", false, "Display this message and exit")
164	exitCode = 0
165)
166
167func init() {
168	flag.Usage = usage
169}
170
171func usage() {
172	fmt.Fprintf(os.Stderr, "Usage: %s [OPTION]... [FILE]...\n", os.Args[0])
173	fmt.Fprintf(os.Stderr, "Flatten Android.bp to python friendly json text.\n")
174	fmt.Fprintf(os.Stderr, "If no file list is specified, read from standard input.\n")
175	fmt.Fprintf(os.Stderr, "\n")
176	flag.PrintDefaults()
177}
178
179func main() {
180	defer func() {
181		if err := recover(); err != nil {
182			fmt.Fprintf(os.Stderr, "error: %v\n", err)
183			exitCode = 1
184		}
185		os.Exit(exitCode)
186	}()
187
188	flag.Parse()
189
190	if *helpFlag {
191		usage()
192		return
193	}
194
195	flatModules := []FlatModule{}
196
197	if flag.NArg() == 0 {
198		if modules, err := processFile("<stdin>", os.Stdin); err != nil {
199			panic(err)
200		} else {
201			flatModules = append(flatModules, modules...)
202		}
203	}
204
205	for _, pathname := range flag.Args() {
206		switch fileInfo, err := os.Stat(pathname); {
207		case err != nil:
208			panic(err)
209		case fileInfo.IsDir():
210			panic(fmt.Errorf("%q is a directory", pathname))
211		default:
212			if modules, err := processFile(pathname, nil); err != nil {
213				panic(err)
214			} else {
215				flatModules = append(flatModules, modules...)
216			}
217		}
218	}
219
220	if *outputBashFlag {
221		printBash(flatModules, os.Stdout)
222	} else {
223		if b, err := json.MarshalIndent(flatModules, "", "  "); err != nil {
224			panic(err)
225		} else {
226			fmt.Printf("%s\n", b)
227		}
228	}
229}
230