1package main 2 3import ( 4 "android/bazel/mkcompare" 5 "bufio" 6 "encoding/json" 7 "flag" 8 "fmt" 9 "math" 10 "os" 11 "runtime" 12 "runtime/pprof" 13 "sort" 14 "strings" 15) 16 17var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") 18var memprofile = flag.String("memprofile", "", "write memory profile to `file`") 19var ignoredVariables = flag.String("ignore_variables", "", "comma-separated list of variables to ignore") 20var maxDiff = flag.Int("max", math.MaxInt, "stop after finding N different modules") 21var showPerModuleDiffs = flag.Bool("show_module_diffs", false, "show per-module differences") 22var showModulesPerType = flag.Bool("show_type_modules", false, "show modules for each differing type") 23var jsonOut = flag.Bool("json", false, "generate JSON output") 24var showSummary = flag.Bool("show_summary", true, "show summary") 25var ignoredVarSet map[string]bool 26 27func maybeQuit(err error) { 28 if err == nil { 29 return 30 } 31 32 fmt.Fprintln(os.Stderr, err) 33 os.Exit(1) 34} 35 36func parse(path string) *mkcompare.MkFile { 37 f, err := os.Open(path) 38 maybeQuit(err) 39 mkFile, err := mkcompare.ParseMkFile(bufio.NewReader(f)) 40 maybeQuit(err) 41 f.Close() 42 return mkFile 43} 44 45func processArgs() { 46 flag.Usage = func() { 47 fmt.Fprintln(os.Stderr, `usage: mkcompare <options> refMkFile mkFile`) 48 flag.PrintDefaults() 49 os.Exit(2) 50 } 51 flag.Parse() 52 if len(flag.Args()) != 2 { 53 flag.Usage() 54 } 55 if *jsonOut { 56 *showPerModuleDiffs = false 57 *showModulesPerType = false 58 *showSummary = false 59 } 60} 61 62func goParse(path string) chan *mkcompare.MkFile { 63 ch := make(chan *mkcompare.MkFile, 1) 64 go func() { ch <- parse(path) }() 65 return ch 66} 67 68func printVars(title string, modulesByVar map[string][]string, mkFile *mkcompare.MkFile) { 69 if len(modulesByVar) > 0 { 70 fmt.Println(title) 71 for varName, mods := range modulesByVar { 72 printModulesByType(fmt.Sprintf(" %s, by type:", varName), mods, mkFile) 73 } 74 } 75} 76 77func printModulesByType(title string, moduleNames []string, mkFile *mkcompare.MkFile) { 78 // Indent all lines by the title's indent 79 prefix := title 80 for i, c := range title { 81 if string(c) != " " { 82 prefix = title[0:i] 83 break 84 } 85 } 86 fmt.Println(title) 87 sortedTypes, byType := mkFile.ModulesByType(moduleNames) 88 for _, typ := range sortedTypes { 89 fmt.Printf("%s %s (%d modules)\n", prefix, typ, len(byType[typ])) 90 if !*showPerModuleDiffs { 91 continue 92 } 93 for _, m := range byType[typ] { 94 fmt.Println(prefix, " ", m) 95 } 96 } 97} 98 99type diffMod struct { 100 Name string 101 mkcompare.MkModuleDiff 102 RefLocation int 103 OurLocation int 104 Type string 105 ReferenceType string `json:",omitempty"` 106} 107 108type missingOrExtraMod struct { 109 Name string 110 Location int 111 Type string 112} 113 114type Diff struct { 115 RefPath string 116 OurPath string 117 ExtraModules []missingOrExtraMod `json:",omitempty"` 118 MissingModules []missingOrExtraMod `json:",omitempty"` 119 DiffModules []diffMod `json:",omitempty"` 120} 121 122func process(refMkFile, ourMkFile *mkcompare.MkFile) bool { 123 diff := Diff{RefPath: refMkFile.Path, OurPath: ourMkFile.Path} 124 missing, common, extra := 125 mkcompare.Classify(refMkFile.Modules, ourMkFile.Modules, func(_ string) bool { return true }) 126 127 sort.Strings(missing) 128 if len(missing) > 0 { 129 if *showSummary { 130 printModulesByType(fmt.Sprintf("%d missing modules, by type:", len(missing)), 131 missing, refMkFile) 132 } 133 if *jsonOut { 134 for _, name := range missing { 135 mod := refMkFile.Modules[name] 136 diff.MissingModules = append(diff.MissingModules, 137 missingOrExtraMod{name, mod.Location, mod.Type}) 138 } 139 } 140 } 141 142 sort.Strings(extra) 143 if len(extra) > 0 { 144 if *showSummary { 145 printModulesByType(fmt.Sprintf("%d extra modules, by type:", len(extra)), extra, ourMkFile) 146 } 147 if *jsonOut { 148 for _, name := range extra { 149 mod := ourMkFile.Modules[name] 150 diff.ExtraModules = append(diff.ExtraModules, 151 missingOrExtraMod{name, mod.Location, mod.Type}) 152 } 153 } 154 } 155 filesAreEqual := len(diff.MissingModules)+len(diff.ExtraModules) == 0 156 157 nDiff := 0 158 sort.Strings(common) 159 filterVars := func(name string) bool { 160 _, ok := ignoredVarSet[name] 161 return !ok 162 } 163 var missingVariables = make(map[string][]string) 164 var extraVariables = make(map[string][]string) 165 var diffVariables = make(map[string][]string) 166 for _, name := range common { 167 d := mkcompare.Compare(refMkFile.Modules[name], ourMkFile.Modules[name], filterVars) 168 if d.Empty() { 169 continue 170 } 171 filesAreEqual = false 172 var refType string 173 if d.Ref.Type != d.Our.Type { 174 refType = d.Ref.Type 175 } 176 if *jsonOut { 177 diff.DiffModules = append(diff.DiffModules, diffMod{ 178 MkModuleDiff: d, 179 Name: name, 180 RefLocation: d.Ref.Location, 181 OurLocation: d.Our.Location, 182 Type: d.Our.Type, 183 ReferenceType: refType, 184 }) 185 } 186 nDiff = nDiff + 1 187 if nDiff >= *maxDiff { 188 fmt.Printf("Only the first %d module diffs are processed\n", *maxDiff) 189 break 190 } 191 addToDiffList := func(d map[string][]string, items []string) { 192 if len(items) == 0 { 193 return 194 } 195 for _, v := range items { 196 d[v] = append(d[v], name) 197 } 198 } 199 addToDiffList(missingVariables, d.MissingVars) 200 addToDiffList(extraVariables, d.ExtraVars) 201 for _, dv := range d.DiffVars { 202 diffVariables[dv.Name] = append(diffVariables[dv.Name], name) 203 } 204 if *showPerModuleDiffs { 205 fmt.Println() 206 d.Print(os.Stdout, name) 207 } 208 } 209 if *showSummary { 210 printVars(fmt.Sprintf("\nMissing variables (%d):", len(missingVariables)), missingVariables, refMkFile) 211 printVars(fmt.Sprintf("\nExtra variables (%d):", len(extraVariables)), extraVariables, ourMkFile) 212 printVars(fmt.Sprintf("\nDiff variables: (%d)", len(diffVariables)), diffVariables, refMkFile) 213 } 214 if *jsonOut { 215 enc := json.NewEncoder(os.Stdout) 216 enc.SetIndent("", " ") 217 enc.Encode(diff) 218 } 219 return filesAreEqual 220} 221 222func main() { 223 processArgs() 224 if *cpuprofile != "" { 225 f, err := os.Create(*cpuprofile) 226 maybeQuit(err) 227 defer f.Close() // error handling omitted for example 228 maybeQuit(pprof.StartCPUProfile(f)) 229 defer pprof.StopCPUProfile() 230 } 231 chRef := goParse(flag.Arg(0)) 232 chNew := goParse(flag.Arg(1)) 233 if *ignoredVariables != "" { 234 ignoredVarSet = make(map[string]bool) 235 for _, v := range strings.Split(*ignoredVariables, ",") { 236 ignoredVarSet[v] = true 237 } 238 } 239 refMkFile, newMkFile := <-chRef, <-chNew 240 refMkFile.Path = flag.Arg(0) 241 newMkFile.Path = flag.Arg(1) 242 equal := process(refMkFile, newMkFile) 243 if *memprofile != "" { 244 f, err := os.Create(*memprofile) 245 maybeQuit(err) 246 defer f.Close() // error handling omitted for example 247 runtime.GC() // get up-to-date statistics 248 maybeQuit(pprof.WriteHeapProfile(f)) 249 } 250 if !equal { 251 os.Exit(2) 252 } 253} 254