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