1// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// This is a script that can be used to analyze the results from
16// build/soong/build_test.bash and recommend what devices need changes to their
17// BUILD_BROKEN_* flags.
18//
19// To use, download the logs.zip from one or more branches, and extract them
20// into subdirectories of the current directory. So for example, I have:
21//
22//   ./aosp-master/aosp_arm/std_full.log
23//   ./aosp-master/aosp_arm64/std_full.log
24//   ./aosp-master/...
25//   ./internal-master/aosp_arm/std_full.log
26//   ./internal-master/aosp_arm64/std_full.log
27//   ./internal-master/...
28//
29// Then I use `go run path/to/build_broken_logs.go *`
30package main
31
32import (
33	"fmt"
34	"io/ioutil"
35	"log"
36	"os"
37	"path/filepath"
38	"sort"
39	"strings"
40)
41
42func main() {
43	for _, branch := range os.Args[1:] {
44		fmt.Printf("\nBranch %s:\n", branch)
45		PrintResults(ParseBranch(branch))
46	}
47}
48
49type BuildBrokenBehavior int
50
51const (
52	DefaultFalse BuildBrokenBehavior = iota
53	DefaultTrue
54	DefaultDeprecated
55)
56
57type Setting struct {
58	name     string
59	behavior BuildBrokenBehavior
60	warnings []string
61}
62
63var buildBrokenSettings = []Setting{
64	{
65		name:     "BUILD_BROKEN_DUP_RULES",
66		behavior: DefaultFalse,
67		warnings: []string{"overriding commands for target"},
68	},
69	{
70		name:     "BUILD_BROKEN_USES_NETWORK",
71		behavior: DefaultDeprecated,
72	},
73	{
74		name:     "BUILD_BROKEN_USES_BUILD_COPY_HEADERS",
75		behavior: DefaultTrue,
76		warnings: []string{
77			"COPY_HEADERS has been deprecated",
78			"COPY_HEADERS is deprecated",
79		},
80	},
81}
82
83type Branch struct {
84	Settings []Setting
85	Logs     []ProductLog
86}
87
88type ProductBranch struct {
89	Branch string
90	Name   string
91}
92
93type ProductLog struct {
94	ProductBranch
95	Log
96	Device string
97}
98
99type Log struct {
100	WarningModuleTypes []string
101	ErrorModuleTypes   []string
102
103	BuildBroken map[string]*bool
104	HasBroken   map[string]int
105}
106
107func Merge(l, l2 Log) Log {
108	if l.BuildBroken == nil {
109		l.BuildBroken = map[string]*bool{}
110	}
111	if l.HasBroken == nil {
112		l.HasBroken = map[string]int{}
113	}
114
115	for n, v := range l.BuildBroken {
116		if v == nil {
117			l.BuildBroken[n] = l2.BuildBroken[n]
118		}
119	}
120	for n, v := range l2.BuildBroken {
121		if _, ok := l.BuildBroken[n]; !ok {
122			l.BuildBroken[n] = v
123		}
124	}
125
126	for n := range l.HasBroken {
127		if l.HasBroken[n] < l2.HasBroken[n] {
128			l.HasBroken[n] = l2.HasBroken[n]
129		}
130	}
131	for n := range l2.HasBroken {
132		if _, ok := l.HasBroken[n]; !ok {
133			l.HasBroken[n] = l2.HasBroken[n]
134		}
135	}
136
137	return l
138}
139
140func PrintResults(branch Branch) {
141	products := branch.Logs
142	devices := map[string]Log{}
143	deviceNames := []string{}
144
145	for _, product := range products {
146		device := product.Device
147		if _, ok := devices[device]; !ok {
148			deviceNames = append(deviceNames, device)
149		}
150		devices[device] = Merge(devices[device], product.Log)
151	}
152
153	sort.Strings(deviceNames)
154
155	for _, setting := range branch.Settings {
156		printed := false
157		n := setting.name
158
159		for _, device := range deviceNames {
160			log := devices[device]
161
162			if setting.behavior == DefaultTrue {
163				if log.BuildBroken[n] == nil || *log.BuildBroken[n] == false {
164					if log.HasBroken[n] > 0 {
165						printed = true
166						plural := ""
167						if log.HasBroken[n] > 1 {
168							plural = "s"
169						}
170						fmt.Printf("  %s needs to set %s := true  (%d instance%s)\n", device, setting.name, log.HasBroken[n], plural)
171					}
172				} else if log.HasBroken[n] == 0 {
173					printed = true
174					fmt.Printf("  %s sets %s := true, but does not need it\n", device, setting.name)
175				}
176			} else if setting.behavior == DefaultFalse {
177				if log.BuildBroken[n] == nil {
178					// Nothing to be done
179				} else if *log.BuildBroken[n] == false {
180					printed = true
181					fmt.Printf("  %s sets %s := false, which is the default and can be removed\n", device, setting.name)
182				} else if log.HasBroken[n] == 0 {
183					printed = true
184					fmt.Printf("  %s sets %s := true, but does not need it\n", device, setting.name)
185				}
186			} else if setting.behavior == DefaultDeprecated {
187				if log.BuildBroken[n] != nil {
188					printed = true
189					if log.HasBroken[n] > 0 {
190						plural := ""
191						if log.HasBroken[n] > 1 {
192							plural = "s"
193						}
194						fmt.Printf("  %s sets %s := %v, which is deprecated, but has %d failure%s\n", device, setting.name, *log.BuildBroken[n], log.HasBroken[n], plural)
195					} else {
196						fmt.Printf("  %s sets %s := %v, which is deprecated and can be removed\n", device, setting.name, *log.BuildBroken[n])
197					}
198				}
199			}
200		}
201
202		if printed {
203			fmt.Println()
204		}
205	}
206}
207
208func ParseBranch(name string) Branch {
209	products, err := filepath.Glob(filepath.Join(name, "*"))
210	if err != nil {
211		log.Fatal(err)
212	}
213
214	ret := Branch{Logs: []ProductLog{}}
215	for _, product := range products {
216		product = filepath.Base(product)
217
218		ret.Logs = append(ret.Logs, ParseProduct(ProductBranch{Branch: name, Name: product}))
219	}
220
221	ret.Settings = append(ret.Settings, buildBrokenSettings...)
222	if len(ret.Logs) > 0 {
223		for _, mtype := range ret.Logs[0].WarningModuleTypes {
224			if mtype == "BUILD_COPY_HEADERS" || mtype == "" {
225				continue
226			}
227			ret.Settings = append(ret.Settings, Setting{
228				name:     "BUILD_BROKEN_USES_" + mtype,
229				behavior: DefaultTrue,
230				warnings: []string{mtype + " has been deprecated"},
231			})
232		}
233		for _, mtype := range ret.Logs[0].ErrorModuleTypes {
234			if mtype == "BUILD_COPY_HEADERS" || mtype == "" {
235				continue
236			}
237			ret.Settings = append(ret.Settings, Setting{
238				name:     "BUILD_BROKEN_USES_" + mtype,
239				behavior: DefaultFalse,
240				warnings: []string{mtype + " has been deprecated"},
241			})
242		}
243	}
244
245	for _, productLog := range ret.Logs {
246		ScanProduct(ret.Settings, productLog)
247	}
248	return ret
249}
250
251func ParseProduct(p ProductBranch) ProductLog {
252	soongLog, err := ioutil.ReadFile(filepath.Join(p.Branch, p.Name, "soong.log"))
253	if err != nil {
254		log.Fatal(err)
255	}
256
257	ret := ProductLog{
258		ProductBranch: p,
259		Log: Log{
260			BuildBroken: map[string]*bool{},
261			HasBroken:   map[string]int{},
262		},
263	}
264
265	lines := strings.Split(string(soongLog), "\n")
266	for _, line := range lines {
267		fields := strings.Split(line, " ")
268		if len(fields) < 5 {
269			continue
270		}
271
272		if fields[3] == "TARGET_DEVICE" {
273			ret.Device = fields[4]
274		}
275
276		if fields[3] == "DEFAULT_WARNING_BUILD_MODULE_TYPES" {
277			ret.WarningModuleTypes = fields[4:]
278		}
279		if fields[3] == "DEFAULT_ERROR_BUILD_MODULE_TYPES" {
280			ret.ErrorModuleTypes = fields[4:]
281		}
282
283		if strings.HasPrefix(fields[3], "BUILD_BROKEN_") {
284			ret.BuildBroken[fields[3]] = ParseBoolPtr(fields[4])
285		}
286	}
287
288	return ret
289}
290
291func ScanProduct(settings []Setting, l ProductLog) {
292	stdLog, err := ioutil.ReadFile(filepath.Join(l.Branch, l.Name, "std_full.log"))
293	if err != nil {
294		log.Fatal(err)
295	}
296	stdStr := string(stdLog)
297
298	for _, setting := range settings {
299		for _, warning := range setting.warnings {
300			if strings.Contains(stdStr, warning) {
301				l.HasBroken[setting.name] += strings.Count(stdStr, warning)
302			}
303		}
304	}
305}
306
307func ParseBoolPtr(str string) *bool {
308	var ret *bool
309	if str != "" {
310		b := str == "true"
311		ret = &b
312	}
313	return ret
314}
315