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,56 @@ 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
206+ # fmt: off
209207 defines = [
210208 "-D" , "__attribute__(x)=" ,
211209 "-D" , "__inline__=inline" ,
212210 "-D" , "__asm__=;#pragma asm" ,
213211 "-D" , "__int64=long long" ,
214- "-D" , "_POSIX_THREADS"
212+ "-D" , "_POSIX_THREADS" ,
215213 ]
216214
217- if os . name == 'nt' :
215+ if sys . platform == "win32" :
218216 defines .extend ([
219217 "-D" , "__inline=inline" ,
220218 "-D" , "__ptr32=" ,
221219 "-D" , "__ptr64=" ,
222220 "-D" , "__declspec(x)=" ,
223221 ])
222+ #fmt: on
224223
225224 if hasattr (sys , "abiflags" ):
226225 if "d" in sys .abiflags :
227226 defines .extend (("-D" , "PYTHON_WITH_PYDEBUG" ))
228227 if "u" in sys .abiflags :
229228 defines .extend (("-D" , "PYTHON_WITH_WIDE_UNICODE" ))
230229
231- python_h = os . path . join ( include_py , "Python.h" )
232- cmd = ["clang" , "-pthread" ] + include_args + defines + ["-E" , python_h ]
230+ python_h = include_py / "Python.h"
231+ cmd = [cc , "-pthread" ] + include_args + defines + ["-E" , str ( python_h ) ]
233232
234233 # normalize as the parser doesn't like windows line endings.
235234 lines = []
@@ -240,16 +239,13 @@ def preprocess_python_headers():
240239 return "\n " .join (lines )
241240
242241
243-
244- def gen_interop_head (writer ):
242+ def gen_interop_head (writer , version , abi_flags ):
245243 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.
244+ class_definition = f"""
245+ // Auto-generated by { filename } .
250246// DO NOT MODIFY BY HAND.
251247
252- // Python %s: ABI flags: '%s '
248+ // Python { "." . join ( version [: 2 ]) } : ABI flags: '{ abi_flags } '
253249
254250// ReSharper disable InconsistentNaming
255251// ReSharper disable IdentifierTypo
@@ -261,7 +257,7 @@ def gen_interop_head(writer):
261257using Python.Runtime.Native;
262258
263259namespace Python.Runtime
264- {""" % ( filename , py_ver , abi_flags )
260+ {{ """
265261 writer .extend (class_definition )
266262
267263
@@ -271,25 +267,24 @@ def gen_interop_tail(writer):
271267 writer .extend (tail )
272268
273269
274- def gen_heap_type_members (parser , writer , type_name = None ):
270+ def gen_heap_type_members (parser , writer , type_name ):
275271 """Generate the TypeOffset C# class"""
276272 members = parser .get_struct_members ("PyHeapTypeObject" )
277- type_name = type_name or "TypeOffset{0}{1}" .format (PY_MAJOR , PY_MINOR )
278- class_definition = """
273+ class_definition = f"""
279274 [SuppressMessage("Style", "IDE1006:Naming Styles",
280275 Justification = "Following CPython",
281276 Scope = "type")]
282277
283278 [StructLayout(LayoutKind.Sequential)]
284- internal class {0 } : GeneratedTypeOffsets, ITypeOffsets
279+ internal class { type_name } : GeneratedTypeOffsets, ITypeOffsets
285280 {{
286- public {0 }() {{ }}
281+ public { type_name } () {{ }}
287282 // Auto-generated from PyHeapTypeObject in Python.h
288- """ . format ( type_name )
283+ """
289284
290285 # All the members are sizeof(void*) so we don't need to do any
291286 # extra work to determine the size based on the type.
292- for name , tpy in members :
287+ for name , _type in members :
293288 name = _typeoffset_member_renames .get (name , name )
294289 class_definition += " public int %s { get; private set; }\n " % name
295290
@@ -304,17 +299,18 @@ def gen_structure_code(parser, writer, type_name, indent):
304299 return False
305300 out = writer .append
306301 out (indent , "[StructLayout(LayoutKind.Sequential)]" )
307- out (indent , "internal struct %s" % type_name )
302+ out (indent , f "internal struct { type_name } " )
308303 out (indent , "{" )
309- for name , tpy in members :
310- out (indent + 1 , "public IntPtr %s;" % name )
304+ for name , _type in members :
305+ out (indent + 1 , f "public IntPtr { name } ;" )
311306 out (indent , "}" )
312307 out ()
313308 return True
314309
315- def main ():
310+
311+ def main (* , cc = None , include_py = None , version = None , out = None ):
316312 # preprocess Python.h and build the AST
317- python_h = preprocess_python_headers ()
313+ python_h = preprocess_python_headers (cc = cc , include_py = include_py )
318314 parser = c_parser .CParser ()
319315 ast = parser .parse (python_h )
320316
@@ -323,21 +319,47 @@ def main():
323319 ast_parser .visit (ast )
324320
325321 writer = Writer ()
322+
323+ if include_py and not version :
324+ raise RuntimeError ("If the include path is overridden, version must be "
325+ "defined"
326+ )
327+
328+ if version :
329+ version = version .split ('.' )
330+ else :
331+ version = sys .version_info
332+
326333 # generate the C# code
327- offsets_type_name = "NativeTypeOffset" if len ( sys . argv ) > 1 else None
328- gen_interop_head (writer )
334+ abi_flags = getattr ( sys , "abiflags" , "" ). replace ( "m" , "" )
335+ gen_interop_head (writer , version , abi_flags )
329336
330- gen_heap_type_members (ast_parser , writer , type_name = offsets_type_name )
337+ type_name = f"TypeOffset{ version [0 ]} { version [1 ]} { abi_flags } "
338+ gen_heap_type_members (ast_parser , writer , type_name )
331339
332340 gen_interop_tail (writer )
333341
334342 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 :
343+ if not out or out == "-" :
339344 print (interop_cs )
345+ else :
346+ with open (out , "w" ) as fh :
347+ fh .write (interop_cs )
340348
341349
342350if __name__ == "__main__" :
343- sys .exit (main ())
351+ import argparse
352+
353+ a = argparse .ArgumentParser ("Interop file generator for Python.NET" )
354+ a .add_argument ("--cc" , help = "C compiler to use, either clang or gcc" )
355+ a .add_argument ("--include-py" , help = "Include path of Python" )
356+ a .add_argument ("--version" , help = "Python version" )
357+ a .add_argument ("--out" , help = "Output path" , default = "-" )
358+ args = a .parse_args ()
359+
360+ sys .exit (main (
361+ cc = args .cc ,
362+ include_py = args .include_py ,
363+ out = args .out ,
364+ version = args .version
365+ ))
0 commit comments