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,65 @@ 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
209
206
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" ,
215
217
]
216
218
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
+ )
224
232
225
233
if hasattr (sys , "abiflags" ):
226
234
if "d" in sys .abiflags :
227
235
defines .extend (("-D" , "PYTHON_WITH_PYDEBUG" ))
228
236
if "u" in sys .abiflags :
229
237
defines .extend (("-D" , "PYTHON_WITH_WIDE_UNICODE" ))
230
238
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 ) ]
233
241
234
242
# normalize as the parser doesn't like windows line endings.
235
243
lines = []
@@ -240,16 +248,13 @@ def preprocess_python_headers():
240
248
return "\n " .join (lines )
241
249
242
250
243
-
244
- def gen_interop_head (writer ):
251
+ def gen_interop_head (writer , version , abi_flags ):
245
252
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 } .
250
255
// DO NOT MODIFY BY HAND.
251
256
252
- // Python %s: ABI flags: '%s '
257
+ // Python { "." . join ( version [: 2 ]) } : ABI flags: '{ abi_flags } '
253
258
254
259
// ReSharper disable InconsistentNaming
255
260
// ReSharper disable IdentifierTypo
@@ -261,7 +266,7 @@ def gen_interop_head(writer):
261
266
using Python.Runtime.Native;
262
267
263
268
namespace Python.Runtime
264
- {""" % ( filename , py_ver , abi_flags )
269
+ {{ """
265
270
writer .extend (class_definition )
266
271
267
272
@@ -271,25 +276,24 @@ def gen_interop_tail(writer):
271
276
writer .extend (tail )
272
277
273
278
274
- def gen_heap_type_members (parser , writer , type_name = None ):
279
+ def gen_heap_type_members (parser , writer , type_name ):
275
280
"""Generate the TypeOffset C# class"""
276
281
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"""
279
283
[SuppressMessage("Style", "IDE1006:Naming Styles",
280
284
Justification = "Following CPython",
281
285
Scope = "type")]
282
286
283
287
[StructLayout(LayoutKind.Sequential)]
284
- internal class {0 } : GeneratedTypeOffsets, ITypeOffsets
288
+ internal class { type_name } : GeneratedTypeOffsets, ITypeOffsets
285
289
{{
286
- public {0 }() {{ }}
290
+ public { type_name } () {{ }}
287
291
// Auto-generated from PyHeapTypeObject in Python.h
288
- """ . format ( type_name )
292
+ """
289
293
290
294
# All the members are sizeof(void*) so we don't need to do any
291
295
# extra work to determine the size based on the type.
292
- for name , tpy in members :
296
+ for name , _type in members :
293
297
name = _typeoffset_member_renames .get (name , name )
294
298
class_definition += " public int %s { get; private set; }\n " % name
295
299
@@ -304,17 +308,18 @@ def gen_structure_code(parser, writer, type_name, indent):
304
308
return False
305
309
out = writer .append
306
310
out (indent , "[StructLayout(LayoutKind.Sequential)]" )
307
- out (indent , "internal struct %s" % type_name )
311
+ out (indent , f "internal struct { type_name } " )
308
312
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 } ;" )
311
315
out (indent , "}" )
312
316
out ()
313
317
return True
314
318
315
- def main ():
319
+
320
+ def main (* , cc = None , include_py = None , version = None , out = None ):
316
321
# 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 )
318
323
parser = c_parser .CParser ()
319
324
ast = parser .parse (python_h )
320
325
@@ -323,21 +328,47 @@ def main():
323
328
ast_parser .visit (ast )
324
329
325
330
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
+
326
342
# 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 )
329
345
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 )
331
348
332
349
gen_interop_tail (writer )
333
350
334
351
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 == "-" :
339
353
print (interop_cs )
354
+ else :
355
+ with open (out , "w" ) as fh :
356
+ fh .write (interop_cs )
340
357
341
358
342
359
if __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