13
13
- clang
14
14
"""
15
15
16
- from __future__ import print_function
17
-
18
- import logging
19
16
import os
17
+ import shutil
20
18
import sys
21
19
import sysconfig
22
20
import subprocess
23
21
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
29
24
from pycparser import c_ast , c_parser
30
25
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
-
37
26
# rename some members from their C name when generating the C#
38
27
_typeoffset_member_renames = {
39
28
"ht_name" : "name" ,
40
- "ht_qualname" : "qualname"
29
+ "ht_qualname" : "qualname" ,
30
+ "getitem" : "spec_cache_getitem" ,
41
31
}
42
32
43
33
44
34
def _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" )
50
36
51
37
52
38
class AstParser (object ):
@@ -92,7 +78,7 @@ def visit(self, node):
92
78
self .visit_identifier (node )
93
79
94
80
def visit_ast (self , ast ):
95
- for name , node in ast .children ():
81
+ for _name , node in ast .children ():
96
82
self .visit (node )
97
83
98
84
def visit_typedef (self , typedef ):
@@ -113,7 +99,7 @@ def visit_struct(self, struct):
113
99
self .visit (decl )
114
100
self ._struct_members_stack .pop (0 )
115
101
self ._struct_stack .pop (0 )
116
- elif self ._ptr_decl_depth :
102
+ elif self ._ptr_decl_depth or self . _struct_members_stack :
117
103
# the struct is empty, but add it as a member to the current
118
104
# struct as the current member maybe a pointer to it.
119
105
self ._add_struct_member (struct .name )
@@ -141,7 +127,8 @@ def _add_struct_member(self, type_name):
141
127
current_struct = self ._struct_stack [0 ]
142
128
member_name = self ._struct_members_stack [0 ]
143
129
struct_members = self ._struct_members .setdefault (
144
- self ._get_struct_name (current_struct ), [])
130
+ self ._get_struct_name (current_struct ), []
131
+ )
145
132
146
133
# get the node associated with this type
147
134
node = None
@@ -179,7 +166,6 @@ def _get_struct_name(self, node):
179
166
180
167
181
168
class Writer (object ):
182
-
183
169
def __init__ (self ):
184
170
self ._stream = StringIO ()
185
171
@@ -193,43 +179,56 @@ def to_string(self):
193
179
return self ._stream .getvalue ()
194
180
195
181
196
- def preprocess_python_headers ():
182
+ def preprocess_python_headers (* , cc = None , include_py = None ):
197
183
"""Return Python.h pre-processed, ready for parsing.
198
184
Requires clang.
199
185
"""
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"
202
189
include_dirs = [fake_libc_include ]
203
190
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
+
205
202
include_dirs .append (include_py )
206
203
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 ) ]]
208
205
206
+ # fmt: off
209
207
defines = [
210
208
"-D" , "__attribute__(x)=" ,
211
209
"-D" , "__inline__=inline" ,
212
210
"-D" , "__asm__=;#pragma asm" ,
213
211
"-D" , "__int64=long long" ,
214
- "-D" , "_POSIX_THREADS"
212
+ "-D" , "_POSIX_THREADS" ,
215
213
]
216
214
217
- if os . name == 'nt' :
215
+ if sys . platform == "win32" :
218
216
defines .extend ([
219
217
"-D" , "__inline=inline" ,
220
218
"-D" , "__ptr32=" ,
221
219
"-D" , "__ptr64=" ,
222
220
"-D" , "__declspec(x)=" ,
223
221
])
222
+ #fmt: on
224
223
225
224
if hasattr (sys , "abiflags" ):
226
225
if "d" in sys .abiflags :
227
226
defines .extend (("-D" , "PYTHON_WITH_PYDEBUG" ))
228
227
if "u" in sys .abiflags :
229
228
defines .extend (("-D" , "PYTHON_WITH_WIDE_UNICODE" ))
230
229
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 ) ]
233
232
234
233
# normalize as the parser doesn't like windows line endings.
235
234
lines = []
@@ -240,16 +239,13 @@ def preprocess_python_headers():
240
239
return "\n " .join (lines )
241
240
242
241
243
-
244
- def gen_interop_head (writer ):
242
+ def gen_interop_head (writer , version , abi_flags ):
245
243
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 } .
250
246
// DO NOT MODIFY BY HAND.
251
247
252
- // Python %s: ABI flags: '%s '
248
+ // Python { "." . join ( version [: 2 ]) } : ABI flags: '{ abi_flags } '
253
249
254
250
// ReSharper disable InconsistentNaming
255
251
// ReSharper disable IdentifierTypo
@@ -261,7 +257,7 @@ def gen_interop_head(writer):
261
257
using Python.Runtime.Native;
262
258
263
259
namespace Python.Runtime
264
- {""" % ( filename , py_ver , abi_flags )
260
+ {{ """
265
261
writer .extend (class_definition )
266
262
267
263
@@ -271,25 +267,24 @@ def gen_interop_tail(writer):
271
267
writer .extend (tail )
272
268
273
269
274
- def gen_heap_type_members (parser , writer , type_name = None ):
270
+ def gen_heap_type_members (parser , writer , type_name ):
275
271
"""Generate the TypeOffset C# class"""
276
272
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"""
279
274
[SuppressMessage("Style", "IDE1006:Naming Styles",
280
275
Justification = "Following CPython",
281
276
Scope = "type")]
282
277
283
278
[StructLayout(LayoutKind.Sequential)]
284
- internal class {0 } : GeneratedTypeOffsets, ITypeOffsets
279
+ internal class { type_name } : GeneratedTypeOffsets, ITypeOffsets
285
280
{{
286
- public {0 }() {{ }}
281
+ public { type_name } () {{ }}
287
282
// Auto-generated from PyHeapTypeObject in Python.h
288
- """ . format ( type_name )
283
+ """
289
284
290
285
# All the members are sizeof(void*) so we don't need to do any
291
286
# extra work to determine the size based on the type.
292
- for name , tpy in members :
287
+ for name , _type in members :
293
288
name = _typeoffset_member_renames .get (name , name )
294
289
class_definition += " public int %s { get; private set; }\n " % name
295
290
@@ -304,17 +299,18 @@ def gen_structure_code(parser, writer, type_name, indent):
304
299
return False
305
300
out = writer .append
306
301
out (indent , "[StructLayout(LayoutKind.Sequential)]" )
307
- out (indent , "internal struct %s" % type_name )
302
+ out (indent , f "internal struct { type_name } " )
308
303
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 } ;" )
311
306
out (indent , "}" )
312
307
out ()
313
308
return True
314
309
315
- def main ():
310
+
311
+ def main (* , cc = None , include_py = None , version = None , out = None ):
316
312
# 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 )
318
314
parser = c_parser .CParser ()
319
315
ast = parser .parse (python_h )
320
316
@@ -323,21 +319,47 @@ def main():
323
319
ast_parser .visit (ast )
324
320
325
321
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
+
326
333
# 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 )
329
336
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 )
331
339
332
340
gen_interop_tail (writer )
333
341
334
342
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 == "-" :
339
344
print (interop_cs )
345
+ else :
346
+ with open (out , "w" ) as fh :
347
+ fh .write (interop_cs )
340
348
341
349
342
350
if __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