// Copyright 2021 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // merge_module_info_json is a utility that merges module_info.json files generated by // Soong and Make. package main import ( "android/soong/response" "cmp" "encoding/json" "flag" "fmt" "os" "slices" ) var ( out = flag.String("o", "", "output file") listFile = flag.String("l", "", "input file list file") ) func usage() { fmt.Fprintf(os.Stderr, "usage: %s -o \n", os.Args[0]) flag.PrintDefaults() fmt.Fprintln(os.Stderr, "merge_module_info_json reads input files that each contain an array of json objects") fmt.Fprintln(os.Stderr, "and writes them out as a single json array to the output file.") os.Exit(2) } func main() { flag.Usage = usage flag.Parse() if *out == "" { fmt.Fprintf(os.Stderr, "%s: error: -o is required\n", os.Args[0]) usage() } inputs := flag.Args() if *listFile != "" { listFileInputs, err := readListFile(*listFile) if err != nil { fmt.Fprintf(os.Stderr, "failed to read list file %s: %s", *listFile, err) os.Exit(1) } inputs = append(inputs, listFileInputs...) } err := mergeJsonObjects(*out, inputs) if err != nil { fmt.Fprintf(os.Stderr, "%s: error: %s\n", os.Args[0], err.Error()) os.Exit(1) } } func readListFile(file string) ([]string, error) { f, err := os.Open(*listFile) if err != nil { return nil, err } return response.ReadRspFile(f) } func mergeJsonObjects(output string, inputs []string) error { combined := make(map[string]any) for _, input := range inputs { objects, err := decodeObjectFromJson(input) if err != nil { return err } for _, object := range objects { for k, v := range object { if old, exists := combined[k]; exists { v = combine(old, v) } combined[k] = v } } } f, err := os.Create(output) if err != nil { return fmt.Errorf("failed to open output file: %w", err) } encoder := json.NewEncoder(f) encoder.SetIndent("", " ") err = encoder.Encode(combined) if err != nil { return fmt.Errorf("failed to encode to output file: %w", err) } return nil } func decodeObjectFromJson(input string) ([]map[string]any, error) { f, err := os.Open(input) if err != nil { return nil, fmt.Errorf("failed to open input file: %w", err) } decoder := json.NewDecoder(f) var object any err = decoder.Decode(&object) if err != nil { return nil, fmt.Errorf("failed to parse input file %q: %w", input, err) } switch o := object.(type) { case []any: var ret []map[string]any for _, arrayElement := range o { if m, ok := arrayElement.(map[string]any); ok { ret = append(ret, m) } else { return nil, fmt.Errorf("unknown JSON type in array %T", arrayElement) } } return ret, nil case map[string]any: return []map[string]any{o}, nil } return nil, fmt.Errorf("unknown JSON type %T", object) } func combine(old, new any) any { // fmt.Printf("%#v %#v\n", old, new) switch oldTyped := old.(type) { case map[string]any: if newObject, ok := new.(map[string]any); ok { return combineObjects(oldTyped, newObject) } else { panic(fmt.Errorf("expected map[string]any, got %#v", new)) } case []any: if newArray, ok := new.([]any); ok { return combineArrays(oldTyped, newArray) } else { panic(fmt.Errorf("expected []any, got %#v", new)) } case string: if newString, ok := new.(string); ok { if oldTyped != newString { panic(fmt.Errorf("strings %q and %q don't match", oldTyped, newString)) } return oldTyped } else { panic(fmt.Errorf("expected []any, got %#v", new)) } default: panic(fmt.Errorf("can't combine type %T", old)) } } func combineObjects(old, new map[string]any) map[string]any { for k, newField := range new { // HACK: Don't merge "test_config" field. This matches the behavior in base_rules.mk that overwrites // instead of appending ALL_MODULES.$(my_register_name).TEST_CONFIG, keeping the if k == "test_config" { old[k] = newField continue } if oldField, exists := old[k]; exists { oldField = combine(oldField, newField) old[k] = oldField } else { old[k] = newField } } return old } func combineArrays(old, new []any) []any { containsNonStrings := false for _, oldElement := range old { switch oldElement.(type) { case string: default: containsNonStrings = true } } for _, newElement := range new { found := false for _, oldElement := range old { if oldElement == newElement { found = true break } } if !found { switch newElement.(type) { case string: default: containsNonStrings = true } old = append(old, newElement) } } if !containsNonStrings { slices.SortFunc(old, func(a, b any) int { return cmp.Compare(a.(string), b.(string)) }) } return old }