package mkcompare import ( "fmt" "io" "regexp" "sort" "strings" ) // Classify takes two maps with string keys and return the lists of left-only, common, and right-only keys func Classify[V interface{}](mLeft map[string]V, mRight map[string]V, varFilter func(_ string) bool) (left []string, common []string, right []string) { for k := range mLeft { if !varFilter(k) { break } if _, ok := mRight[k]; ok { common = append(common, k) } else { left = append(left, k) } } for k := range mRight { if !varFilter(k) { break } if _, ok := mLeft[k]; !ok { right = append(right, k) } } return left, common, right } var normalizer = map[string]func(ref, our string) (string, string){ "LOCAL_SOONG_INSTALL_PAIRS": normalizeInstallPairs, "LOCAL_COMPATIBILITY_SUPPORT_FILES": normalizeInstallPairs, "LOCAL_PREBUILT_MODULE_FILE": normalizePrebuiltModuleFile, "LOCAL_SOONG_CLASSES_JAR": normalizePrebuiltModuleFile, "LOCAL_SOONG_HEADER_JAR": normalizePrebuiltModuleFile, } func normalizePrebuiltModuleFile(ref string, our string) (string, string) { return strings.ReplaceAll(ref, "/bazelCombined/", "/combined/"), strings.ReplaceAll(our, "/bazelCombined/", "/combined/") } var rexRemoveInstallSource = regexp.MustCompile("([^ ]+:)") func normalizeInstallPairs(ref string, our string) (string, string) { return rexRemoveInstallSource.ReplaceAllString(ref, ""), rexRemoveInstallSource.ReplaceAllString(our, "") } type MkVarDiff struct { Name string MissingItems []string `json:",omitempty"` ExtraItems []string `json:",omitempty"` } // MkModuleDiff holds module difference between reference and our mkfile. type MkModuleDiff struct { Ref *MkModule `json:"-"` Our *MkModule `json:"-"` MissingVars []string `json:",omitempty"` ExtraVars []string `json:",omitempty"` DiffVars []MkVarDiff `json:",omitempty"` TypeDiffers bool `json:",omitempty"` ExtrasDiffer bool `json:",omitempty"` } // Empty returns true if there is no difference func (d *MkModuleDiff) Empty() bool { return !d.TypeDiffers && !d.ExtrasDiffer && len(d.MissingVars) == 0 && len(d.ExtraVars) == 0 && len(d.DiffVars) == 0 } // Print prints the difference func (d *MkModuleDiff) Print(sink io.Writer, name string) { if d.Empty() { return } fmt.Fprintf(sink, "%s (ref line %d, our line %d):\n", name, d.Ref.Location, d.Our.Location) if d.TypeDiffers { fmt.Fprintf(sink, " type %s <-> %s\n", d.Ref.Type, d.Our.Type) } if !d.ExtrasDiffer { fmt.Fprintf(sink, " extras %d <-> %d\n", d.Ref.Extras, d.Our.Extras) } if len(d.MissingVars)+len(d.DiffVars) > 0 { fmt.Fprintf(sink, " variables:\n") if len(d.MissingVars) > 0 { fmt.Fprintf(sink, " -%v\n", d.MissingVars) } if len(d.ExtraVars) > 0 { fmt.Fprintf(sink, " +%v\n", d.ExtraVars) } } for _, vdiff := range d.DiffVars { fmt.Printf(" %s value:\n", vdiff.Name) if len(vdiff.MissingItems) > 0 { fmt.Printf(" -%v\n", vdiff.MissingItems) } if len(vdiff.ExtraItems) > 0 { fmt.Printf(" +%v\n", vdiff.ExtraItems) } } } // Compare returns the difference for a module. Only the variables filtered by the given // function are considered. func Compare(refMod *MkModule, ourMod *MkModule, varFilter func(string) bool) MkModuleDiff { d := MkModuleDiff{ Ref: refMod, Our: ourMod, TypeDiffers: refMod.Type != ourMod.Type, ExtrasDiffer: refMod.Extras != ourMod.Extras, } var common []string d.MissingVars, common, d.ExtraVars = Classify(d.Ref.Variables, d.Our.Variables, varFilter) if len(common) > 0 { for _, v := range common { doSort := true // TODO(asmundak): find if for some variables the value should not be sorted refValue := d.Ref.Variables[v] ourValue := d.Our.Variables[v] if f, ok := normalizer[v]; ok { refValue, ourValue = f(refValue, ourValue) } missingItems, extraItems := compareVariableValues(refValue, ourValue, doSort) if len(missingItems)+len(extraItems) > 0 { d.DiffVars = append(d.DiffVars, MkVarDiff{ Name: v, MissingItems: missingItems, ExtraItems: extraItems, }) } } } return d } func compareVariableValues(ref string, our string, sortItems bool) ([]string, []string) { refTokens := strings.Split(ref, " ") ourTokens := strings.Split(our, " ") if sortItems { sort.Strings(refTokens) sort.Strings(ourTokens) } var missing []string var extra []string refStream := &tokenStream{refTokens, 0} ourStream := &tokenStream{ourTokens, 0} refToken := refStream.next() ourToken := ourStream.next() compare := 0 for refToken != tsEOF || ourToken != tsEOF { if refToken == tsEOF { compare = 1 } else if ourToken == tsEOF { compare = -1 } else { compare = 0 if refToken <= ourToken { compare = -1 } if refToken >= ourToken { compare = compare + 1 } } switch compare { case -1: missing = append(missing, refToken) refToken = refStream.next() case 0: refToken = refStream.next() ourToken = ourStream.next() case 1: extra = append(extra, ourToken) ourToken = ourStream.next() } } return missing, extra } // Auxiliary stuff used to find the difference const tsEOF = " " type tokenStream struct { tokens []string current int } func (ts *tokenStream) next() string { if ts.current >= len(ts.tokens) { return tsEOF } ret := ts.tokens[ts.current] ts.current = ts.current + 1 return ret }