Golang程序  |  271行  |  6.31 KB

// 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",
		},
	},
	{
		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
}