3
3
"""
4
4
5
5
import io
6
+ import pathlib
6
7
import re
7
8
import os
9
+ import os .path
8
10
import subprocess
9
11
import sys
10
12
import tempfile
@@ -48,6 +50,8 @@ def make_gcc_preprocessor(
48
50
encoding : typing .Optional [str ] = None ,
49
51
gcc_args : typing .List [str ] = ["g++" ],
50
52
print_cmd : bool = True ,
53
+ depfile : typing .Optional [pathlib .Path ] = None ,
54
+ deptarget : typing .Optional [typing .List [str ]] = None ,
51
55
) -> PreprocessorFunction :
52
56
"""
53
57
Creates a preprocessor function that uses g++ to preprocess the input text.
@@ -62,6 +66,9 @@ def make_gcc_preprocessor(
62
66
:param encoding: If specified any include files are opened with this encoding
63
67
:param gcc_args: This is the path to G++ and any extra args you might want
64
68
:param print_cmd: Prints the gcc command as its executed
69
+ :param depfile: If specified, will generate a preprocessor depfile that contains
70
+ a list of include files that were parsed. Must also specify deptarget.
71
+ :param deptarget: List of targets to put in the depfile
65
72
66
73
.. code-block:: python
67
74
@@ -93,6 +100,16 @@ def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
93
100
else :
94
101
cmd .append (filename )
95
102
103
+ if depfile is not None :
104
+ if deptarget is None :
105
+ raise PreprocessorError (
106
+ "must specify deptarget if depfile is specified"
107
+ )
108
+ cmd .append ("-MD" )
109
+ for target in deptarget :
110
+ cmd += ["-MQ" , target ]
111
+ cmd += ["-MF" , str (depfile )]
112
+
96
113
if print_cmd :
97
114
print ("+" , " " .join (cmd ), file = sys .stderr )
98
115
@@ -242,7 +259,9 @@ def on_comment(self, *ignored):
242
259
pcpp = None
243
260
244
261
245
- def _pcpp_filter (fname : str , fp : typing .TextIO ) -> str :
262
+ def _pcpp_filter (
263
+ fname : str , fp : typing .TextIO , deps : typing .Optional [typing .Dict [str , bool ]]
264
+ ) -> str :
246
265
# the output of pcpp includes the contents of all the included files, which
247
266
# isn't what a typical user of cxxheaderparser would want, so we strip out
248
267
# the line directives and any content that isn't in our original file
@@ -255,6 +274,9 @@ def _pcpp_filter(fname: str, fp: typing.TextIO) -> str:
255
274
for line in fp :
256
275
if line .startswith ("#line" ):
257
276
keep = line .endswith (line_ending )
277
+ if deps is not None :
278
+ start = line .find ('"' )
279
+ deps [line [start + 1 : - 2 ]] = True
258
280
259
281
if keep :
260
282
new_output .write (line )
@@ -270,6 +292,8 @@ def make_pcpp_preprocessor(
270
292
retain_all_content : bool = False ,
271
293
encoding : typing .Optional [str ] = None ,
272
294
passthru_includes : typing .Optional ["re.Pattern" ] = None ,
295
+ depfile : typing .Optional [pathlib .Path ] = None ,
296
+ deptarget : typing .Optional [typing .List [str ]] = None ,
273
297
) -> PreprocessorFunction :
274
298
"""
275
299
Creates a preprocessor function that uses pcpp (which must be installed
@@ -285,6 +309,10 @@ def make_pcpp_preprocessor(
285
309
:param encoding: If specified any include files are opened with this encoding
286
310
:param passthru_includes: If specified any #include directives that match the
287
311
compiled regex pattern will be part of the output.
312
+ :param depfile: If specified, will generate a preprocessor depfile that contains
313
+ a list of include files that were parsed. Must also specify deptarget.
314
+ Not compatible with retain_all_content
315
+ :param deptarget: List of targets to put in the depfile
288
316
289
317
.. code-block:: python
290
318
@@ -309,6 +337,8 @@ def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
309
337
310
338
if not retain_all_content :
311
339
pp .line_directive = "#line"
340
+ elif depfile :
341
+ raise PreprocessorError ("retain_all_content and depfile not compatible" )
312
342
313
343
if content is None :
314
344
with open (filename , "r" , encoding = encoding ) as fp :
@@ -327,6 +357,16 @@ def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
327
357
if retain_all_content :
328
358
return fp .read ()
329
359
else :
360
+ deps : typing .Optional [typing .Dict [str , bool ]] = None
361
+ target = None
362
+ if depfile :
363
+ deps = {}
364
+ if not deptarget :
365
+ base , _ = os .path .splitext (filename )
366
+ target = f"{ base } .o"
367
+ else :
368
+ target = " " .join (deptarget )
369
+
330
370
# pcpp emits the #line directive using the filename you pass in
331
371
# but will rewrite it if it's on the include path it uses. This
332
372
# is copied from pcpp:
@@ -339,6 +379,18 @@ def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
339
379
filename = filename .replace (os .sep , "/" )
340
380
break
341
381
342
- return _pcpp_filter (filename , fp )
382
+ filtered = _pcpp_filter (filename , fp , deps )
383
+
384
+ if depfile is not None :
385
+ assert deps is not None
386
+ with open (depfile , "w" ) as fp :
387
+ fp .write (f"{ target } :" )
388
+ for dep in reversed (list (deps .keys ())):
389
+ dep = dep .replace ("\\ " , "\\ \\ " )
390
+ dep = dep .replace (" " , "\\ " )
391
+ fp .write (f" \\ \n { dep } " )
392
+ fp .write ("\n " )
393
+
394
+ return filtered
343
395
344
396
return _preprocess_file
0 commit comments