| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,40 +13,26 @@ | |||
| 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 | + )) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments