|  | // Copyright 2019 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. | 
|  |  | 
|  | // This is a script that can be used to analyze the results from | 
|  | // build/soong/build_test.bash and recommend what devices need changes to their | 
|  | // BUILD_BROKEN_* flags. | 
|  | // | 
|  | // To use, download the logs.zip from one or more branches, and extract them | 
|  | // into subdirectories of the current directory. So for example, I have: | 
|  | // | 
|  | //   ./aosp-master/aosp_arm/std_full.log | 
|  | //   ./aosp-master/aosp_arm64/std_full.log | 
|  | //   ./aosp-master/... | 
|  | //   ./internal-master/aosp_arm/std_full.log | 
|  | //   ./internal-master/aosp_arm64/std_full.log | 
|  | //   ./internal-master/... | 
|  | // | 
|  | // Then I use `go run path/to/build_broken_logs.go *` | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "io/ioutil" | 
|  | "log" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "sort" | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | func main() { | 
|  | for _, branch := range os.Args[1:] { | 
|  | fmt.Printf("\nBranch %s:\n", branch) | 
|  | PrintResults(ParseBranch(branch)) | 
|  | } | 
|  | } | 
|  |  | 
|  | type BuildBrokenBehavior int | 
|  |  | 
|  | const ( | 
|  | DefaultFalse BuildBrokenBehavior = iota | 
|  | DefaultTrue | 
|  | DefaultDeprecated | 
|  | ) | 
|  |  | 
|  | var buildBrokenSettings = []struct { | 
|  | name     string | 
|  | behavior BuildBrokenBehavior | 
|  | warnings []string | 
|  | }{ | 
|  | { | 
|  | name:     "BUILD_BROKEN_DUP_COPY_HEADERS", | 
|  | behavior: DefaultDeprecated, | 
|  | warnings: []string{"Duplicate header copy:"}, | 
|  | }, | 
|  | { | 
|  | name:     "BUILD_BROKEN_DUP_RULES", | 
|  | behavior: DefaultFalse, | 
|  | warnings: []string{"overriding commands for target"}, | 
|  | }, | 
|  | { | 
|  | name:     "BUILD_BROKEN_ANDROIDMK_EXPORTS", | 
|  | behavior: DefaultFalse, | 
|  | warnings: []string{"export_keyword"}, | 
|  | }, | 
|  | { | 
|  | name:     "BUILD_BROKEN_PHONY_TARGETS", | 
|  | behavior: DefaultFalse, | 
|  | warnings: []string{ | 
|  | "depends on PHONY target", | 
|  | "looks like a real file", | 
|  | "writing to readonly directory", | 
|  | }, | 
|  | }, | 
|  | { | 
|  | name:     "BUILD_BROKEN_ENG_DEBUG_TAGS", | 
|  | behavior: DefaultTrue, | 
|  | warnings: []string{ | 
|  | "Changes.md#LOCAL_MODULE_TAGS", | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | type ProductBranch struct { | 
|  | Branch string | 
|  | Name   string | 
|  | } | 
|  |  | 
|  | type ProductLog struct { | 
|  | ProductBranch | 
|  | Log | 
|  | Device string | 
|  | } | 
|  |  | 
|  | type Log struct { | 
|  | BuildBroken []*bool | 
|  | HasBroken   []bool | 
|  | } | 
|  |  | 
|  | func Merge(l, l2 Log) Log { | 
|  | if len(l.BuildBroken) == 0 { | 
|  | l.BuildBroken = make([]*bool, len(buildBrokenSettings)) | 
|  | } | 
|  | if len(l.HasBroken) == 0 { | 
|  | l.HasBroken = make([]bool, len(buildBrokenSettings)) | 
|  | } | 
|  |  | 
|  | if len(l.BuildBroken) != len(l2.BuildBroken) || len(l.HasBroken) != len(l2.HasBroken) { | 
|  | panic("mis-matched logs") | 
|  | } | 
|  |  | 
|  | for i, v := range l.BuildBroken { | 
|  | if v == nil { | 
|  | l.BuildBroken[i] = l2.BuildBroken[i] | 
|  | } | 
|  | } | 
|  | for i := range l.HasBroken { | 
|  | l.HasBroken[i] = l.HasBroken[i] || l2.HasBroken[i] | 
|  | } | 
|  |  | 
|  | return l | 
|  | } | 
|  |  | 
|  | func PrintResults(products []ProductLog) { | 
|  | devices := map[string]Log{} | 
|  | deviceNames := []string{} | 
|  |  | 
|  | for _, product := range products { | 
|  | device := product.Device | 
|  | if _, ok := devices[device]; !ok { | 
|  | deviceNames = append(deviceNames, device) | 
|  | } | 
|  | devices[device] = Merge(devices[device], product.Log) | 
|  | } | 
|  |  | 
|  | sort.Strings(deviceNames) | 
|  |  | 
|  | for i, setting := range buildBrokenSettings { | 
|  | printed := false | 
|  |  | 
|  | for _, device := range deviceNames { | 
|  | log := devices[device] | 
|  |  | 
|  | if setting.behavior == DefaultTrue { | 
|  | if log.BuildBroken[i] == nil || *log.BuildBroken[i] == false { | 
|  | if log.HasBroken[i] { | 
|  | printed = true | 
|  | fmt.Printf("  %s needs to set %s := true\n", device, setting.name) | 
|  | } | 
|  | } else if !log.HasBroken[i] { | 
|  | printed = true | 
|  | fmt.Printf("  %s sets %s := true, but does not need it\n", device, setting.name) | 
|  | } | 
|  | } else if setting.behavior == DefaultFalse { | 
|  | if log.BuildBroken[i] == nil { | 
|  | // Nothing to be done | 
|  | } else if *log.BuildBroken[i] == false { | 
|  | printed = true | 
|  | fmt.Printf("  %s sets %s := false, which is the default and can be removed\n", device, setting.name) | 
|  | } else if !log.HasBroken[i] { | 
|  | printed = true | 
|  | fmt.Printf("  %s sets %s := true, but does not need it\n", device, setting.name) | 
|  | } | 
|  | } else if setting.behavior == DefaultDeprecated { | 
|  | if log.BuildBroken[i] != nil { | 
|  | printed = true | 
|  | if log.HasBroken[i] { | 
|  | fmt.Printf("  %s sets %s := %v, which is deprecated, but has failures\n", device, setting.name, *log.BuildBroken[i]) | 
|  | } else { | 
|  | fmt.Printf("  %s sets %s := %v, which is deprecated and can be removed\n", device, setting.name, *log.BuildBroken[i]) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if printed { | 
|  | fmt.Println() | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func ParseBranch(name string) []ProductLog { | 
|  | products, err := filepath.Glob(filepath.Join(name, "*")) | 
|  | if err != nil { | 
|  | log.Fatal(err) | 
|  | } | 
|  |  | 
|  | ret := []ProductLog{} | 
|  | for _, product := range products { | 
|  | product = filepath.Base(product) | 
|  |  | 
|  | ret = append(ret, ParseProduct(ProductBranch{Branch: name, Name: product})) | 
|  | } | 
|  | return ret | 
|  | } | 
|  |  | 
|  | func ParseProduct(p ProductBranch) ProductLog { | 
|  | soongLog, err := ioutil.ReadFile(filepath.Join(p.Branch, p.Name, "soong.log")) | 
|  | if err != nil { | 
|  | log.Fatal(err) | 
|  | } | 
|  |  | 
|  | ret := ProductLog{ | 
|  | ProductBranch: p, | 
|  | Log: Log{ | 
|  | BuildBroken: make([]*bool, len(buildBrokenSettings)), | 
|  | HasBroken:   make([]bool, len(buildBrokenSettings)), | 
|  | }, | 
|  | } | 
|  |  | 
|  | lines := strings.Split(string(soongLog), "\n") | 
|  | for _, line := range lines { | 
|  | fields := strings.Split(line, " ") | 
|  | if len(fields) != 5 { | 
|  | continue | 
|  | } | 
|  |  | 
|  | if fields[3] == "TARGET_DEVICE" { | 
|  | ret.Device = fields[4] | 
|  | } | 
|  |  | 
|  | if strings.HasPrefix(fields[3], "BUILD_BROKEN_") { | 
|  | for i, setting := range buildBrokenSettings { | 
|  | if setting.name == fields[3] { | 
|  | ret.BuildBroken[i] = ParseBoolPtr(fields[4]) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | stdLog, err := ioutil.ReadFile(filepath.Join(p.Branch, p.Name, "std_full.log")) | 
|  | if err != nil { | 
|  | log.Fatal(err) | 
|  | } | 
|  | stdStr := string(stdLog) | 
|  |  | 
|  | for i, setting := range buildBrokenSettings { | 
|  | for _, warning := range setting.warnings { | 
|  | if strings.Contains(stdStr, warning) { | 
|  | ret.HasBroken[i] = true | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return ret | 
|  | } | 
|  |  | 
|  | func ParseBoolPtr(str string) *bool { | 
|  | var ret *bool | 
|  | if str != "" { | 
|  | b := str == "true" | 
|  | ret = &b | 
|  | } | 
|  | return ret | 
|  | } |