diff options
author | Paul Eggleton <paul.eggleton@linux.intel.com> | 2017-04-07 09:52:10 +1200 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2017-04-10 23:00:32 +0100 |
commit | 5f7bf1f66d21155dfa5328aa57b4302cc64c132b (patch) | |
tree | ce0fd73f0c03de93a367ab124b0e6a42247f7850 /bitbake | |
parent | 5d8b89fc0b9a703243dcd4cce483c335effee78d (diff) | |
download | poky-5f7bf1f66d21155dfa5328aa57b4302cc64c132b.tar.gz |
bitbake: lib/bb/siggen: show word-diff for single-line values containing spaces
If a variable value has changed and either the new or old value contains
spaces, a word diff should be appropriate and may be a bit more readable.
Import the "simplediff" module and use it to show a word diff (in the
style of GNU wdiff and git diff --word-diff).
Also use a similar style diff to show changes in the runtaskhashes list.
I didn't use an actual word-diff here since it's a little different - we
can be sure that the list is a list and not simply a free-format string.
(Bitbake rev: 20db6b6553c80e18afc4f43dc2495435f7477822)
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake')
-rw-r--r-- | bitbake/LICENSE | 2 | ||||
-rw-r--r-- | bitbake/lib/bb/siggen.py | 38 | ||||
-rw-r--r-- | bitbake/lib/simplediff/LICENSE | 22 | ||||
-rw-r--r-- | bitbake/lib/simplediff/__init__.py | 198 |
4 files changed, 259 insertions, 1 deletions
diff --git a/bitbake/LICENSE b/bitbake/LICENSE index 5d4a4c2a8a..7d4e5f44b5 100644 --- a/bitbake/LICENSE +++ b/bitbake/LICENSE | |||
@@ -15,3 +15,5 @@ Foundation and individual contributors. | |||
15 | * QUnit is redistributed under the MIT license. | 15 | * QUnit is redistributed under the MIT license. |
16 | 16 | ||
17 | * Font Awesome fonts redistributed under the SIL Open Font License 1.1 | 17 | * Font Awesome fonts redistributed under the SIL Open Font License 1.1 |
18 | |||
19 | * simplediff is distributed under the zlib license. | ||
diff --git a/bitbake/lib/bb/siggen.py b/bitbake/lib/bb/siggen.py index 3c5d86247c..d40c721fbf 100644 --- a/bitbake/lib/bb/siggen.py +++ b/bitbake/lib/bb/siggen.py | |||
@@ -6,6 +6,7 @@ import tempfile | |||
6 | import pickle | 6 | import pickle |
7 | import bb.data | 7 | import bb.data |
8 | import difflib | 8 | import difflib |
9 | import simplediff | ||
9 | from bb.checksum import FileChecksumCache | 10 | from bb.checksum import FileChecksumCache |
10 | 11 | ||
11 | logger = logging.getLogger('BitBake.SigGen') | 12 | logger = logging.getLogger('BitBake.SigGen') |
@@ -352,6 +353,39 @@ def dump_this_task(outfile, d): | |||
352 | referencestamp = bb.build.stamp_internal(task, d, None, True) | 353 | referencestamp = bb.build.stamp_internal(task, d, None, True) |
353 | bb.parse.siggen.dump_sigtask(fn, task, outfile, "customfile:" + referencestamp) | 354 | bb.parse.siggen.dump_sigtask(fn, task, outfile, "customfile:" + referencestamp) |
354 | 355 | ||
356 | def worddiff_str(oldstr, newstr): | ||
357 | diff = simplediff.diff(oldstr.split(' '), newstr.split(' ')) | ||
358 | ret = [] | ||
359 | for change, value in diff: | ||
360 | value = ' '.join(value) | ||
361 | if change == '=': | ||
362 | ret.append(value) | ||
363 | elif change == '+': | ||
364 | item = '{+%s+}' % value | ||
365 | ret.append(item) | ||
366 | elif change == '-': | ||
367 | item = '[-%s-]' % value | ||
368 | ret.append(item) | ||
369 | whitespace_note = '' | ||
370 | if oldstr != newstr and ' '.join(oldstr.split()) == ' '.join(newstr.split()): | ||
371 | whitespace_note = ' (whitespace changed)' | ||
372 | return '"%s"%s' % (' '.join(ret), whitespace_note) | ||
373 | |||
374 | def list_inline_diff(oldlist, newlist): | ||
375 | diff = simplediff.diff(oldlist, newlist) | ||
376 | ret = [] | ||
377 | for change, value in diff: | ||
378 | value = ' '.join(value) | ||
379 | if change == '=': | ||
380 | ret.append("'%s'" % value) | ||
381 | elif change == '+': | ||
382 | item = "+'%s'" % value | ||
383 | ret.append(item) | ||
384 | elif change == '-': | ||
385 | item = "-'%s'" % value | ||
386 | ret.append(item) | ||
387 | return '[%s]' % (', '.join(ret)) | ||
388 | |||
355 | def clean_basepath(a): | 389 | def clean_basepath(a): |
356 | mc = None | 390 | mc = None |
357 | if a.startswith("multiconfig:"): | 391 | if a.startswith("multiconfig:"): |
@@ -471,6 +505,8 @@ def compare_sigfiles(a, b, recursecb=None, collapsed=False): | |||
471 | # the old/new filename (they are blank anyway in this case) | 505 | # the old/new filename (they are blank anyway in this case) |
472 | difflines = list(diff)[2:] | 506 | difflines = list(diff)[2:] |
473 | output.append("Variable %s value changed:\n%s" % (dep, '\n'.join(difflines))) | 507 | output.append("Variable %s value changed:\n%s" % (dep, '\n'.join(difflines))) |
508 | elif newval and oldval and (' ' in oldval or ' ' in newval): | ||
509 | output.append("Variable %s value changed:\n%s" % (dep, worddiff_str(oldval, newval))) | ||
474 | else: | 510 | else: |
475 | output.append("Variable %s value changed from '%s' to '%s'" % (dep, oldval, newval)) | 511 | output.append("Variable %s value changed from '%s' to '%s'" % (dep, oldval, newval)) |
476 | 512 | ||
@@ -510,7 +546,7 @@ def compare_sigfiles(a, b, recursecb=None, collapsed=False): | |||
510 | clean_a = clean_basepaths_list(a_data['runtaskdeps']) | 546 | clean_a = clean_basepaths_list(a_data['runtaskdeps']) |
511 | clean_b = clean_basepaths_list(b_data['runtaskdeps']) | 547 | clean_b = clean_basepaths_list(b_data['runtaskdeps']) |
512 | if clean_a != clean_b: | 548 | if clean_a != clean_b: |
513 | output.append("runtaskdeps changed from %s to %s" % (clean_a, clean_b)) | 549 | output.append("runtaskdeps changed:\n%s" % list_inline_diff(clean_a, clean_b)) |
514 | else: | 550 | else: |
515 | output.append("runtaskdeps changed:") | 551 | output.append("runtaskdeps changed:") |
516 | output.append("\n".join(changed)) | 552 | output.append("\n".join(changed)) |
diff --git a/bitbake/lib/simplediff/LICENSE b/bitbake/lib/simplediff/LICENSE new file mode 100644 index 0000000000..8242dde97c --- /dev/null +++ b/bitbake/lib/simplediff/LICENSE | |||
@@ -0,0 +1,22 @@ | |||
1 | Copyright (c) 2008 - 2013 Paul Butler and contributors | ||
2 | |||
3 | This sofware may be used under a zlib/libpng-style license: | ||
4 | |||
5 | This software is provided 'as-is', without any express or implied warranty. In | ||
6 | no event will the authors be held liable for any damages arising from the use | ||
7 | of this software. | ||
8 | |||
9 | Permission is granted to anyone to use this software for any purpose, including | ||
10 | commercial applications, and to alter it and redistribute it freely, subject to | ||
11 | the following restrictions: | ||
12 | |||
13 | 1. The origin of this software must not be misrepresented; you must not claim | ||
14 | that you wrote the original software. If you use this software in a product, an | ||
15 | acknowledgment in the product documentation would be appreciated but is not | ||
16 | required. | ||
17 | |||
18 | 2. Altered source versions must be plainly marked as such, and must not be | ||
19 | misrepresented as being the original software. | ||
20 | |||
21 | 3. This notice may not be removed or altered from any source distribution. | ||
22 | |||
diff --git a/bitbake/lib/simplediff/__init__.py b/bitbake/lib/simplediff/__init__.py new file mode 100644 index 0000000000..57ee3c5c40 --- /dev/null +++ b/bitbake/lib/simplediff/__init__.py | |||
@@ -0,0 +1,198 @@ | |||
1 | ''' | ||
2 | Simple Diff for Python version 1.0 | ||
3 | |||
4 | Annotate two versions of a list with the values that have been | ||
5 | changed between the versions, similar to unix's `diff` but with | ||
6 | a dead-simple Python interface. | ||
7 | |||
8 | (C) Paul Butler 2008-2012 <http://www.paulbutler.org/> | ||
9 | May be used and distributed under the zlib/libpng license | ||
10 | <http://www.opensource.org/licenses/zlib-license.php> | ||
11 | ''' | ||
12 | |||
13 | __all__ = ['diff', 'string_diff', 'html_diff'] | ||
14 | __version__ = '1.0' | ||
15 | |||
16 | |||
17 | def diff(old, new): | ||
18 | ''' | ||
19 | Find the differences between two lists. Returns a list of pairs, where the | ||
20 | first value is in ['+','-','='] and represents an insertion, deletion, or | ||
21 | no change for that list. The second value of the pair is the list | ||
22 | of elements. | ||
23 | |||
24 | Params: | ||
25 | old the old list of immutable, comparable values (ie. a list | ||
26 | of strings) | ||
27 | new the new list of immutable, comparable values | ||
28 | |||
29 | Returns: | ||
30 | A list of pairs, with the first part of the pair being one of three | ||
31 | strings ('-', '+', '=') and the second part being a list of values from | ||
32 | the original old and/or new lists. The first part of the pair | ||
33 | corresponds to whether the list of values is a deletion, insertion, or | ||
34 | unchanged, respectively. | ||
35 | |||
36 | Examples: | ||
37 | >>> diff([1,2,3,4],[1,3,4]) | ||
38 | [('=', [1]), ('-', [2]), ('=', [3, 4])] | ||
39 | |||
40 | >>> diff([1,2,3,4],[2,3,4,1]) | ||
41 | [('-', [1]), ('=', [2, 3, 4]), ('+', [1])] | ||
42 | |||
43 | >>> diff('The quick brown fox jumps over the lazy dog'.split(), | ||
44 | ... 'The slow blue cheese drips over the lazy carrot'.split()) | ||
45 | ... # doctest: +NORMALIZE_WHITESPACE | ||
46 | [('=', ['The']), | ||
47 | ('-', ['quick', 'brown', 'fox', 'jumps']), | ||
48 | ('+', ['slow', 'blue', 'cheese', 'drips']), | ||
49 | ('=', ['over', 'the', 'lazy']), | ||
50 | ('-', ['dog']), | ||
51 | ('+', ['carrot'])] | ||
52 | |||
53 | ''' | ||
54 | |||
55 | # Create a map from old values to their indices | ||
56 | old_index_map = dict() | ||
57 | for i, val in enumerate(old): | ||
58 | old_index_map.setdefault(val,list()).append(i) | ||
59 | |||
60 | # Find the largest substring common to old and new. | ||
61 | # We use a dynamic programming approach here. | ||
62 | # | ||
63 | # We iterate over each value in the `new` list, calling the | ||
64 | # index `inew`. At each iteration, `overlap[i]` is the | ||
65 | # length of the largest suffix of `old[:i]` equal to a suffix | ||
66 | # of `new[:inew]` (or unset when `old[i]` != `new[inew]`). | ||
67 | # | ||
68 | # At each stage of iteration, the new `overlap` (called | ||
69 | # `_overlap` until the original `overlap` is no longer needed) | ||
70 | # is built from the old one. | ||
71 | # | ||
72 | # If the length of overlap exceeds the largest substring | ||
73 | # seen so far (`sub_length`), we update the largest substring | ||
74 | # to the overlapping strings. | ||
75 | |||
76 | overlap = dict() | ||
77 | # `sub_start_old` is the index of the beginning of the largest overlapping | ||
78 | # substring in the old list. `sub_start_new` is the index of the beginning | ||
79 | # of the same substring in the new list. `sub_length` is the length that | ||
80 | # overlaps in both. | ||
81 | # These track the largest overlapping substring seen so far, so naturally | ||
82 | # we start with a 0-length substring. | ||
83 | sub_start_old = 0 | ||
84 | sub_start_new = 0 | ||
85 | sub_length = 0 | ||
86 | |||
87 | for inew, val in enumerate(new): | ||
88 | _overlap = dict() | ||
89 | for iold in old_index_map.get(val,list()): | ||
90 | # now we are considering all values of iold such that | ||
91 | # `old[iold] == new[inew]`. | ||
92 | _overlap[iold] = (iold and overlap.get(iold - 1, 0)) + 1 | ||
93 | if(_overlap[iold] > sub_length): | ||
94 | # this is the largest substring seen so far, so store its | ||
95 | # indices | ||
96 | sub_length = _overlap[iold] | ||
97 | sub_start_old = iold - sub_length + 1 | ||
98 | sub_start_new = inew - sub_length + 1 | ||
99 | overlap = _overlap | ||
100 | |||
101 | if sub_length == 0: | ||
102 | # If no common substring is found, we return an insert and delete... | ||
103 | return (old and [('-', old)] or []) + (new and [('+', new)] or []) | ||
104 | else: | ||
105 | # ...otherwise, the common substring is unchanged and we recursively | ||
106 | # diff the text before and after that substring | ||
107 | return diff(old[ : sub_start_old], new[ : sub_start_new]) + \ | ||
108 | [('=', new[sub_start_new : sub_start_new + sub_length])] + \ | ||
109 | diff(old[sub_start_old + sub_length : ], | ||
110 | new[sub_start_new + sub_length : ]) | ||
111 | |||
112 | |||
113 | def string_diff(old, new): | ||
114 | ''' | ||
115 | Returns the difference between the old and new strings when split on | ||
116 | whitespace. Considers punctuation a part of the word | ||
117 | |||
118 | This function is intended as an example; you'll probably want | ||
119 | a more sophisticated wrapper in practice. | ||
120 | |||
121 | Params: | ||
122 | old the old string | ||
123 | new the new string | ||
124 | |||
125 | Returns: | ||
126 | the output of `diff` on the two strings after splitting them | ||
127 | on whitespace (a list of change instructions; see the docstring | ||
128 | of `diff`) | ||
129 | |||
130 | Examples: | ||
131 | >>> string_diff('The quick brown fox', 'The fast blue fox') | ||
132 | ... # doctest: +NORMALIZE_WHITESPACE | ||
133 | [('=', ['The']), | ||
134 | ('-', ['quick', 'brown']), | ||
135 | ('+', ['fast', 'blue']), | ||
136 | ('=', ['fox'])] | ||
137 | |||
138 | ''' | ||
139 | return diff(old.split(), new.split()) | ||
140 | |||
141 | |||
142 | def html_diff(old, new): | ||
143 | ''' | ||
144 | Returns the difference between two strings (as in stringDiff) in | ||
145 | HTML format. HTML code in the strings is NOT escaped, so you | ||
146 | will get weird results if the strings contain HTML. | ||
147 | |||
148 | This function is intended as an example; you'll probably want | ||
149 | a more sophisticated wrapper in practice. | ||
150 | |||
151 | Params: | ||
152 | old the old string | ||
153 | new the new string | ||
154 | |||
155 | Returns: | ||
156 | the output of the diff expressed with HTML <ins> and <del> | ||
157 | tags. | ||
158 | |||
159 | Examples: | ||
160 | >>> html_diff('The quick brown fox', 'The fast blue fox') | ||
161 | 'The <del>quick brown</del> <ins>fast blue</ins> fox' | ||
162 | ''' | ||
163 | con = {'=': (lambda x: x), | ||
164 | '+': (lambda x: "<ins>" + x + "</ins>"), | ||
165 | '-': (lambda x: "<del>" + x + "</del>")} | ||
166 | return " ".join([(con[a])(" ".join(b)) for a, b in string_diff(old, new)]) | ||
167 | |||
168 | |||
169 | def check_diff(old, new): | ||
170 | ''' | ||
171 | This tests that diffs returned by `diff` are valid. You probably won't | ||
172 | want to use this function, but it's provided for documentation and | ||
173 | testing. | ||
174 | |||
175 | A diff should satisfy the property that the old input is equal to the | ||
176 | elements of the result annotated with '-' or '=' concatenated together. | ||
177 | Likewise, the new input is equal to the elements of the result annotated | ||
178 | with '+' or '=' concatenated together. This function compares `old`, | ||
179 | `new`, and the results of `diff(old, new)` to ensure this is true. | ||
180 | |||
181 | Tests: | ||
182 | >>> check_diff('ABCBA', 'CBABA') | ||
183 | >>> check_diff('Foobarbaz', 'Foobarbaz') | ||
184 | >>> check_diff('Foobarbaz', 'Boobazbam') | ||
185 | >>> check_diff('The quick brown fox', 'Some quick brown car') | ||
186 | >>> check_diff('A thick red book', 'A quick blue book') | ||
187 | >>> check_diff('dafhjkdashfkhasfjsdafdasfsda', 'asdfaskjfhksahkfjsdha') | ||
188 | >>> check_diff('88288822828828288282828', '88288882882828282882828') | ||
189 | >>> check_diff('1234567890', '24689') | ||
190 | ''' | ||
191 | old = list(old) | ||
192 | new = list(new) | ||
193 | result = diff(old, new) | ||
194 | _old = [val for (a, vals) in result if (a in '=-') for val in vals] | ||
195 | assert old == _old, 'Expected %s, got %s' % (old, _old) | ||
196 | _new = [val for (a, vals) in result if (a in '=+') for val in vals] | ||
197 | assert new == _new, 'Expected %s, got %s' % (new, _new) | ||
198 | |||