1package mkcompare
2
3import (
4	"bufio"
5	"fmt"
6	"github.com/google/go-cmp/cmp"
7	"io"
8	"regexp"
9	"sort"
10	"strings"
11)
12
13type MkVariable struct {
14	Name  string
15	Value string
16}
17
18type MkModule struct {
19	Type      string
20	Location  int
21	Extras    int
22	Variables map[string]string
23}
24
25type MkFile struct {
26	Path    string
27	Modules map[string]*MkModule
28}
29
30type myScanner struct {
31	*bufio.Scanner
32	lineNo int
33}
34
35func (s *myScanner) Scan() bool {
36	if s.Scanner.Scan() {
37		s.lineNo = s.lineNo + 1
38		return true
39	}
40	return false
41}
42
43var (
44	rexEmpty   = regexp.MustCompile("^ *$")
45	rexHeader  = regexp.MustCompile("^include +\\Q$(CLEAR_VARS)\\E *(# *(.*))?")
46	rexAssign  = regexp.MustCompile("^ *(.*) ([:+])= *(.*)$")
47	rexFooter  = regexp.MustCompile("^-?include *(.*)$")
48	rexIgnore1 = regexp.MustCompile("\\$\\(call dist-for-goals")
49	rexIgnore2 = regexp.MustCompile("\\$\\(LOCAL_INSTALLED_MODULE\\)")
50)
51
52const (
53	rexPairsHeader = 6
54	rexPairsAssign = 8
55	rexPairsFooter = 4
56)
57
58func (mk *MkFile) handleModule(scanner *myScanner, moduleType string) (*MkModule, error) {
59	mod := MkModule{Location: scanner.lineNo, Type: moduleType, Variables: make(map[string]string)}
60	includePath := ""
61	for scanner.Scan() {
62		line := scanner.Text()
63		if rexEmpty.MatchString(line) {
64			break
65		}
66		if m := rexAssign.FindStringSubmatchIndex(line); len(m) == rexPairsAssign {
67			v := line[m[2]:m[3]]
68			if line[m[4]:m[5]] == "+" {
69				mod.Variables[v] = mod.Variables[v] + line[m[6]:m[7]]
70			} else {
71				mod.Variables[v] = line[m[6]:m[7]]
72			}
73		} else if m := rexFooter.FindStringSubmatchIndex(line); len(m) == rexPairsFooter {
74			if includePath != "" {
75				return nil, fmt.Errorf("%d: second include for module", scanner.lineNo)
76			}
77			includePath = strings.TrimSpace(line[m[2]:m[3]])
78			if mod.Type == "" {
79				mod.Type = includePath
80			}
81		} else if mod.Type != "" {
82			mod.Extras = mod.Extras + 1
83			continue
84		} else if rexIgnore1.MatchString(line) {
85			continue
86		} else if rexIgnore2.MatchString(line) {
87			continue
88		} else {
89			return nil, fmt.Errorf("%d: unexpected line:\n%s", scanner.lineNo, line)
90		}
91	}
92	return &mod, scanner.Err()
93}
94
95func (mk *MkFile) ModulesByType(names []string) (sortedKeys []string, byType map[string][]string) {
96	byType = make(map[string][]string)
97	for _, name := range names {
98		mod, ok := mk.Modules[name]
99		if !ok {
100			break
101		}
102		mt := mod.Type
103		v, ok := byType[mt]
104		if !ok {
105			sortedKeys = append(sortedKeys, mt)
106		}
107		byType[mt] = append(v, name)
108	}
109	sort.Strings(sortedKeys)
110	return
111}
112
113func (mk *MkFile) moduleKey(mod *MkModule) (string, error) {
114	// Synthesize unique module name.
115	name := mod.Variables["LOCAL_MODULE"]
116	if name == "" {
117		return "", fmt.Errorf("%d: the module above lacks LOCAL_MODULE assignment", mod.Location)
118	}
119	var buf strings.Builder
120	writebuf := func(chunks ...string) {
121		for _, s := range chunks {
122			buf.WriteString(s)
123		}
124	}
125
126	writebuf(name, "|class:", mod.Variables["LOCAL_MODULE_CLASS"])
127	if mod.Variables["LOCAL_IS_HOST_MODULE"] == "true" {
128		if v, ok := mod.Variables["LOCAL_MODULE_HOST_ARCH"]; ok {
129			writebuf("|host_arch:", v)
130		}
131		if v, ok := mod.Variables["LOCAL_MODULE_HOST_CROSS_ARCH"]; ok {
132			writebuf("|cross_arch:", v)
133		}
134	} else {
135		if v, ok := mod.Variables["LOCAL_MODULE_TARGET_ARCH"]; ok {
136			writebuf("|target_arch:", v)
137		} else {
138			writebuf("|target_arch:*")
139		}
140	}
141	return buf.String(), nil
142}
143
144// ParseMkFile parses Android-TARGET.mk file generated by Android build
145func ParseMkFile(source io.Reader) (*MkFile, error) {
146	scanner := &myScanner{bufio.NewScanner(source), 0}
147	buffer := make([]byte, 1000000000)
148	scanner.Scanner.Buffer(buffer, len(buffer))
149	mkFile := &MkFile{Modules: make(map[string]*MkModule)}
150
151	for scanner.Scan() {
152		line := scanner.Text()
153		m := rexHeader.FindStringSubmatchIndex(line)
154		if len(m) != rexPairsHeader {
155			continue
156		}
157		moduleType := ""
158		if m[4] >= 0 {
159			moduleType = line[m[4]:m[5]]
160		}
161		mod, err := mkFile.handleModule(scanner, moduleType)
162		if err != nil {
163			return mkFile, err
164		}
165		name, err := mkFile.moduleKey(mod)
166		if err != nil {
167			return mkFile, err
168		}
169		if old, found := mkFile.Modules[name]; found {
170			return mkFile, fmt.Errorf(":%d: module %s already found, diff: %s", old.Location, name, cmp.Diff(old, mod))
171		}
172		mkFile.Modules[name] = mod
173	}
174	return mkFile, scanner.Err()
175}
176