diff --git a/scripts/build_broken_logs.go b/scripts/build_broken_logs.go new file mode 100644 index 000000000..73832a654 --- /dev/null +++ b/scripts/build_broken_logs.go @@ -0,0 +1,266 @@ +// 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 +} diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go index 1ab855d02..98bb1c56d 100644 --- a/ui/build/dumpvars.go +++ b/ui/build/dumpvars.go @@ -206,6 +206,7 @@ func runMakeProductConfig(ctx Context, config Config) { // Not used, but useful to be in the soong.log "BUILD_BROKEN_ANDROIDMK_EXPORTS", "BUILD_BROKEN_DUP_COPY_HEADERS", + "BUILD_BROKEN_ENG_DEBUG_TAGS", }, exportEnvVars...), BannerVars...) make_vars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true)