blob: 73832a65484fa5a6fa264122148308785c120c08 [file] [log] [blame]
Dan Willemsen01f0a052019-02-05 13:44:20 -08001// 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
57var buildBrokenSettings = []struct {
58 name string
59 behavior BuildBrokenBehavior
60 warnings []string
61}{
62 {
63 name: "BUILD_BROKEN_DUP_COPY_HEADERS",
64 behavior: DefaultDeprecated,
65 warnings: []string{"Duplicate header copy:"},
66 },
67 {
68 name: "BUILD_BROKEN_DUP_RULES",
69 behavior: DefaultFalse,
70 warnings: []string{"overriding commands for target"},
71 },
72 {
73 name: "BUILD_BROKEN_ANDROIDMK_EXPORTS",
74 behavior: DefaultFalse,
75 warnings: []string{"export_keyword"},
76 },
77 {
78 name: "BUILD_BROKEN_PHONY_TARGETS",
79 behavior: DefaultFalse,
80 warnings: []string{
81 "depends on PHONY target",
82 "looks like a real file",
83 "writing to readonly directory",
84 },
85 },
86 {
87 name: "BUILD_BROKEN_ENG_DEBUG_TAGS",
88 behavior: DefaultTrue,
89 warnings: []string{
90 "Changes.md#LOCAL_MODULE_TAGS",
91 },
92 },
93}
94
95type ProductBranch struct {
96 Branch string
97 Name string
98}
99
100type ProductLog struct {
101 ProductBranch
102 Log
103 Device string
104}
105
106type Log struct {
107 BuildBroken []*bool
108 HasBroken []bool
109}
110
111func Merge(l, l2 Log) Log {
112 if len(l.BuildBroken) == 0 {
113 l.BuildBroken = make([]*bool, len(buildBrokenSettings))
114 }
115 if len(l.HasBroken) == 0 {
116 l.HasBroken = make([]bool, len(buildBrokenSettings))
117 }
118
119 if len(l.BuildBroken) != len(l2.BuildBroken) || len(l.HasBroken) != len(l2.HasBroken) {
120 panic("mis-matched logs")
121 }
122
123 for i, v := range l.BuildBroken {
124 if v == nil {
125 l.BuildBroken[i] = l2.BuildBroken[i]
126 }
127 }
128 for i := range l.HasBroken {
129 l.HasBroken[i] = l.HasBroken[i] || l2.HasBroken[i]
130 }
131
132 return l
133}
134
135func PrintResults(products []ProductLog) {
136 devices := map[string]Log{}
137 deviceNames := []string{}
138
139 for _, product := range products {
140 device := product.Device
141 if _, ok := devices[device]; !ok {
142 deviceNames = append(deviceNames, device)
143 }
144 devices[device] = Merge(devices[device], product.Log)
145 }
146
147 sort.Strings(deviceNames)
148
149 for i, setting := range buildBrokenSettings {
150 printed := false
151
152 for _, device := range deviceNames {
153 log := devices[device]
154
155 if setting.behavior == DefaultTrue {
156 if log.BuildBroken[i] == nil || *log.BuildBroken[i] == false {
157 if log.HasBroken[i] {
158 printed = true
159 fmt.Printf(" %s needs to set %s := true\n", device, setting.name)
160 }
161 } else if !log.HasBroken[i] {
162 printed = true
163 fmt.Printf(" %s sets %s := true, but does not need it\n", device, setting.name)
164 }
165 } else if setting.behavior == DefaultFalse {
166 if log.BuildBroken[i] == nil {
167 // Nothing to be done
168 } else if *log.BuildBroken[i] == false {
169 printed = true
170 fmt.Printf(" %s sets %s := false, which is the default and can be removed\n", device, setting.name)
171 } else if !log.HasBroken[i] {
172 printed = true
173 fmt.Printf(" %s sets %s := true, but does not need it\n", device, setting.name)
174 }
175 } else if setting.behavior == DefaultDeprecated {
176 if log.BuildBroken[i] != nil {
177 printed = true
178 if log.HasBroken[i] {
179 fmt.Printf(" %s sets %s := %v, which is deprecated, but has failures\n", device, setting.name, *log.BuildBroken[i])
180 } else {
181 fmt.Printf(" %s sets %s := %v, which is deprecated and can be removed\n", device, setting.name, *log.BuildBroken[i])
182 }
183 }
184 }
185 }
186
187 if printed {
188 fmt.Println()
189 }
190 }
191}
192
193func ParseBranch(name string) []ProductLog {
194 products, err := filepath.Glob(filepath.Join(name, "*"))
195 if err != nil {
196 log.Fatal(err)
197 }
198
199 ret := []ProductLog{}
200 for _, product := range products {
201 product = filepath.Base(product)
202
203 ret = append(ret, ParseProduct(ProductBranch{Branch: name, Name: product}))
204 }
205 return ret
206}
207
208func ParseProduct(p ProductBranch) ProductLog {
209 soongLog, err := ioutil.ReadFile(filepath.Join(p.Branch, p.Name, "soong.log"))
210 if err != nil {
211 log.Fatal(err)
212 }
213
214 ret := ProductLog{
215 ProductBranch: p,
216 Log: Log{
217 BuildBroken: make([]*bool, len(buildBrokenSettings)),
218 HasBroken: make([]bool, len(buildBrokenSettings)),
219 },
220 }
221
222 lines := strings.Split(string(soongLog), "\n")
223 for _, line := range lines {
224 fields := strings.Split(line, " ")
225 if len(fields) != 5 {
226 continue
227 }
228
229 if fields[3] == "TARGET_DEVICE" {
230 ret.Device = fields[4]
231 }
232
233 if strings.HasPrefix(fields[3], "BUILD_BROKEN_") {
234 for i, setting := range buildBrokenSettings {
235 if setting.name == fields[3] {
236 ret.BuildBroken[i] = ParseBoolPtr(fields[4])
237 }
238 }
239 }
240 }
241
242 stdLog, err := ioutil.ReadFile(filepath.Join(p.Branch, p.Name, "std_full.log"))
243 if err != nil {
244 log.Fatal(err)
245 }
246 stdStr := string(stdLog)
247
248 for i, setting := range buildBrokenSettings {
249 for _, warning := range setting.warnings {
250 if strings.Contains(stdStr, warning) {
251 ret.HasBroken[i] = true
252 }
253 }
254 }
255
256 return ret
257}
258
259func ParseBoolPtr(str string) *bool {
260 var ret *bool
261 if str != "" {
262 b := str == "true"
263 ret = &b
264 }
265 return ret
266}