• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2021 Google Inc. All rights reserved.
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
15// merge_module_info_json is a utility that merges module_info.json files generated by
16// Soong and Make.
17
18package main
19
20import (
21	"android/soong/response"
22	"cmp"
23	"encoding/json"
24	"flag"
25	"fmt"
26	"os"
27	"slices"
28)
29
30var (
31	out      = flag.String("o", "", "output file")
32	listFile = flag.String("l", "", "input file list file")
33)
34
35func usage() {
36	fmt.Fprintf(os.Stderr, "usage: %s -o <output file> <input files>\n", os.Args[0])
37	flag.PrintDefaults()
38	fmt.Fprintln(os.Stderr, "merge_module_info_json reads input files that each contain an array of json objects")
39	fmt.Fprintln(os.Stderr, "and writes them out as a single json array to the output file.")
40
41	os.Exit(2)
42}
43
44func main() {
45	flag.Usage = usage
46	flag.Parse()
47
48	if *out == "" {
49		fmt.Fprintf(os.Stderr, "%s: error: -o is required\n", os.Args[0])
50		usage()
51	}
52
53	inputs := flag.Args()
54	if *listFile != "" {
55		listFileInputs, err := readListFile(*listFile)
56		if err != nil {
57			fmt.Fprintf(os.Stderr, "failed to read list file %s: %s", *listFile, err)
58			os.Exit(1)
59		}
60		inputs = append(inputs, listFileInputs...)
61	}
62
63	err := mergeJsonObjects(*out, inputs)
64
65	if err != nil {
66		fmt.Fprintf(os.Stderr, "%s: error: %s\n", os.Args[0], err.Error())
67		os.Exit(1)
68	}
69}
70
71func readListFile(file string) ([]string, error) {
72	f, err := os.Open(*listFile)
73	if err != nil {
74		return nil, err
75	}
76	return response.ReadRspFile(f)
77}
78
79func mergeJsonObjects(output string, inputs []string) error {
80	combined := make(map[string]any)
81	for _, input := range inputs {
82		objects, err := decodeObjectFromJson(input)
83		if err != nil {
84			return err
85		}
86
87		for _, object := range objects {
88			for k, v := range object {
89				if old, exists := combined[k]; exists {
90					v = combine(old, v)
91				}
92				combined[k] = v
93			}
94		}
95	}
96
97	f, err := os.Create(output)
98	if err != nil {
99		return fmt.Errorf("failed to open output file: %w", err)
100	}
101	encoder := json.NewEncoder(f)
102	encoder.SetIndent("", "  ")
103	err = encoder.Encode(combined)
104	if err != nil {
105		return fmt.Errorf("failed to encode to output file: %w", err)
106	}
107
108	return nil
109}
110
111func decodeObjectFromJson(input string) ([]map[string]any, error) {
112	f, err := os.Open(input)
113	if err != nil {
114		return nil, fmt.Errorf("failed to open input file: %w", err)
115	}
116
117	decoder := json.NewDecoder(f)
118	var object any
119	err = decoder.Decode(&object)
120	if err != nil {
121		return nil, fmt.Errorf("failed to parse input file %q: %w", input, err)
122	}
123
124	switch o := object.(type) {
125	case []any:
126		var ret []map[string]any
127		for _, arrayElement := range o {
128			if m, ok := arrayElement.(map[string]any); ok {
129				ret = append(ret, m)
130			} else {
131				return nil, fmt.Errorf("unknown JSON type in array %T", arrayElement)
132			}
133		}
134		return ret, nil
135
136	case map[string]any:
137		return []map[string]any{o}, nil
138	}
139
140	return nil, fmt.Errorf("unknown JSON type %T", object)
141}
142
143func combine(old, new any) any {
144	//	fmt.Printf("%#v %#v\n", old, new)
145	switch oldTyped := old.(type) {
146	case map[string]any:
147		if newObject, ok := new.(map[string]any); ok {
148			return combineObjects(oldTyped, newObject)
149		} else {
150			panic(fmt.Errorf("expected map[string]any, got %#v", new))
151		}
152	case []any:
153		if newArray, ok := new.([]any); ok {
154			return combineArrays(oldTyped, newArray)
155		} else {
156			panic(fmt.Errorf("expected []any, got %#v", new))
157		}
158	case string:
159		if newString, ok := new.(string); ok {
160			if oldTyped != newString {
161				panic(fmt.Errorf("strings %q and %q don't match", oldTyped, newString))
162			}
163			return oldTyped
164		} else {
165			panic(fmt.Errorf("expected []any, got %#v", new))
166		}
167	default:
168		panic(fmt.Errorf("can't combine type %T", old))
169	}
170}
171
172func combineObjects(old, new map[string]any) map[string]any {
173	for k, newField := range new {
174		// HACK: Don't merge "test_config" field.  This matches the behavior in base_rules.mk that overwrites
175		// instead of appending ALL_MODULES.$(my_register_name).TEST_CONFIG, keeping the
176		if k == "test_config" {
177			old[k] = newField
178			continue
179		}
180		if oldField, exists := old[k]; exists {
181			oldField = combine(oldField, newField)
182			old[k] = oldField
183		} else {
184			old[k] = newField
185		}
186	}
187
188	return old
189}
190
191func combineArrays(old, new []any) []any {
192	containsNonStrings := false
193	for _, oldElement := range old {
194		switch oldElement.(type) {
195		case string:
196		default:
197			containsNonStrings = true
198		}
199	}
200	for _, newElement := range new {
201		found := false
202		for _, oldElement := range old {
203			if oldElement == newElement {
204				found = true
205				break
206			}
207		}
208		if !found {
209			switch newElement.(type) {
210			case string:
211			default:
212				containsNonStrings = true
213			}
214			old = append(old, newElement)
215		}
216	}
217	if !containsNonStrings {
218		slices.SortFunc(old, func(a, b any) int {
219			return cmp.Compare(a.(string), b.(string))
220		})
221	}
222	return old
223}
224