1package mkcompare 2 3import ( 4 "fmt" 5 "io" 6 "regexp" 7 "sort" 8 "strings" 9) 10 11// Classify takes two maps with string keys and return the lists of left-only, common, and right-only keys 12func Classify[V interface{}](mLeft map[string]V, mRight map[string]V, varFilter func(_ string) bool) (left []string, common []string, right []string) { 13 for k := range mLeft { 14 if !varFilter(k) { 15 break 16 } 17 if _, ok := mRight[k]; ok { 18 common = append(common, k) 19 } else { 20 left = append(left, k) 21 } 22 } 23 for k := range mRight { 24 if !varFilter(k) { 25 break 26 } 27 if _, ok := mLeft[k]; !ok { 28 right = append(right, k) 29 } 30 } 31 32 return left, common, right 33} 34 35var normalizer = map[string]func(ref, our string) (string, string){ 36 "LOCAL_SOONG_INSTALL_PAIRS": normalizeInstallPairs, 37 "LOCAL_COMPATIBILITY_SUPPORT_FILES": normalizeInstallPairs, 38 "LOCAL_PREBUILT_MODULE_FILE": normalizePrebuiltModuleFile, 39 "LOCAL_SOONG_CLASSES_JAR": normalizePrebuiltModuleFile, 40 "LOCAL_SOONG_HEADER_JAR": normalizePrebuiltModuleFile, 41} 42 43func normalizePrebuiltModuleFile(ref string, our string) (string, string) { 44 return strings.ReplaceAll(ref, "/bazelCombined/", "/combined/"), strings.ReplaceAll(our, "/bazelCombined/", "/combined/") 45} 46 47var rexRemoveInstallSource = regexp.MustCompile("([^ ]+:)") 48 49func normalizeInstallPairs(ref string, our string) (string, string) { 50 return rexRemoveInstallSource.ReplaceAllString(ref, ""), rexRemoveInstallSource.ReplaceAllString(our, "") 51} 52 53type MkVarDiff struct { 54 Name string 55 MissingItems []string `json:",omitempty"` 56 ExtraItems []string `json:",omitempty"` 57} 58 59// MkModuleDiff holds module difference between reference and our mkfile. 60type MkModuleDiff struct { 61 Ref *MkModule `json:"-"` 62 Our *MkModule `json:"-"` 63 MissingVars []string `json:",omitempty"` 64 ExtraVars []string `json:",omitempty"` 65 DiffVars []MkVarDiff `json:",omitempty"` 66 TypeDiffers bool `json:",omitempty"` 67 ExtrasDiffer bool `json:",omitempty"` 68} 69 70// Empty returns true if there is no difference 71func (d *MkModuleDiff) Empty() bool { 72 return !d.TypeDiffers && !d.ExtrasDiffer && len(d.MissingVars) == 0 && len(d.ExtraVars) == 0 && len(d.DiffVars) == 0 73} 74 75// Print prints the difference 76func (d *MkModuleDiff) Print(sink io.Writer, name string) { 77 if d.Empty() { 78 return 79 } 80 fmt.Fprintf(sink, "%s (ref line %d, our line %d):\n", name, d.Ref.Location, d.Our.Location) 81 if d.TypeDiffers { 82 fmt.Fprintf(sink, " type %s <-> %s\n", d.Ref.Type, d.Our.Type) 83 } 84 85 if !d.ExtrasDiffer { 86 fmt.Fprintf(sink, " extras %d <-> %d\n", d.Ref.Extras, d.Our.Extras) 87 } 88 89 if len(d.MissingVars)+len(d.DiffVars) > 0 { 90 fmt.Fprintf(sink, " variables:\n") 91 if len(d.MissingVars) > 0 { 92 fmt.Fprintf(sink, " -%v\n", d.MissingVars) 93 } 94 if len(d.ExtraVars) > 0 { 95 fmt.Fprintf(sink, " +%v\n", d.ExtraVars) 96 } 97 } 98 for _, vdiff := range d.DiffVars { 99 fmt.Printf(" %s value:\n", vdiff.Name) 100 if len(vdiff.MissingItems) > 0 { 101 fmt.Printf(" -%v\n", vdiff.MissingItems) 102 } 103 if len(vdiff.ExtraItems) > 0 { 104 fmt.Printf(" +%v\n", vdiff.ExtraItems) 105 } 106 } 107} 108 109// Compare returns the difference for a module. Only the variables filtered by the given 110// function are considered. 111func Compare(refMod *MkModule, ourMod *MkModule, varFilter func(string) bool) MkModuleDiff { 112 d := MkModuleDiff{ 113 Ref: refMod, 114 Our: ourMod, 115 TypeDiffers: refMod.Type != ourMod.Type, 116 ExtrasDiffer: refMod.Extras != ourMod.Extras, 117 } 118 var common []string 119 d.MissingVars, common, d.ExtraVars = Classify(d.Ref.Variables, d.Our.Variables, varFilter) 120 121 if len(common) > 0 { 122 for _, v := range common { 123 doSort := true // TODO(asmundak): find if for some variables the value should not be sorted 124 refValue := d.Ref.Variables[v] 125 ourValue := d.Our.Variables[v] 126 if f, ok := normalizer[v]; ok { 127 refValue, ourValue = f(refValue, ourValue) 128 } 129 missingItems, extraItems := compareVariableValues(refValue, ourValue, doSort) 130 if len(missingItems)+len(extraItems) > 0 { 131 d.DiffVars = append(d.DiffVars, MkVarDiff{ 132 Name: v, 133 MissingItems: missingItems, 134 ExtraItems: extraItems, 135 }) 136 } 137 } 138 } 139 return d 140} 141 142func compareVariableValues(ref string, our string, sortItems bool) ([]string, []string) { 143 refTokens := strings.Split(ref, " ") 144 ourTokens := strings.Split(our, " ") 145 if sortItems { 146 sort.Strings(refTokens) 147 sort.Strings(ourTokens) 148 } 149 var missing []string 150 var extra []string 151 refStream := &tokenStream{refTokens, 0} 152 ourStream := &tokenStream{ourTokens, 0} 153 refToken := refStream.next() 154 ourToken := ourStream.next() 155 compare := 0 156 for refToken != tsEOF || ourToken != tsEOF { 157 if refToken == tsEOF { 158 compare = 1 159 } else if ourToken == tsEOF { 160 compare = -1 161 } else { 162 compare = 0 163 if refToken <= ourToken { 164 compare = -1 165 } 166 if refToken >= ourToken { 167 compare = compare + 1 168 } 169 } 170 switch compare { 171 case -1: 172 missing = append(missing, refToken) 173 refToken = refStream.next() 174 case 0: 175 refToken = refStream.next() 176 ourToken = ourStream.next() 177 case 1: 178 extra = append(extra, ourToken) 179 ourToken = ourStream.next() 180 } 181 } 182 return missing, extra 183} 184 185// Auxiliary stuff used to find the difference 186const tsEOF = " " 187 188type tokenStream struct { 189 tokens []string 190 current int 191} 192 193func (ts *tokenStream) next() string { 194 if ts.current >= len(ts.tokens) { 195 return tsEOF 196 } 197 ret := ts.tokens[ts.current] 198 ts.current = ts.current + 1 199 return ret 200} 201