| // 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_RULES", |
| behavior: DefaultFalse, |
| warnings: []string{"overriding commands for target"}, |
| }, |
| { |
| name: "BUILD_BROKEN_USES_NETWORK", |
| behavior: DefaultDeprecated, |
| }, |
| } |
| |
| 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 |
| } |