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
Lines changed: 83 additions & 61 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy