Merge "Support generating module_info.json in Soong" into main
This commit is contained in:
30
cmd/merge_module_info_json/Android.bp
Normal file
30
cmd/merge_module_info_json/Android.bp
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
blueprint_go_binary {
|
||||
name: "merge_module_info_json",
|
||||
srcs: [
|
||||
"merge_module_info_json.go",
|
||||
],
|
||||
deps: [
|
||||
"soong-response",
|
||||
],
|
||||
testSrcs: [
|
||||
"merge_module_info_json_test.go",
|
||||
],
|
||||
}
|
223
cmd/merge_module_info_json/merge_module_info_json.go
Normal file
223
cmd/merge_module_info_json/merge_module_info_json.go
Normal file
@@ -0,0 +1,223 @@
|
||||
// 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 <output file> <input files>\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
|
||||
}
|
58
cmd/merge_module_info_json/merge_module_info_json_test.go
Normal file
58
cmd/merge_module_info_json/merge_module_info_json_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_combine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
old any
|
||||
new any
|
||||
want any
|
||||
}{
|
||||
{
|
||||
name: "objects",
|
||||
old: map[string]any{
|
||||
"foo": "bar",
|
||||
"baz": []any{"a"},
|
||||
},
|
||||
new: map[string]any{
|
||||
"foo": "bar",
|
||||
"baz": []any{"b"},
|
||||
},
|
||||
want: map[string]any{
|
||||
"foo": "bar",
|
||||
"baz": []any{"a", "b"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "arrays",
|
||||
old: []any{"foo", "bar"},
|
||||
new: []any{"foo", "baz"},
|
||||
want: []any{"bar", "baz", "foo"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := combine(tt.old, tt.new); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("combine() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user