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
30 from __future__
import print_function
49 cmd
= command
.split(' ', 1)
50 status
= subprocess
.call(cmd
, stdout
=open(os
.devnull
, 'w'),
51 stderr
=subprocess
.STDOUT
, close_fds
=True)
52 return True if status
== 0 else False
53 except Exception as e
:
54 sys
.stderr
.write('Error: {}\n'.format(e
))
57 def update_pch(filename
, lines
, marks
):
58 with
open(filename
, 'w') as f
:
59 for i
in xrange(len(marks
)):
64 f
.write('//' + lines
[i
])
66 def log(*args
, **kwargs
):
69 print(*args
, **kwargs
)
71 def bisect(lines
, marks
, min, max, update
, command
):
72 """ Disable half the includes and
74 Depending on the result,
79 log('Bisecting [{}, {}].'.format(min+1, max))
80 for i
in range(min, max):
81 if marks
[i
] != IGNORE
:
82 marks
[i
] = TEST_ON
if FIND_CONFLICTS
else TEST_OFF
85 if not FIND_CONFLICTS
:
86 on_list
= [x
for x
in marks
if x
in (TEST_ON
, GOOD
)]
87 assume_fail
= (len(on_list
) == 0)
90 if assume_fail
or not command():
92 log('Failed [{}, {}].'.format(min+1, max))
94 if not FIND_CONFLICTS
:
95 # Try with this one alone.
99 log(' Found @{}: {}'.format(min+1, lines
[min].strip('\n')))
103 log(' Found @{}: {}'.format(min+1, lines
[min].strip('\n')))
104 # Either way, this one is irrelevant.
109 for i
in range(min, max):
110 if marks
[i
] != IGNORE
:
111 marks
[i
] = TEST_OFF
if FIND_CONFLICTS
else TEST_ON
113 half
= min + ((max - min) / 2)
114 marks
= bisect(lines
, marks
, min, half
, update
, command
)
115 marks
= bisect(lines
, marks
, half
, max, update
, command
)
119 log(' Good [{}, {}].'.format(min+1, max))
120 for i
in range(min, max):
121 if marks
[i
] != IGNORE
:
126 def get_filename(line
):
127 """ Strips the line from the
128 '#include' and angled brakets
129 and return the filename only.
131 return re
.sub(r
'(.*#include\s*)<(.*)>(.*)', r
'\2', line
)
133 def get_marks(lines
):
137 for i
in xrange(len(lines
)):
139 if line
.startswith('#include'):
140 marks
.append(TEST_ON
)
141 min = i
if min < 0 else min
146 return (marks
, min, max+1)
150 global FIND_CONFLICTS
153 filename
= sys
.argv
[1]
154 command
= sys
.argv
[2]
156 for i
in range(3, len(sys
.argv
)):
158 if opt
== '--find-conflicts':
159 FIND_CONFLICTS
= True
160 elif opt
== '--find-required':
161 FIND_CONFLICTS
= False
162 elif opt
== '--verbose':
165 sys
.stderr
.write('Error: Unknown option [{}].\n'.format(opt
))
169 with
open(filename
) as f
:
170 lines
= f
.readlines()
172 (marks
, min, max) = get_marks(lines
)
174 # Test preconditions.
175 log('Validating all-excluded state...')
176 for i
in range(min, max):
177 if marks
[i
] != IGNORE
:
179 update_pch(filename
, lines
, marks
)
183 # Must build all excluded.
185 sys
.stderr
.write("Error: broken state when all excluded, fix first and try again.")
188 # If builds all excluded, we can't bisect.
190 sys
.stderr
.write("Done: in good state when all excluded, nothing to do.")
193 # Must build all included.
194 log('Validating all-included state...')
195 for i
in range(min, max):
196 if marks
[i
] != IGNORE
:
198 update_pch(filename
, lines
, marks
)
200 sys
.stderr
.write("Error: broken state without modifying, fix first and try again.")
203 marks
= bisect(lines
, marks
, min, max+1,
204 lambda l
, m
: update_pch(filename
, l
, m
),
205 lambda: run(command
))
206 if not FIND_CONFLICTS
:
207 # Simplify further, as sometimes we can have
208 # false positives due to the benign nature
209 # of includes that are not absolutely required.
210 for i
in xrange(len(marks
)):
213 update_pch(filename
, lines
, marks
)
219 elif marks
[i
] == TEST_OFF
:
222 update_pch(filename
, lines
, marks
)
225 for i
in xrange(len(marks
)):
226 if marks
[i
] == (BAD
if FIND_CONFLICTS
else GOOD
):
227 print("'{}',".format(get_filename(lines
[i
].strip('\n'))))
231 if __name__
== '__main__':
233 if len(sys
.argv
) in (3, 4, 5):
237 print('Usage: {} <pch> <command> [--find-conflicts]|[--find-required] [--verbose]\n'.format(sys
.argv
[0]))
238 print(' --find-conflicts - Finds all conflicting includes. (Default)')
239 print(' Must compile without any includes.\n')
240 print(' --find-required - Finds all required includes.')
241 print(' Must compile with all includes.\n')
242 print(' --verbose - print noisy progress.')
243 print('Example: ./bin/update_pch_bisect ./vcl/inc/pch/precompiled_vcl.hxx "make vcl.build" --find-required --verbose')
244 print('\nRunning unit-tests...')
247 class TestBisectConflict(unittest
.TestCase
):
248 TEST
= """ /* Test header. */
255 BAD_LINE
= "#include <bad>"
258 global FIND_CONFLICTS
259 FIND_CONFLICTS
= True
261 def _update_func(self
, lines
, marks
):
263 for i
in xrange(len(marks
)):
266 self
.lines
.append(lines
[i
])
268 self
.lines
.append('//' + lines
[i
])
270 def _test_func(self
):
271 """ Command function called by bisect.
272 Returns True on Success, False on failure.
274 # If the bad line is still there, fail.
275 return self
.BAD_LINE
not in self
.lines
277 def test_success(self
):
278 lines
= self
.TEST
.split('\n')
279 (marks
, min, max) = get_marks(lines
)
280 marks
= bisect(lines
, marks
, min, max,
281 lambda l
, m
: self
._update
_func
(l
, m
),
282 lambda: self
._test
_func
())
283 self
.assertTrue(BAD
not in marks
)
285 def test_conflict(self
):
286 lines
= self
.TEST
.split('\n')
287 for pos
in xrange(len(lines
) + 1):
288 lines
= self
.TEST
.split('\n')
289 lines
.insert(pos
, self
.BAD_LINE
)
290 (marks
, min, max) = get_marks(lines
)
292 marks
= bisect(lines
, marks
, min, max,
293 lambda l
, m
: self
._update
_func
(l
, m
),
294 lambda: self
._test
_func
())
295 for i
in xrange(len(marks
)):
297 self
.assertEqual(BAD
, marks
[i
])
299 self
.assertNotEqual(BAD
, marks
[i
])
301 class TestBisectRequired(unittest
.TestCase
):
302 TEST
= """#include <algorithm>
307 REQ_LINE
= "#include <req>"
310 global FIND_CONFLICTS
311 FIND_CONFLICTS
= False
313 def _update_func(self
, lines
, marks
):
315 for i
in xrange(len(marks
)):
318 self
.lines
.append(lines
[i
])
320 self
.lines
.append('//' + lines
[i
])
322 def _test_func(self
):
323 """ Command function called by bisect.
324 Returns True on Success, False on failure.
326 # If the required line is not there, fail.
327 found
= self
.REQ_LINE
in self
.lines
330 def test_success(self
):
331 lines
= self
.TEST
.split('\n')
332 (marks
, min, max) = get_marks(lines
)
333 marks
= bisect(lines
, marks
, min, max,
334 lambda l
, m
: self
._update
_func
(l
, m
),
335 lambda: self
._test
_func
())
336 self
.assertTrue(GOOD
not in marks
)
338 def test_required(self
):
339 lines
= self
.TEST
.split('\n')
340 for pos
in xrange(len(lines
) + 1):
341 lines
= self
.TEST
.split('\n')
342 lines
.insert(pos
, self
.REQ_LINE
)
343 (marks
, min, max) = get_marks(lines
)
345 marks
= bisect(lines
, marks
, min, max,
346 lambda l
, m
: self
._update
_func
(l
, m
),
347 lambda: self
._test
_func
())
348 for i
in xrange(len(marks
)):
350 self
.assertEqual(GOOD
, marks
[i
])
352 self
.assertNotEqual(GOOD
, marks
[i
])
356 # vim: set et sw=4 ts=4 expandtab: