package mkcompare import ( "bufio" "fmt" "github.com/google/go-cmp/cmp" "io" "regexp" "sort" "strings" ) type MkVariable struct { Name string Value string } type MkModule struct { Type string Location int Extras int Variables map[string]string } type MkFile struct { Path string Modules map[string]*MkModule } type myScanner struct { *bufio.Scanner lineNo int } func (s *myScanner) Scan() bool { if s.Scanner.Scan() { s.lineNo = s.lineNo + 1 return true } return false } var ( rexEmpty = regexp.MustCompile("^ *$") rexHeader = regexp.MustCompile("^include +\\Q$(CLEAR_VARS)\\E *(# *(.*))?") rexAssign = regexp.MustCompile("^ *(.*) ([:+])= *(.*)$") rexFooter = regexp.MustCompile("^-?include *(.*)$") rexIgnore1 = regexp.MustCompile("\\$\\(call dist-for-goals") rexIgnore2 = regexp.MustCompile("\\$\\(LOCAL_INSTALLED_MODULE\\)") ) const ( rexPairsHeader = 6 rexPairsAssign = 8 rexPairsFooter = 4 ) func (mk *MkFile) handleModule(scanner *myScanner, moduleType string) (*MkModule, error) { mod := MkModule{Location: scanner.lineNo, Type: moduleType, Variables: make(map[string]string)} includePath := "" for scanner.Scan() { line := scanner.Text() if rexEmpty.MatchString(line) { break } if m := rexAssign.FindStringSubmatchIndex(line); len(m) == rexPairsAssign { v := line[m[2]:m[3]] if line[m[4]:m[5]] == "+" { mod.Variables[v] = mod.Variables[v] + line[m[6]:m[7]] } else { mod.Variables[v] = line[m[6]:m[7]] } } else if m := rexFooter.FindStringSubmatchIndex(line); len(m) == rexPairsFooter { if includePath != "" { return nil, fmt.Errorf("%d: second include for module", scanner.lineNo) } includePath = strings.TrimSpace(line[m[2]:m[3]]) if mod.Type == "" { mod.Type = includePath } } else if mod.Type != "" { mod.Extras = mod.Extras + 1 continue } else if rexIgnore1.MatchString(line) { continue } else if rexIgnore2.MatchString(line) { continue } else { return nil, fmt.Errorf("%d: unexpected line:\n%s", scanner.lineNo, line) } } return &mod, scanner.Err() } func (mk *MkFile) ModulesByType(names []string) (sortedKeys []string, byType map[string][]string) { byType = make(map[string][]string) for _, name := range names { mod, ok := mk.Modules[name] if !ok { break } mt := mod.Type v, ok := byType[mt] if !ok { sortedKeys = append(sortedKeys, mt) } byType[mt] = append(v, name) } sort.Strings(sortedKeys) return } func (mk *MkFile) moduleKey(mod *MkModule) (string, error) { // Synthesize unique module name. name := mod.Variables["LOCAL_MODULE"] if name == "" { return "", fmt.Errorf("%d: the module above lacks LOCAL_MODULE assignment", mod.Location) } var buf strings.Builder writebuf := func(chunks ...string) { for _, s := range chunks { buf.WriteString(s) } } writebuf(name, "|class:", mod.Variables["LOCAL_MODULE_CLASS"]) if mod.Variables["LOCAL_IS_HOST_MODULE"] == "true" { if v, ok := mod.Variables["LOCAL_MODULE_HOST_ARCH"]; ok { writebuf("|host_arch:", v) } if v, ok := mod.Variables["LOCAL_MODULE_HOST_CROSS_ARCH"]; ok { writebuf("|cross_arch:", v) } } else { if v, ok := mod.Variables["LOCAL_MODULE_TARGET_ARCH"]; ok { writebuf("|target_arch:", v) } else { writebuf("|target_arch:*") } } return buf.String(), nil } // ParseMkFile parses Android-TARGET.mk file generated by Android build func ParseMkFile(source io.Reader) (*MkFile, error) { scanner := &myScanner{bufio.NewScanner(source), 0} buffer := make([]byte, 1000000000) scanner.Scanner.Buffer(buffer, len(buffer)) mkFile := &MkFile{Modules: make(map[string]*MkModule)} for scanner.Scan() { line := scanner.Text() m := rexHeader.FindStringSubmatchIndex(line) if len(m) != rexPairsHeader { continue } moduleType := "" if m[4] >= 0 { moduleType = line[m[4]:m[5]] } mod, err := mkFile.handleModule(scanner, moduleType) if err != nil { return mkFile, err } name, err := mkFile.moduleKey(mod) if err != nil { return mkFile, err } if old, found := mkFile.Modules[name]; found { return mkFile, fmt.Errorf(":%d: module %s already found, diff: %s", old.Location, name, cmp.Diff(old, mod)) } mkFile.Modules[name] = mod } return mkFile, scanner.Err() }