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
in xrange(len(marks
)):
62 f
.write('//' + lines
[i
])
64 def log(*args
, **kwargs
):
67 print(*args
, **kwargs
)
69 def bisect(lines
, marks
, min, max, update
, command
):
70 """ Disable half the includes and
72 Depending on the result,
77 log('Bisecting [{}, {}].'.format(min+1, max))
78 for i
in range(min, max):
79 if marks
[i
] != IGNORE
:
80 marks
[i
] = TEST_ON
if FIND_CONFLICTS
else TEST_OFF
83 if not FIND_CONFLICTS
:
84 on_list
= [x
for x
in marks
if x
in (TEST_ON
, GOOD
)]
85 assume_fail
= (len(on_list
) == 0)
88 if assume_fail
or not command():
90 log('Failed [{}, {}].'.format(min+1, max))
92 if not FIND_CONFLICTS
:
93 # Try with this one alone.
97 log(' Found @{}: {}'.format(min+1, lines
[min].strip('\n')))
101 log(' Found @{}: {}'.format(min+1, lines
[min].strip('\n')))
102 # Either way, this one is irrelevant.
107 for i
in range(min, max):
108 if marks
[i
] != IGNORE
:
109 marks
[i
] = TEST_OFF
if FIND_CONFLICTS
else TEST_ON
111 half
= min + ((max - min) / 2)
112 marks
= bisect(lines
, marks
, min, half
, update
, command
)
113 marks
= bisect(lines
, marks
, half
, max, update
, command
)
117 log(' Good [{}, {}].'.format(min+1, max))
118 for i
in range(min, max):
119 if marks
[i
] != IGNORE
:
124 def get_filename(line
):
125 """ Strips the line from the
126 '#include' and angled brakets
127 and return the filename only.
129 return re
.sub(r
'(.*#include\s*)<(.*)>(.*)', r
'\2', line
)
131 def get_marks(lines
):
135 for i
in xrange(len(lines
)):
137 if line
.startswith('#include'):
138 marks
.append(TEST_ON
)
139 min = i
if min < 0 else min
144 return (marks
, min, max+1)
148 global FIND_CONFLICTS
151 filename
= sys
.argv
[1]
152 command
= sys
.argv
[2]
154 for i
in range(3, len(sys
.argv
)):
156 if opt
== '--find-conflicts':
157 FIND_CONFLICTS
= True
158 elif opt
== '--find-required':
159 FIND_CONFLICTS
= False
160 elif opt
== '--verbose':
163 sys
.stderr
.write('Error: Unknown option [{}].\n'.format(opt
))
167 with
open(filename
) as f
:
168 lines
= f
.readlines()
170 (marks
, min, max) = get_marks(lines
)
172 # Test preconditions.
173 log('Validating all-excluded state...')
174 for i
in range(min, max):
175 if marks
[i
] != IGNORE
:
177 update_pch(filename
, lines
, marks
)
181 # Must build all excluded.
183 sys
.stderr
.write("Error: broken state when all excluded, fix first and try again.")
186 # If builds all excluded, we can't bisect.
188 sys
.stderr
.write("Done: in good state when all excluded, nothing to do.")
191 # Must build all included.
192 log('Validating all-included state...')
193 for i
in range(min, max):
194 if marks
[i
] != IGNORE
:
196 update_pch(filename
, lines
, marks
)
198 sys
.stderr
.write("Error: broken state without modifying, fix first and try again.")
201 marks
= bisect(lines
, marks
, min, max+1,
202 lambda l
, m
: update_pch(filename
, l
, m
),
203 lambda: run(command
))
204 if not FIND_CONFLICTS
:
205 # Simplify further, as sometimes we can have
206 # false positives due to the benign nature
207 # of includes that are not absolutely required.
208 for i
in xrange(len(marks
)):
211 update_pch(filename
, lines
, marks
)
217 elif marks
[i
] == TEST_OFF
:
220 update_pch(filename
, lines
, marks
)
223 for i
in xrange(len(marks
)):
224 if marks
[i
] == (BAD
if FIND_CONFLICTS
else GOOD
):
225 print("'{}',".format(get_filename(lines
[i
].strip('\n'))))
229 if __name__
== '__main__':
231 if len(sys
.argv
) in (3, 4, 5):
235 print('Usage: {} <pch> <command> [--find-conflicts]|[--find-required] [--verbose]\n'.format(sys
.argv
[0]))
236 print(' --find-conflicts - Finds all conflicting includes. (Default)')
237 print(' Must compile without any includes.\n')
238 print(' --find-required - Finds all required includes.')
239 print(' Must compile with all includes.\n')
240 print(' --verbose - print noisy progress.')
241 print('Example: ./bin/update_pch_bisect ./vcl/inc/pch/precompiled_vcl.hxx "make vcl.build" --find-required --verbose')
242 print('\nRunning unit-tests...')
245 class TestBisectConflict(unittest
.TestCase
):
246 TEST
= """ /* Test header. */
253 BAD_LINE
= "#include <bad>"
256 global FIND_CONFLICTS
257 FIND_CONFLICTS
= True
259 def _update_func(self
, lines
, marks
):
261 for i
in xrange(len(marks
)):
264 self
.lines
.append(lines
[i
])
266 self
.lines
.append('//' + lines
[i
])
268 def _test_func(self
):
269 """ Command function called by bisect.
270 Returns True on Success, False on failure.
272 # If the bad line is still there, fail.
273 return self
.BAD_LINE
not in self
.lines
275 def test_success(self
):
276 lines
= self
.TEST
.split('\n')
277 (marks
, min, max) = get_marks(lines
)
278 marks
= bisect(lines
, marks
, min, max,
279 lambda l
, m
: self
._update
_func
(l
, m
),
280 lambda: self
._test
_func
())
281 self
.assertTrue(BAD
not in marks
)
283 def test_conflict(self
):
284 lines
= self
.TEST
.split('\n')
285 for pos
in xrange(len(lines
) + 1):
286 lines
= self
.TEST
.split('\n')
287 lines
.insert(pos
, self
.BAD_LINE
)
288 (marks
, min, max) = get_marks(lines
)
290 marks
= bisect(lines
, marks
, min, max,
291 lambda l
, m
: self
._update
_func
(l
, m
),
292 lambda: self
._test
_func
())
293 for i
in xrange(len(marks
)):
295 self
.assertEqual(BAD
, marks
[i
])
297 self
.assertNotEqual(BAD
, marks
[i
])
299 class TestBisectRequired(unittest
.TestCase
):
300 TEST
= """#include <algorithm>
305 REQ_LINE
= "#include <req>"
308 global FIND_CONFLICTS
309 FIND_CONFLICTS
= False
311 def _update_func(self
, lines
, marks
):
313 for i
in xrange(len(marks
)):
316 self
.lines
.append(lines
[i
])
318 self
.lines
.append('//' + lines
[i
])
320 def _test_func(self
):
321 """ Command function called by bisect.
322 Returns True on Success, False on failure.
324 # If the required line is not there, fail.
325 found
= self
.REQ_LINE
in self
.lines
328 def test_success(self
):
329 lines
= self
.TEST
.split('\n')
330 (marks
, min, max) = get_marks(lines
)
331 marks
= bisect(lines
, marks
, min, max,
332 lambda l
, m
: self
._update
_func
(l
, m
),
333 lambda: self
._test
_func
())
334 self
.assertTrue(GOOD
not in marks
)
336 def test_required(self
):
337 lines
= self
.TEST
.split('\n')
338 for pos
in xrange(len(lines
) + 1):
339 lines
= self
.TEST
.split('\n')
340 lines
.insert(pos
, self
.REQ_LINE
)
341 (marks
, min, max) = get_marks(lines
)
343 marks
= bisect(lines
, marks
, min, max,
344 lambda l
, m
: self
._update
_func
(l
, m
),
345 lambda: self
._test
_func
())
346 for i
in xrange(len(marks
)):
348 self
.assertEqual(GOOD
, marks
[i
])
350 self
.assertNotEqual(GOOD
, marks
[i
])
354 # vim: set et sw=4 ts=4 expandtab: