33"""
44
55import io
6+ import pathlib
67import re
78import os
9+ import os .path
810import subprocess
911import sys
1012import tempfile
@@ -48,6 +50,8 @@ def make_gcc_preprocessor(
4850 encoding : typing .Optional [str ] = None ,
4951 gcc_args : typing .List [str ] = ["g++" ],
5052 print_cmd : bool = True ,
53+ depfile : typing .Optional [pathlib .Path ] = None ,
54+ deptarget : typing .Optional [typing .List [str ]] = None ,
5155) -> PreprocessorFunction :
5256 """
5357 Creates a preprocessor function that uses g++ to preprocess the input text.
@@ -62,6 +66,9 @@ def make_gcc_preprocessor(
6266 :param encoding: If specified any include files are opened with this encoding
6367 :param gcc_args: This is the path to G++ and any extra args you might want
6468 :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
6572
6673 .. code-block:: python
6774
@@ -93,6 +100,16 @@ def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
93100 else :
94101 cmd .append (filename )
95102
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+
96113 if print_cmd :
97114 print ("+" , " " .join (cmd ), file = sys .stderr )
98115
@@ -242,7 +259,9 @@ def on_comment(self, *ignored):
242259 pcpp = None
243260
244261
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 :
246265 # the output of pcpp includes the contents of all the included files, which
247266 # isn't what a typical user of cxxheaderparser would want, so we strip out
248267 # 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:
255274 for line in fp :
256275 if line .startswith ("#line" ):
257276 keep = line .endswith (line_ending )
277+ if deps is not None :
278+ start = line .find ('"' )
279+ deps [line [start + 1 : - 2 ]] = True
258280
259281 if keep :
260282 new_output .write (line )
@@ -270,6 +292,8 @@ def make_pcpp_preprocessor(
270292 retain_all_content : bool = False ,
271293 encoding : typing .Optional [str ] = None ,
272294 passthru_includes : typing .Optional ["re.Pattern" ] = None ,
295+ depfile : typing .Optional [pathlib .Path ] = None ,
296+ deptarget : typing .Optional [typing .List [str ]] = None ,
273297) -> PreprocessorFunction :
274298 """
275299 Creates a preprocessor function that uses pcpp (which must be installed
@@ -285,6 +309,10 @@ def make_pcpp_preprocessor(
285309 :param encoding: If specified any include files are opened with this encoding
286310 :param passthru_includes: If specified any #include directives that match the
287311 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
288316
289317 .. code-block:: python
290318
@@ -309,6 +337,8 @@ def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
309337
310338 if not retain_all_content :
311339 pp .line_directive = "#line"
340+ elif depfile :
341+ raise PreprocessorError ("retain_all_content and depfile not compatible" )
312342
313343 if content is None :
314344 with open (filename , "r" , encoding = encoding ) as fp :
@@ -327,6 +357,16 @@ def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
327357 if retain_all_content :
328358 return fp .read ()
329359 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+
330370 # pcpp emits the #line directive using the filename you pass in
331371 # but will rewrite it if it's on the include path it uses. This
332372 # is copied from pcpp:
@@ -339,6 +379,18 @@ def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
339379 filename = filename .replace (os .sep , "/" )
340380 break
341381
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
343395
344396 return _preprocess_file
0 commit comments