1313 - clang
1414"""
1515
16- from __future__ import print_function
17-
18- import logging
1916import os
17+ import shutil
2018import sys
2119import sysconfig
2220import subprocess
2321
24- if sys .version_info .major > 2 :
25- from io import StringIO
26- else :
27- from StringIO import StringIO
28-
22+ from io import StringIO
23+ from pathlib import Path
2924from pycparser import c_ast , c_parser
3025
31- _log = logging .getLogger ()
32- logging .basicConfig (level = logging .DEBUG )
33-
34- PY_MAJOR = sys .version_info [0 ]
35- PY_MINOR = sys .version_info [1 ]
36-
3726# rename some members from their C name when generating the C#
3827_typeoffset_member_renames = {
3928 "ht_name" : "name" ,
40- "ht_qualname" : "qualname"
29+ "ht_qualname" : "qualname" ,
30+ "getitem" : "spec_cache_getitem" ,
4131}
4232
4333
4434def _check_output (* args , ** kwargs ):
45- """Check output wrapper for py2/py3 compatibility"""
46- output = subprocess .check_output (* args , ** kwargs )
47- if PY_MAJOR == 2 :
48- return output
49- return output .decode ("ascii" )
35+ return subprocess .check_output (* args , ** kwargs , encoding = "utf8" )
5036
5137
5238class AstParser (object ):
@@ -92,7 +78,7 @@ def visit(self, node):
9278 self .visit_identifier (node )
9379
9480 def visit_ast (self , ast ):
95- for name , node in ast .children ():
81+ for _name , node in ast .children ():
9682 self .visit (node )
9783
9884 def visit_typedef (self , typedef ):
@@ -113,7 +99,7 @@ def visit_struct(self, struct):
11399 self .visit (decl )
114100 self ._struct_members_stack .pop (0 )
115101 self ._struct_stack .pop (0 )
116- elif self ._ptr_decl_depth :
102+ elif self ._ptr_decl_depth or self . _struct_members_stack :
117103 # the struct is empty, but add it as a member to the current
118104 # struct as the current member maybe a pointer to it.
119105 self ._add_struct_member (struct .name )
@@ -141,7 +127,8 @@ def _add_struct_member(self, type_name):
141127 current_struct = self ._struct_stack [0 ]
142128 member_name = self ._struct_members_stack [0 ]
143129 struct_members = self ._struct_members .setdefault (
144- self ._get_struct_name (current_struct ), [])
130+ self ._get_struct_name (current_struct ), []
131+ )
145132
146133 # get the node associated with this type
147134 node = None
@@ -179,7 +166,6 @@ def _get_struct_name(self, node):
179166
180167
181168class Writer (object ):
182-
183169 def __init__ (self ):
184170 self ._stream = StringIO ()
185171
@@ -193,43 +179,65 @@ def to_string(self):
193179 return self ._stream .getvalue ()
194180
195181
196- def preprocess_python_headers ():
182+ def preprocess_python_headers (* , cc = None , include_py = None ):
197183 """Return Python.h pre-processed, ready for parsing.
198184 Requires clang.
199185 """
200- fake_libc_include = os .path .join (os .path .dirname (__file__ ),
201- "fake_libc_include" )
186+ this_path = Path (__file__ ).parent
187+
188+ fake_libc_include = this_path / "fake_libc_include"
202189 include_dirs = [fake_libc_include ]
203190
204- include_py = sysconfig .get_config_var ("INCLUDEPY" )
191+ if cc is None :
192+ cc = shutil .which ("clang" )
193+ if cc is None :
194+ cc = shutil .which ("gcc" )
195+ if cc is None :
196+ raise RuntimeError ("No suitable C compiler found, need clang or gcc" )
197+
198+ if include_py is None :
199+ include_py = sysconfig .get_config_var ("INCLUDEPY" )
200+ include_py = Path (include_py )
201+
205202 include_dirs .append (include_py )
206203
207- include_args = [c for p in include_dirs for c in ["-I" , p ]]
204+ include_args = [c for p in include_dirs for c in ["-I" , str ( p ) ]]
208205
209206 defines = [
210- "-D" , "__attribute__(x)=" ,
211- "-D" , "__inline__=inline" ,
212- "-D" , "__asm__=;#pragma asm" ,
213- "-D" , "__int64=long long" ,
214- "-D" , "_POSIX_THREADS"
207+ "-D" ,
208+ "__attribute__(x)=" ,
209+ "-D" ,
210+ "__inline__=inline" ,
211+ "-D" ,
212+ "__asm__=;#pragma asm" ,
213+ "-D" ,
214+ "__int64=long long" ,
215+ "-D" ,
216+ "_POSIX_THREADS" ,
215217 ]
216218
217- if os .name == 'nt' :
218- defines .extend ([
219- "-D" , "__inline=inline" ,
220- "-D" , "__ptr32=" ,
221- "-D" , "__ptr64=" ,
222- "-D" , "__declspec(x)=" ,
223- ])
219+ if os .name == "nt" :
220+ defines .extend (
221+ [
222+ "-D" ,
223+ "__inline=inline" ,
224+ "-D" ,
225+ "__ptr32=" ,
226+ "-D" ,
227+ "__ptr64=" ,
228+ "-D" ,
229+ "__declspec(x)=" ,
230+ ]
231+ )
224232
225233 if hasattr (sys , "abiflags" ):
226234 if "d" in sys .abiflags :
227235 defines .extend (("-D" , "PYTHON_WITH_PYDEBUG" ))
228236 if "u" in sys .abiflags :
229237 defines .extend (("-D" , "PYTHON_WITH_WIDE_UNICODE" ))
230238
231- python_h = os . path . join ( include_py , "Python.h" )
232- cmd = ["clang" , "-pthread" ] + include_args + defines + ["-E" , python_h ]
239+ python_h = include_py / "Python.h"
240+ cmd = [cc , "-pthread" ] + include_args + defines + ["-E" , str ( python_h ) ]
233241
234242 # normalize as the parser doesn't like windows line endings.
235243 lines = []
@@ -240,16 +248,13 @@ def preprocess_python_headers():
240248 return "\n " .join (lines )
241249
242250
243-
244- def gen_interop_head (writer ):
251+ def gen_interop_head (writer , version , abi_flags ):
245252 filename = os .path .basename (__file__ )
246- abi_flags = getattr (sys , "abiflags" , "" ).replace ("m" , "" )
247- py_ver = "{0}.{1}" .format (PY_MAJOR , PY_MINOR )
248- class_definition = """
249- // Auto-generated by %s.
253+ class_definition = f"""
254+ // Auto-generated by { filename } .
250255// DO NOT MODIFY BY HAND.
251256
252- // Python %s: ABI flags: '%s '
257+ // Python { "." . join ( version [: 2 ]) } : ABI flags: '{ abi_flags } '
253258
254259// ReSharper disable InconsistentNaming
255260// ReSharper disable IdentifierTypo
@@ -261,7 +266,7 @@ def gen_interop_head(writer):
261266using Python.Runtime.Native;
262267
263268namespace Python.Runtime
264- {""" % ( filename , py_ver , abi_flags )
269+ {{ """
265270 writer .extend (class_definition )
266271
267272
@@ -271,25 +276,24 @@ def gen_interop_tail(writer):
271276 writer .extend (tail )
272277
273278
274- def gen_heap_type_members (parser , writer , type_name = None ):
279+ def gen_heap_type_members (parser , writer , type_name ):
275280 """Generate the TypeOffset C# class"""
276281 members = parser .get_struct_members ("PyHeapTypeObject" )
277- type_name = type_name or "TypeOffset{0}{1}" .format (PY_MAJOR , PY_MINOR )
278- class_definition = """
282+ class_definition = f"""
279283 [SuppressMessage("Style", "IDE1006:Naming Styles",
280284 Justification = "Following CPython",
281285 Scope = "type")]
282286
283287 [StructLayout(LayoutKind.Sequential)]
284- internal class {0 } : GeneratedTypeOffsets, ITypeOffsets
288+ internal class { type_name } : GeneratedTypeOffsets, ITypeOffsets
285289 {{
286- public {0 }() {{ }}
290+ public { type_name } () {{ }}
287291 // Auto-generated from PyHeapTypeObject in Python.h
288- """ . format ( type_name )
292+ """
289293
290294 # All the members are sizeof(void*) so we don't need to do any
291295 # extra work to determine the size based on the type.
292- for name , tpy in members :
296+ for name , _type in members :
293297 name = _typeoffset_member_renames .get (name , name )
294298 class_definition += " public int %s { get; private set; }\n " % name
295299
@@ -304,17 +308,18 @@ def gen_structure_code(parser, writer, type_name, indent):
304308 return False
305309 out = writer .append
306310 out (indent , "[StructLayout(LayoutKind.Sequential)]" )
307- out (indent , "internal struct %s" % type_name )
311+ out (indent , f "internal struct { type_name } " )
308312 out (indent , "{" )
309- for name , tpy in members :
310- out (indent + 1 , "public IntPtr %s;" % name )
313+ for name , _type in members :
314+ out (indent + 1 , f "public IntPtr { name } ;" )
311315 out (indent , "}" )
312316 out ()
313317 return True
314318
315- def main ():
319+
320+ def main (* , cc = None , include_py = None , version = None , out = None ):
316321 # preprocess Python.h and build the AST
317- python_h = preprocess_python_headers ()
322+ python_h = preprocess_python_headers (cc = cc , include_py = include_py )
318323 parser = c_parser .CParser ()
319324 ast = parser .parse (python_h )
320325
@@ -323,21 +328,47 @@ def main():
323328 ast_parser .visit (ast )
324329
325330 writer = Writer ()
331+
332+ if include_py and not version :
333+ raise RuntimeError ("If the include path is overridden, version must be "
334+ "defined"
335+ )
336+
337+ if version :
338+ version = version .split ('.' )
339+ else :
340+ version = sys .version_info
341+
326342 # generate the C# code
327- offsets_type_name = "NativeTypeOffset" if len ( sys . argv ) > 1 else None
328- gen_interop_head (writer )
343+ abi_flags = getattr ( sys , "abiflags" , "" ). replace ( "m" , "" )
344+ gen_interop_head (writer , version , abi_flags )
329345
330- gen_heap_type_members (ast_parser , writer , type_name = offsets_type_name )
346+ type_name = f"TypeOffset{ version [0 ]} { version [1 ]} { abi_flags } "
347+ gen_heap_type_members (ast_parser , writer , type_name )
331348
332349 gen_interop_tail (writer )
333350
334351 interop_cs = writer .to_string ()
335- if len (sys .argv ) > 1 :
336- with open (sys .argv [1 ], "w" ) as fh :
337- fh .write (interop_cs )
338- else :
352+ if not out or out == "-" :
339353 print (interop_cs )
354+ else :
355+ with open (out , "w" ) as fh :
356+ fh .write (interop_cs )
340357
341358
342359if __name__ == "__main__" :
343- sys .exit (main ())
360+ import argparse
361+
362+ a = argparse .ArgumentParser ("Interop file generator for Python.NET" )
363+ a .add_argument ("--cc" , help = "C compiler to use, either clang or gcc" )
364+ a .add_argument ("--include-py" , help = "Include path of Python" )
365+ a .add_argument ("--version" , help = "Python version" )
366+ a .add_argument ("--out" , help = "Output path" , default = "-" )
367+ args = a .parse_args ()
368+
369+ sys .exit (main (
370+ cc = args .cc ,
371+ include_py = args .include_py ,
372+ out = args .out ,
373+ version = args .version
374+ ))
0 commit comments