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