diff options
author | Colin Cross | 2018-03-22 14:43:16 -0500 |
---|---|---|
committer | Colin Cross | 2018-04-16 12:32:16 -0500 |
commit | 9c55d237f6b72896209344aee18a1702c2f9ac3e (patch) | |
tree | 9cc07e0ff8e5305611ad267d56bee111834ab1cf /bpfix | |
parent | adee968a4bace28bd26253bc436043eb513f6117 (diff) | |
download | platform-build-soong-9c55d237f6b72896209344aee18a1702c2f9ac3e.tar.gz platform-build-soong-9c55d237f6b72896209344aee18a1702c2f9ac3e.tar.xz platform-build-soong-9c55d237f6b72896209344aee18a1702c2f9ac3e.zip |
Merge matching properties in bpfix
androidmk will start to generate multiple static_libs: properties
in a single module, add a pass in bpfix to fix them up into a
single property.
Test: bpfix_test.go
Change-Id: I30955b6efbb767c02ba77f2f18d44951ef094bad
Diffstat (limited to 'bpfix')
-rw-r--r-- | bpfix/bpfix/bpfix.go | 133 | ||||
-rw-r--r-- | bpfix/bpfix/bpfix_test.go | 128 |
2 files changed, 261 insertions, 0 deletions
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go index 4300e81b..4ab42a4e 100644 --- a/bpfix/bpfix/bpfix.go +++ b/bpfix/bpfix/bpfix.go | |||
@@ -26,11 +26,27 @@ import ( | |||
26 | "github.com/google/blueprint/parser" | 26 | "github.com/google/blueprint/parser" |
27 | ) | 27 | ) |
28 | 28 | ||
29 | // Reformat takes a blueprint file as a string and returns a formatted version | ||
30 | func Reformat(input string) (string, error) { | ||
31 | tree, err := parse("<string>", bytes.NewBufferString(input)) | ||
32 | if err != nil { | ||
33 | return "", err | ||
34 | } | ||
35 | |||
36 | res, err := parser.Print(tree) | ||
37 | if err != nil { | ||
38 | return "", err | ||
39 | } | ||
40 | |||
41 | return string(res), nil | ||
42 | } | ||
43 | |||
29 | // A FixRequest specifies the details of which fixes to apply to an individual file | 44 | // A FixRequest specifies the details of which fixes to apply to an individual file |
30 | // A FixRequest doesn't specify whether to do a dry run or where to write the results; that's in cmd/bpfix.go | 45 | // A FixRequest doesn't specify whether to do a dry run or where to write the results; that's in cmd/bpfix.go |
31 | type FixRequest struct { | 46 | type FixRequest struct { |
32 | simplifyKnownRedundantVariables bool | 47 | simplifyKnownRedundantVariables bool |
33 | rewriteIncorrectAndroidmkPrebuilts bool | 48 | rewriteIncorrectAndroidmkPrebuilts bool |
49 | mergeMatchingModuleProperties bool | ||
34 | } | 50 | } |
35 | 51 | ||
36 | func NewFixRequest() FixRequest { | 52 | func NewFixRequest() FixRequest { |
@@ -41,6 +57,7 @@ func (r FixRequest) AddAll() (result FixRequest) { | |||
41 | result = r | 57 | result = r |
42 | result.simplifyKnownRedundantVariables = true | 58 | result.simplifyKnownRedundantVariables = true |
43 | result.rewriteIncorrectAndroidmkPrebuilts = true | 59 | result.rewriteIncorrectAndroidmkPrebuilts = true |
60 | result.mergeMatchingModuleProperties = true | ||
44 | return result | 61 | return result |
45 | } | 62 | } |
46 | 63 | ||
@@ -136,6 +153,13 @@ func (f *Fixer) fixTreeOnce(config FixRequest) error { | |||
136 | return err | 153 | return err |
137 | } | 154 | } |
138 | } | 155 | } |
156 | |||
157 | if config.mergeMatchingModuleProperties { | ||
158 | err := f.mergeMatchingModuleProperties() | ||
159 | if err != nil { | ||
160 | return err | ||
161 | } | ||
162 | } | ||
139 | return nil | 163 | return nil |
140 | } | 164 | } |
141 | 165 | ||
@@ -179,6 +203,115 @@ func (f *Fixer) rewriteIncorrectAndroidmkPrebuilts() error { | |||
179 | return nil | 203 | return nil |
180 | } | 204 | } |
181 | 205 | ||
206 | func (f *Fixer) mergeMatchingModuleProperties() error { | ||
207 | // Make sure all the offsets are accurate | ||
208 | buf, err := f.reparse() | ||
209 | if err != nil { | ||
210 | return err | ||
211 | } | ||
212 | |||
213 | var patchlist parser.PatchList | ||
214 | for _, def := range f.tree.Defs { | ||
215 | mod, ok := def.(*parser.Module) | ||
216 | if !ok { | ||
217 | continue | ||
218 | } | ||
219 | |||
220 | err := mergeMatchingProperties(&mod.Properties, buf, &patchlist) | ||
221 | if err != nil { | ||
222 | return err | ||
223 | } | ||
224 | } | ||
225 | |||
226 | newBuf := new(bytes.Buffer) | ||
227 | err = patchlist.Apply(bytes.NewReader(buf), newBuf) | ||
228 | if err != nil { | ||
229 | return err | ||
230 | } | ||
231 | |||
232 | newTree, err := parse(f.tree.Name, newBuf) | ||
233 | if err != nil { | ||
234 | return err | ||
235 | } | ||
236 | |||
237 | f.tree = newTree | ||
238 | |||
239 | return nil | ||
240 | } | ||
241 | |||
242 | func mergeMatchingProperties(properties *[]*parser.Property, buf []byte, patchlist *parser.PatchList) error { | ||
243 | seen := make(map[string]*parser.Property) | ||
244 | for i := 0; i < len(*properties); i++ { | ||
245 | property := (*properties)[i] | ||
246 | if prev, exists := seen[property.Name]; exists { | ||
247 | err := mergeProperties(prev, property, buf, patchlist) | ||
248 | if err != nil { | ||
249 | return err | ||
250 | } | ||
251 | *properties = append((*properties)[:i], (*properties)[i+1:]...) | ||
252 | } else { | ||
253 | seen[property.Name] = property | ||
254 | if mapProperty, ok := property.Value.(*parser.Map); ok { | ||
255 | err := mergeMatchingProperties(&mapProperty.Properties, buf, patchlist) | ||
256 | if err != nil { | ||
257 | return err | ||
258 | } | ||
259 | } | ||
260 | } | ||
261 | } | ||
262 | return nil | ||
263 | } | ||
264 | |||
265 | func mergeProperties(a, b *parser.Property, buf []byte, patchlist *parser.PatchList) error { | ||
266 | if a.Value.Type() != b.Value.Type() { | ||
267 | return fmt.Errorf("type mismatch when merging properties %q: %s and %s", a.Name, a.Value.Type(), b.Value.Type()) | ||
268 | } | ||
269 | |||
270 | switch a.Value.Type() { | ||
271 | case parser.StringType: | ||
272 | return fmt.Errorf("conflicting definitions of string property %q", a.Name) | ||
273 | case parser.ListType: | ||
274 | return mergeListProperties(a, b, buf, patchlist) | ||
275 | } | ||
276 | |||
277 | return nil | ||
278 | } | ||
279 | |||
280 | func mergeListProperties(a, b *parser.Property, buf []byte, patchlist *parser.PatchList) error { | ||
281 | aval, oka := a.Value.(*parser.List) | ||
282 | bval, okb := b.Value.(*parser.List) | ||
283 | if !oka || !okb { | ||
284 | // Merging expressions not supported yet | ||
285 | return nil | ||
286 | } | ||
287 | |||
288 | s := string(buf[bval.LBracePos.Offset+1 : bval.RBracePos.Offset]) | ||
289 | if bval.LBracePos.Line != bval.RBracePos.Line { | ||
290 | if s[0] != '\n' { | ||
291 | panic("expected \n") | ||
292 | } | ||
293 | // If B is a multi line list, skip the first "\n" in case A already has a trailing "\n" | ||
294 | s = s[1:] | ||
295 | } | ||
296 | if aval.LBracePos.Line == aval.RBracePos.Line { | ||
297 | // A is a single line list with no trailing comma | ||
298 | if len(aval.Values) > 0 { | ||
299 | s = "," + s | ||
300 | } | ||
301 | } | ||
302 | |||
303 | err := patchlist.Add(aval.RBracePos.Offset, aval.RBracePos.Offset, s) | ||
304 | if err != nil { | ||
305 | return err | ||
306 | } | ||
307 | err = patchlist.Add(b.NamePos.Offset, b.End().Offset+2, "") | ||
308 | if err != nil { | ||
309 | return err | ||
310 | } | ||
311 | |||
312 | return nil | ||
313 | } | ||
314 | |||
182 | // removes from <items> every item present in <removals> | 315 | // removes from <items> every item present in <removals> |
183 | func filterExpressionList(items *parser.List, removals *parser.List) { | 316 | func filterExpressionList(items *parser.List, removals *parser.List) { |
184 | writeIndex := 0 | 317 | writeIndex := 0 |
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go index 1e1e6933..51708eb6 100644 --- a/bpfix/bpfix/bpfix_test.go +++ b/bpfix/bpfix/bpfix_test.go | |||
@@ -17,6 +17,7 @@ | |||
17 | package bpfix | 17 | package bpfix |
18 | 18 | ||
19 | import ( | 19 | import ( |
20 | "bytes" | ||
20 | "fmt" | 21 | "fmt" |
21 | "strings" | 22 | "strings" |
22 | "testing" | 23 | "testing" |
@@ -115,3 +116,130 @@ func TestSimplifyKnownVariablesDuplicatingEachOther(t *testing.T) { | |||
115 | implFilterListTest(t, []string{}, []string{"include"}, []string{}) | 116 | implFilterListTest(t, []string{}, []string{"include"}, []string{}) |
116 | implFilterListTest(t, []string{}, []string{}, []string{}) | 117 | implFilterListTest(t, []string{}, []string{}, []string{}) |
117 | } | 118 | } |
119 | |||
120 | func TestMergeMatchingProperties(t *testing.T) { | ||
121 | tests := []struct { | ||
122 | name string | ||
123 | in string | ||
124 | out string | ||
125 | }{ | ||
126 | { | ||
127 | name: "empty", | ||
128 | in: ` | ||
129 | java_library { | ||
130 | name: "foo", | ||
131 | static_libs: [], | ||
132 | static_libs: [], | ||
133 | } | ||
134 | `, | ||
135 | out: ` | ||
136 | java_library { | ||
137 | name: "foo", | ||
138 | static_libs: [], | ||
139 | } | ||
140 | `, | ||
141 | }, | ||
142 | { | ||
143 | name: "single line into multiline", | ||
144 | in: ` | ||
145 | java_library { | ||
146 | name: "foo", | ||
147 | static_libs: [ | ||
148 | "a", | ||
149 | "b", | ||
150 | ], | ||
151 | //c1 | ||
152 | static_libs: ["c" /*c2*/], | ||
153 | } | ||
154 | `, | ||
155 | out: ` | ||
156 | java_library { | ||
157 | name: "foo", | ||
158 | static_libs: [ | ||
159 | "a", | ||
160 | "b", | ||
161 | "c", /*c2*/ | ||
162 | ], | ||
163 | //c1 | ||
164 | } | ||
165 | `, | ||
166 | }, | ||
167 | { | ||
168 | name: "multiline into multiline", | ||
169 | in: ` | ||
170 | java_library { | ||
171 | name: "foo", | ||
172 | static_libs: [ | ||
173 | "a", | ||
174 | "b", | ||
175 | ], | ||
176 | //c1 | ||
177 | static_libs: [ | ||
178 | //c2 | ||
179 | "c", //c3 | ||
180 | "d", | ||
181 | ], | ||
182 | } | ||
183 | `, | ||
184 | out: ` | ||
185 | java_library { | ||
186 | name: "foo", | ||
187 | static_libs: [ | ||
188 | "a", | ||
189 | "b", | ||
190 | //c2 | ||
191 | "c", //c3 | ||
192 | "d", | ||
193 | ], | ||
194 | //c1 | ||
195 | } | ||
196 | `, | ||
197 | }, | ||
198 | } | ||
199 | |||
200 | for _, test := range tests { | ||
201 | t.Run(test.name, func(t *testing.T) { | ||
202 | expected, err := Reformat(test.out) | ||
203 | if err != nil { | ||
204 | t.Error(err) | ||
205 | } | ||
206 | |||
207 | in, err := Reformat(test.in) | ||
208 | if err != nil { | ||
209 | t.Error(err) | ||
210 | } | ||
211 | |||
212 | tree, errs := parser.Parse("<testcase>", bytes.NewBufferString(in), parser.NewScope(nil)) | ||
213 | if errs != nil { | ||
214 | t.Fatal(errs) | ||
215 | } | ||
216 | |||
217 | fixer := NewFixer(tree) | ||
218 | |||
219 | got := "" | ||
220 | prev := "foo" | ||
221 | passes := 0 | ||
222 | for got != prev && passes < 10 { | ||
223 | err := fixer.mergeMatchingModuleProperties() | ||
224 | if err != nil { | ||
225 | t.Fatal(err) | ||
226 | } | ||
227 | |||
228 | out, err := parser.Print(fixer.tree) | ||
229 | if err != nil { | ||
230 | t.Fatal(err) | ||
231 | } | ||
232 | |||
233 | prev = got | ||
234 | got = string(out) | ||
235 | passes++ | ||
236 | } | ||
237 | |||
238 | if got != expected { | ||
239 | t.Errorf("failed testcase '%s'\ninput:\n%s\n\nexpected:\n%s\ngot:\n%s\n", | ||
240 | test.name, in, expected, got) | ||
241 | } | ||
242 | |||
243 | }) | ||
244 | } | ||
245 | } | ||