3535
3636def preprocess (source , * ,
3737 incldirs = None ,
38+ includes = None ,
3839 macros = None ,
3940 samefiles = None ,
4041 filename = None ,
42+ cwd = None ,
4143 tool = True ,
4244 ):
4345 """...
4446
4547 CWD should be the project root and "source" should be relative.
4648 """
4749 if tool :
48- logger .debug (f'CWD: { os .getcwd ()!r} ' )
49- logger .debug (f'incldirs: { incldirs !r} ' )
50- logger .debug (f'macros: { macros !r} ' )
50+ if not cwd :
51+ cwd = os .getcwd ()
52+ logger .debug (f'CWD: { cwd !r} ' )
53+ logger .debug (f'incldirs: { incldirs !r} ' )
54+ logger .debug (f'includes: { includes !r} ' )
55+ logger .debug (f'macros: { macros !r} ' )
5156 logger .debug (f'samefiles: { samefiles !r} ' )
5257 _preprocess = _get_preprocessor (tool )
5358 with _good_file (source , filename ) as source :
54- return _preprocess (source , incldirs , macros , samefiles ) or ()
59+ return _preprocess (
60+ source ,
61+ incldirs ,
62+ includes ,
63+ macros ,
64+ samefiles ,
65+ cwd ,
66+ ) or ()
5567 else :
5668 source , filename = _resolve_source (source , filename )
5769 # We ignore "includes", "macros", etc.
58- return _pure .preprocess (source , filename )
70+ return _pure .preprocess (source , filename , cwd )
5971
6072 # if _run() returns just the lines:
6173# text = _run(source)
@@ -72,6 +84,7 @@ def preprocess(source, *,
7284
7385def get_preprocessor (* ,
7486 file_macros = None ,
87+ file_includes = None ,
7588 file_incldirs = None ,
7689 file_same = None ,
7790 ignore_exc = False ,
@@ -80,27 +93,39 @@ def get_preprocessor(*,
8093 _preprocess = preprocess
8194 if file_macros :
8295 file_macros = tuple (_parse_macros (file_macros ))
96+ if file_includes :
97+ file_includes = tuple (_parse_includes (file_includes ))
8398 if file_incldirs :
8499 file_incldirs = tuple (_parse_incldirs (file_incldirs ))
85100 if file_same :
86- file_same = tuple (file_same )
101+ file_same = dict (file_same or () )
87102 if not callable (ignore_exc ):
88103 ignore_exc = (lambda exc , _ig = ignore_exc : _ig )
89104
90105 def get_file_preprocessor (filename ):
91106 filename = filename .strip ()
92107 if file_macros :
93108 macros = list (_resolve_file_values (filename , file_macros ))
109+ if file_includes :
110+ # There's a small chance we could need to filter out any
111+ # includes that import "filename". It isn't clear that it's
112+ # a problem any longer. If we do end up filtering then
113+ # it may make sense to use c_common.fsutil.match_path_tail().
114+ includes = [i for i , in _resolve_file_values (filename , file_includes )]
94115 if file_incldirs :
95116 incldirs = [v for v , in _resolve_file_values (filename , file_incldirs )]
117+ if file_same :
118+ samefiles = _resolve_samefiles (filename , file_same )
96119
97120 def preprocess (** kwargs ):
98121 if file_macros and 'macros' not in kwargs :
99122 kwargs ['macros' ] = macros
123+ if file_includes and 'includes' not in kwargs :
124+ kwargs ['includes' ] = includes
100125 if file_incldirs and 'incldirs' not in kwargs :
101- kwargs ['incldirs' ] = [ v for v , in _resolve_file_values ( filename , file_incldirs )]
102- if file_same and 'file_same ' not in kwargs :
103- kwargs ['samefiles' ] = file_same
126+ kwargs ['incldirs' ] = incldirs
127+ if file_same and 'samefiles ' not in kwargs :
128+ kwargs ['samefiles' ] = samefiles
104129 kwargs .setdefault ('filename' , filename )
105130 with handling_errors (ignore_exc , log_err = log_err ):
106131 return _preprocess (filename , ** kwargs )
@@ -120,6 +145,11 @@ def _parse_macros(macros):
120145 yield row
121146
122147
148+ def _parse_includes (includes ):
149+ for row , srcfile in _parse_table (includes , '\t ' , 'glob\t include' , default = None ):
150+ yield row
151+
152+
123153def _parse_incldirs (incldirs ):
124154 for row , srcfile in _parse_table (incldirs , '\t ' , 'glob\t dirname' , default = None ):
125155 glob , dirname = row
@@ -130,6 +160,43 @@ def _parse_incldirs(incldirs):
130160 yield row
131161
132162
163+ def _resolve_samefiles (filename , file_same ):
164+ assert '*' not in filename , (filename ,)
165+ assert os .path .normpath (filename ) == filename , (filename ,)
166+ _ , suffix = os .path .splitext (filename )
167+ samefiles = []
168+ for patterns , in _resolve_file_values (filename , file_same .items ()):
169+ for pattern in patterns :
170+ same = _resolve_samefile (filename , pattern , suffix )
171+ if not same :
172+ continue
173+ samefiles .append (same )
174+ return samefiles
175+
176+
177+ def _resolve_samefile (filename , pattern , suffix ):
178+ if pattern == filename :
179+ return None
180+ if pattern .endswith (os .path .sep ):
181+ pattern += f'*{ suffix } '
182+ assert os .path .normpath (pattern ) == pattern , (pattern ,)
183+ if '*' in os .path .dirname (pattern ):
184+ raise NotImplementedError ((filename , pattern ))
185+ if '*' not in os .path .basename (pattern ):
186+ return pattern
187+
188+ common = os .path .commonpath ([filename , pattern ])
189+ relpattern = pattern [len (common ) + len (os .path .sep ):]
190+ relpatterndir = os .path .dirname (relpattern )
191+ relfile = filename [len (common ) + len (os .path .sep ):]
192+ if os .path .basename (pattern ) == '*' :
193+ return os .path .join (common , relpatterndir , relfile )
194+ elif os .path .basename (relpattern ) == '*' + suffix :
195+ return os .path .join (common , relpatterndir , relfile )
196+ else :
197+ raise NotImplementedError ((filename , pattern ))
198+
199+
133200@contextlib .contextmanager
134201def handling_errors (ignore_exc = None , * , log_err = None ):
135202 try :
0 commit comments