2 # -*- Mode: python; tab-width: 4; indent-tabs-mode: t -*-
4 # This file is part of the LibreOffice project.
6 # This Source Code Form is subject to the terms of the Mozilla Public
7 # License, v. 2.0. If a copy of the MPL was not distributed with this
8 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
12 This script is to fix precompiled headers.
14 This script runs in two modes.
15 In one mode, it starts with a header
16 that doesn't compile. If finds the
17 minimum number of includes in the
18 header to remove to get a successful
19 run of the command (i.e. compile).
21 In the second mode, it starts with a
22 header that compiles fine, however,
23 it contains one or more required
24 include without which it wouldn't
25 compile, which it identifies.
27 Usage: ./bin/update_pch_bisect ./vcl/inc/pch/precompiled_vcl.hxx "make vcl.build" --find-required --verbose
47 cmd
= command
.split(' ', 1)
48 status
= subprocess
.call(cmd
, stdout
=open(os
.devnull
, 'w'),
49 stderr
=subprocess
.STDOUT
, close_fds
=True)
50 return True if status
== 0 else False
51 except Exception as e
:
52 sys
.stderr
.write('Error: {}\n'.format(e
))
55 def update_pch(filename
, lines
, marks
):
56 with
open(filename
, 'w') as f
:
57 for i
, mark
in enumerate(marks
):
61 f
.write('//' + lines
[i
])
63 def log(*args
, **kwargs
):
66 print(*args
, **kwargs
)
68 def bisect(lines
, marks
, min, max, update
, command
):
69 """ Disable half the includes and
71 Depending on the result,
76 log('Bisecting [{}, {}].'.format(min+1, max))
77 for i
in range(min, max):
78 if marks
[i
] != IGNORE
:
79 marks
[i
] = TEST_ON
if FIND_CONFLICTS
else TEST_OFF
82 if not FIND_CONFLICTS
:
83 on_list
= [x
for x
in marks
if x
in (TEST_ON
, GOOD
)]
84 assume_fail
= (len(on_list
) == 0)
87 if assume_fail
or not command():
89 log('Failed [{}, {}].'.format(min+1, max))
91 if not FIND_CONFLICTS
:
92 # Try with this one alone.
96 log(' Found @{}: {}'.format(min+1, lines
[min].strip('\n')))
100 log(' Found @{}: {}'.format(min+1, lines
[min].strip('\n')))
101 # Either way, this one is irrelevant.
106 for i
in range(min, max):
107 if marks
[i
] != IGNORE
:
108 marks
[i
] = TEST_OFF
if FIND_CONFLICTS
else TEST_ON
110 half
= min + ((max - min) / 2)
111 marks
= bisect(lines
, marks
, min, half
, update
, command
)
112 marks
= bisect(lines
, marks
, half
, max, update
, command
)
116 log(' Good [{}, {}].'.format(min+1, max))
117 for i
in range(min, max):
118 if marks
[i
] != IGNORE
:
123 def get_filename(line
):
124 """ Strips the line from the
125 '#include' and angled brackets
126 and return the filename only.
128 return re
.sub(r
'(.*#include\s*)<(.*)>(.*)', r
'\2', line
)
130 def get_marks(lines
):
134 for i
, line
in enumerate(lines
):
135 if line
.startswith('#include'):
136 marks
.append(TEST_ON
)
137 min = i
if min < 0 else min
142 return (marks
, min, max+1)
146 global FIND_CONFLICTS
149 filename
= sys
.argv
[1]
150 command
= sys
.argv
[2]
152 for i
in range(3, len(sys
.argv
)):
154 if opt
== '--find-conflicts':
155 FIND_CONFLICTS
= True
156 elif opt
== '--find-required':
157 FIND_CONFLICTS
= False
158 elif opt
== '--verbose':
161 sys
.stderr
.write('Error: Unknown option [{}].\n'.format(opt
))
165 with
open(filename
) as f
:
166 lines
= f
.readlines()
168 (marks
, min, max) = get_marks(lines
)
170 # Test preconditions.
171 log('Validating all-excluded state...')
172 for i
in range(min, max):
173 if marks
[i
] != IGNORE
:
175 update_pch(filename
, lines
, marks
)
179 # Must build all excluded.
181 sys
.stderr
.write("Error: broken state when all excluded, fix first and try again.")
184 # If builds all excluded, we can't bisect.
186 sys
.stderr
.write("Done: in good state when all excluded, nothing to do.")
189 # Must build all included.
190 log('Validating all-included state...')
191 for i
in range(min, max):
192 if marks
[i
] != IGNORE
:
194 update_pch(filename
, lines
, marks
)
196 sys
.stderr
.write("Error: broken state without modifying, fix first and try again.")
199 marks
= bisect(lines
, marks
, min, max+1,
200 lambda l
, m
: update_pch(filename
, l
, m
),
201 lambda: run(command
))
202 if not FIND_CONFLICTS
:
203 # Simplify further, as sometimes we can have
204 # false positives due to the benign nature
205 # of includes that are not absolutely required.
206 for i
, mark
in enumerate(marks
):
209 update_pch(filename
, lines
, marks
)
215 elif mark
== TEST_OFF
:
218 update_pch(filename
, lines
, marks
)
221 for i
, mark
in enumerate(marks
):
222 if mark
== (BAD
if FIND_CONFLICTS
else GOOD
):
223 print("'{}',".format(get_filename(lines
[i
].strip('\n'))))
227 if __name__
== '__main__':
229 if len(sys
.argv
) in (3, 4, 5):
233 print('Usage: {} <pch> <command> [--find-conflicts]|[--find-required] [--verbose]\n'.format(sys
.argv
[0]))
234 print(' --find-conflicts - Finds all conflicting includes. (Default)')
235 print(' Must compile without any includes.\n')
236 print(' --find-required - Finds all required includes.')
237 print(' Must compile with all includes.\n')
238 print(' --verbose - print noisy progress.')
239 print('Example: ./bin/update_pch_bisect ./vcl/inc/pch/precompiled_vcl.hxx "make vcl.build" --find-required --verbose')
240 print('\nRunning unit-tests...')
243 class TestBisectConflict(unittest
.TestCase
):
244 TEST
= """ /* Test header. */
251 BAD_LINE
= "#include <bad>"
254 global FIND_CONFLICTS
255 FIND_CONFLICTS
= True
257 def _update_func(self
, lines
, marks
):
259 for i
, mark
in enumerate(marks
):
261 self
.lines
.append(lines
[i
])
263 self
.lines
.append('//' + lines
[i
])
265 def _test_func(self
):
266 """ Command function called by bisect.
267 Returns True on Success, False on failure.
269 # If the bad line is still there, fail.
270 return self
.BAD_LINE
not in self
.lines
272 def test_success(self
):
273 lines
= self
.TEST
.split('\n')
274 (marks
, min, max) = get_marks(lines
)
275 marks
= bisect(lines
, marks
, min, max,
276 lambda l
, m
: self
._update
_func
(l
, m
),
277 lambda: self
._test
_func
())
278 self
.assertTrue(BAD
not in marks
)
280 def test_conflict(self
):
281 lines
= self
.TEST
.split('\n')
282 for pos
in range(len(lines
) + 1):
283 lines
= self
.TEST
.split('\n')
284 lines
.insert(pos
, self
.BAD_LINE
)
285 (marks
, min, max) = get_marks(lines
)
287 marks
= bisect(lines
, marks
, min, max,
288 lambda l
, m
: self
._update
_func
(l
, m
),
289 lambda: self
._test
_func
())
290 for i
, mark
in enumerate(marks
):
292 self
.assertEqual(BAD
, mark
)
294 self
.assertNotEqual(BAD
, mark
)
296 class TestBisectRequired(unittest
.TestCase
):
297 TEST
= """#include <algorithm>
302 REQ_LINE
= "#include <req>"
305 global FIND_CONFLICTS
306 FIND_CONFLICTS
= False
308 def _update_func(self
, lines
, marks
):
310 for i
, mark
in enumerate(marks
):
312 self
.lines
.append(lines
[i
])
314 self
.lines
.append('//' + lines
[i
])
316 def _test_func(self
):
317 """ Command function called by bisect.
318 Returns True on Success, False on failure.
320 # If the required line is not there, fail.
321 found
= self
.REQ_LINE
in self
.lines
324 def test_success(self
):
325 lines
= self
.TEST
.split('\n')
326 (marks
, min, max) = get_marks(lines
)
327 marks
= bisect(lines
, marks
, min, max,
328 lambda l
, m
: self
._update
_func
(l
, m
),
329 lambda: self
._test
_func
())
330 self
.assertTrue(GOOD
not in marks
)
332 def test_required(self
):
333 lines
= self
.TEST
.split('\n')
334 for pos
in range(len(lines
) + 1):
335 lines
= self
.TEST
.split('\n')
336 lines
.insert(pos
, self
.REQ_LINE
)
337 (marks
, min, max) = get_marks(lines
)
339 marks
= bisect(lines
, marks
, min, max,
340 lambda l
, m
: self
._update
_func
(l
, m
),
341 lambda: self
._test
_func
())
342 for i
, mark
in enumerate(marks
):
344 self
.assertEqual(GOOD
, mark
)
346 self
.assertNotEqual(GOOD
, mark
)
350 # vim: set et sw=4 ts=4 expandtab: