Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c0b4eb2

Browse files
committed
Improve geninterop script to handle new case in 3.11
1 parent 2b52910 commit c0b4eb2

File tree

1 file changed

+83
-61
lines changed

1 file changed

+83
-61
lines changed

tools/geninterop/geninterop.py

100644100755
+83-61
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,26 @@
1313
- clang
1414
"""
1515

16-
from __future__ import print_function
17-
18-
import logging
1916
import os
17+
import shutil
2018
import sys
2119
import sysconfig
2220
import 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
2924
from 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

4434
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")
5036

5137

5238
class 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

181168
class 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):
261257
using Python.Runtime.Native;
262258
263259
namespace 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

342350
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

Comments
 (0)