FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
pythonnet/tools/geninterop/geninterop.py at master · pythonnet/pythonnet · GitHub
pythonnet
pythonnet
Repository navigation
Code
Issues
147
(147)
Pull requests
11
(11)
Discussions
Actions
Projects
Wiki
Security and quality
Insights
Expand file tree
Breadcrumbs
pythonnet
/
tools
/
geninterop
/
geninterop.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
executable file
·
378 lines (304 loc) · 11.4 KB
Breadcrumbs
pythonnet
/
tools
/
geninterop
/
geninterop.py
Copy path
File metadata and controls
executable file
·
378 lines (304 loc) · 11.4 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/usr/bin/env uv run
# /// script
# dependencies = ["pycparser"]
# ///
"""
TypeOffset is a C# class that mirrors the in-memory layout of heap
allocated Python objects.
This script parses the Python C headers and outputs the TypeOffset
C# class.
Requirements:
- pycparser
- clang
"""
import
os
import
shutil
import
sys
import
sysconfig
import
subprocess
from
io
import
StringIO
from
pathlib
import
Path
from
pycparser
import
c_ast
,
c_parser
# rename some members from their C name when generating the C#
_typeoffset_member_renames
=
{
"ht_name"
:
"name"
,
"ht_qualname"
:
"qualname"
,
"getitem"
:
"spec_cache_getitem"
,
}
def
_check_output
(
*
args
,
**
kwargs
):
return
subprocess
.
check_output
(
*
args
,
**
kwargs
,
encoding
=
"utf8"
)
class
AstParser
(
object
):
"""Walk an AST and determine the members of all structs"""
def
__init__
(
self
):
self
.
_typedefs
=
{}
self
.
_typedecls
=
{}
self
.
_structs
=
{}
self
.
_struct_stack
=
[]
self
.
_struct_members_stack
=
[]
self
.
_ptr_decl_depth
=
0
self
.
_struct_members
=
{}
self
.
_decl_names
=
{}
def
get_struct_members
(
self
,
name
):
"""return a list of (name, type) of struct members"""
defs
=
self
.
_typedefs
.
get
(
name
)
if
defs
is
None
:
return
None
node
=
self
.
_get_leaf_node
(
defs
)
name
=
node
.
name
if
name
is
None
:
name
=
defs
.
declname
return
self
.
_struct_members
.
get
(
name
)
def
visit
(
self
,
node
):
if
isinstance
(
node
,
c_ast
.
FileAST
):
self
.
visit_ast
(
node
)
elif
isinstance
(
node
,
c_ast
.
Typedef
):
self
.
visit_typedef
(
node
)
elif
isinstance
(
node
,
c_ast
.
TypeDecl
):
self
.
visit_typedecl
(
node
)
elif
isinstance
(
node
,
c_ast
.
Struct
):
self
.
visit_struct
(
node
)
elif
isinstance
(
node
,
c_ast
.
Decl
):
self
.
visit_decl
(
node
)
elif
isinstance
(
node
,
c_ast
.
FuncDecl
):
self
.
visit_funcdecl
(
node
)
elif
isinstance
(
node
,
c_ast
.
PtrDecl
):
self
.
visit_ptrdecl
(
node
)
elif
isinstance
(
node
,
c_ast
.
IdentifierType
):
self
.
visit_identifier
(
node
)
elif
isinstance
(
node
,
c_ast
.
Union
):
self
.
visit_union
(
node
)
def
visit_ast
(
self
,
ast
):
for
_name
,
node
in
ast
.
children
():
self
.
visit
(
node
)
def
visit_typedef
(
self
,
typedef
):
self
.
_typedefs
[
typedef
.
name
]
=
typedef
.
type
self
.
visit
(
typedef
.
type
)
def
visit_typedecl
(
self
,
typedecl
):
self
.
_decl_names
[
typedecl
.
type
]
=
typedecl
.
declname
self
.
visit
(
typedecl
.
type
)
def
visit_struct
(
self
,
struct
):
if
struct
.
decls
:
self
.
_structs
[
self
.
_get_struct_name
(
struct
)]
=
struct
# recurse into the struct
self
.
_struct_stack
.
insert
(
0
,
struct
)
for
decl
in
struct
.
decls
:
self
.
_struct_members_stack
.
insert
(
0
,
decl
.
name
)
self
.
visit
(
decl
)
self
.
_struct_members_stack
.
pop
(
0
)
self
.
_struct_stack
.
pop
(
0
)
elif
self
.
_ptr_decl_depth
or
self
.
_struct_members_stack
:
# the struct is empty, but add it as a member to the current
# struct as the current member maybe a pointer to it.
self
.
_add_struct_member
(
struct
.
name
)
def
visit_decl
(
self
,
decl
):
self
.
visit
(
decl
.
type
)
def
visit_funcdecl
(
self
,
funcdecl
):
self
.
visit
(
funcdecl
.
type
)
def
visit_ptrdecl
(
self
,
ptrdecl
):
self
.
_ptr_decl_depth
+=
1
self
.
visit
(
ptrdecl
.
type
)
self
.
_ptr_decl_depth
-=
1
def
visit_identifier
(
self
,
identifier
):
type_name
=
" "
.
join
(
identifier
.
names
)
self
.
_add_struct_member
(
type_name
)
def
visit_union
(
self
,
union
):
# Treat the field as if it was just the first declaration for now. This
# is not really correct, but handles the one case that is relevant for
# us right now (ob_refcnt being "split" in Python 3.12)
if
self
.
_struct_members_stack
and
union
.
decls
:
decl
=
union
.
decls
[
0
]
self
.
_struct_members_stack
.
pop
(
0
)
self
.
_struct_members_stack
.
insert
(
0
,
decl
.
name
)
self
.
visit
(
decl
)
def
_add_struct_member
(
self
,
type_name
):
if
not
(
self
.
_struct_stack
and
self
.
_struct_members_stack
):
return
# add member to current struct
current_struct
=
self
.
_struct_stack
[
0
]
member_name
=
self
.
_struct_members_stack
[
0
]
struct_members
=
self
.
_struct_members
.
setdefault
(
self
.
_get_struct_name
(
current_struct
), []
)
# get the node associated with this type
node
=
None
if
type_name
in
self
.
_typedefs
:
node
=
self
.
_get_leaf_node
(
self
.
_typedefs
[
type_name
])
# If the struct was only declared when the typedef was created, its member
# information will not have been recorded and we have to look it up in the
# structs
if
isinstance
(
node
,
c_ast
.
Struct
)
and
node
.
decls
is
None
:
if
node
.
name
in
self
.
_structs
:
node
=
self
.
_structs
[
node
.
name
]
elif
type_name
in
self
.
_structs
:
node
=
self
.
_structs
[
type_name
]
# If it's a struct (and not a pointer to a struct) expand
# it into the current struct definition
if
not
self
.
_ptr_decl_depth
and
isinstance
(
node
,
c_ast
.
Struct
):
for
decl
in
node
.
decls
or
[]:
self
.
_struct_members_stack
.
insert
(
0
,
decl
.
name
)
self
.
visit
(
decl
)
self
.
_struct_members_stack
.
pop
(
0
)
else
:
# otherwise add it as a single member
struct_members
.
append
((
member_name
,
type_name
))
def
_get_leaf_node
(
self
,
node
):
if
isinstance
(
node
,
c_ast
.
Typedef
):
return
self
.
_get_leaf_node
(
node
.
type
)
if
isinstance
(
node
,
c_ast
.
TypeDecl
):
return
self
.
_get_leaf_node
(
node
.
type
)
return
node
def
_get_struct_name
(
self
,
node
):
return
node
.
name
or
self
.
_decl_names
.
get
(
node
)
or
"_struct_%d"
%
id
(
node
)
class
Writer
(
object
):
def
__init__
(
self
):
self
.
_stream
=
StringIO
()
def
append
(
self
,
indent
=
0
,
code
=
""
):
self
.
_stream
.
write
(
"%s%s
\n
"
%
(
indent
*
" "
,
code
))
def
extend
(
self
,
s
):
self
.
_stream
.
write
(
s
)
def
to_string
(
self
):
return
self
.
_stream
.
getvalue
()
def
preprocess_python_headers
(
*
,
cc
=
None
,
include_py
=
None
):
"""Return Python.h pre-processed, ready for parsing.
Requires clang.
"""
this_path
=
Path
(
__file__
).
parent
fake_libc_include
=
this_path
/
"fake_libc_include"
include_dirs
=
[
fake_libc_include
]
if
cc
is
None
:
cc
=
shutil
.
which
(
"clang"
)
if
cc
is
None
:
cc
=
shutil
.
which
(
"gcc"
)
if
cc
is
None
:
raise
RuntimeError
(
"No suitable C compiler found, need clang or gcc"
)
if
include_py
is
None
:
include_py
=
sysconfig
.
get_config_var
(
"INCLUDEPY"
)
include_py
=
Path
(
include_py
)
include_dirs
.
append
(
include_py
)
include_args
=
[
c
for
p
in
include_dirs
for
c
in
[
"-I"
,
str
(
p
)]]
# fmt: off
defines
=
[
"-D"
,
"__attribute__(x)="
,
"-D"
,
"__inline__=inline"
,
"-D"
,
"__asm__=;#pragma asm"
,
"-D"
,
"__int64=long long"
,
"-D"
,
"_POSIX_THREADS"
,
]
if
sys
.
platform
==
"win32"
:
defines
.
extend
([
"-D"
,
"__inline=inline"
,
"-D"
,
"__ptr32="
,
"-D"
,
"__ptr64="
,
"-D"
,
"__declspec(x)="
,
])
#fmt: on
if
hasattr
(
sys
,
"abiflags"
):
if
"d"
in
sys
.
abiflags
:
defines
.
extend
((
"-D"
,
"PYTHON_WITH_PYDEBUG"
))
if
"u"
in
sys
.
abiflags
:
defines
.
extend
((
"-D"
,
"PYTHON_WITH_WIDE_UNICODE"
))
python_h
=
include_py
/
"Python.h"
cmd
=
[
cc
,
"-pthread"
]
+
include_args
+
defines
+
[
"-E"
,
str
(
python_h
)]
# normalize as the parser doesn't like windows line endings.
lines
=
[]
for
line
in
_check_output
(
cmd
).
splitlines
():
if
line
.
startswith
(
"#"
):
line
=
line
.
replace
(
"
\\
"
,
"/"
)
lines
.
append
(
line
)
return
"
\n
"
.
join
(
lines
)
def
gen_interop_head
(
writer
,
version
,
abi_flags
):
filename
=
os
.
path
.
basename
(
__file__
)
class_definition
=
f"""
// Auto-generated by
{
filename
}
.
// DO NOT MODIFY BY HAND.
// Python
{
"."
.
join
(
map
(
str
,
version
[:
2
]))
}
: ABI flags: '
{
abi_flags
}
'
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Python.Runtime.Native;
namespace Python.Runtime
{{"""
writer
.
extend
(
class_definition
)
def
gen_interop_tail
(
writer
):
tail
=
"""}
"""
writer
.
extend
(
tail
)
def
gen_heap_type_members
(
parser
,
writer
,
type_name
):
"""Generate the TypeOffset C# class"""
members
=
parser
.
get_struct_members
(
"PyHeapTypeObject"
)
class_definition
=
f"""
[SuppressMessage("Style", "IDE1006:Naming Styles",
Justification = "Following CPython",
Scope = "type")]
[StructLayout(LayoutKind.Sequential)]
internal class
{
type_name
}
: GeneratedTypeOffsets, ITypeOffsets
{{
public
{
type_name
}
() {{ }}
// Auto-generated from PyHeapTypeObject in Python.h
"""
# All the members are sizeof(void*) so we don't need to do any
# extra work to determine the size based on the type.
for
name
,
_type
in
members
:
name
=
_typeoffset_member_renames
.
get
(
name
,
name
)
class_definition
+=
" public int %s { get; private set; }
\n
"
%
name
class_definition
+=
""" }
"""
writer
.
extend
(
class_definition
)
def
gen_structure_code
(
parser
,
writer
,
type_name
,
indent
):
members
=
parser
.
get_struct_members
(
type_name
)
if
members
is
None
:
return
False
out
=
writer
.
append
out
(
indent
,
"[StructLayout(LayoutKind.Sequential)]"
)
out
(
indent
,
f"internal struct
{
type_name
}
"
)
out
(
indent
,
"{"
)
for
name
,
_type
in
members
:
out
(
indent
+
1
,
f"public IntPtr
{
name
}
;"
)
out
(
indent
,
"}"
)
out
()
return
True
def
main
(
*
,
cc
=
None
,
include_py
=
None
,
version
=
None
,
out
=
None
):
# preprocess Python.h and build the AST
python_h
=
preprocess_python_headers
(
cc
=
cc
,
include_py
=
include_py
)
parser
=
c_parser
.
CParser
()
ast
=
parser
.
parse
(
python_h
)
# extract struct members from the AST
ast_parser
=
AstParser
()
ast_parser
.
visit
(
ast
)
writer
=
Writer
()
if
include_py
and
not
version
:
raise
RuntimeError
(
"If the include path is overridden, version must be "
"defined"
)
if
version
:
version
=
version
.
split
(
'.'
)
else
:
version
=
sys
.
version_info
# generate the C# code
abi_flags
=
getattr
(
sys
,
"abiflags"
,
""
).
replace
(
"m"
,
""
)
gen_interop_head
(
writer
,
version
,
abi_flags
)
type_name
=
f"TypeOffset
{
version
[
0
]
}
{
version
[
1
]
}
{
abi_flags
}
"
gen_heap_type_members
(
ast_parser
,
writer
,
type_name
)
gen_interop_tail
(
writer
)
interop_cs
=
writer
.
to_string
()
if
not
out
or
out
==
"-"
:
print
(
interop_cs
)
else
:
with
open
(
out
,
"w"
)
as
fh
:
fh
.
write
(
interop_cs
)
if
__name__
==
"__main__"
:
import
argparse
a
=
argparse
.
ArgumentParser
(
"Interop file generator for Python.NET"
)
a
.
add_argument
(
"--cc"
,
help
=
"C compiler to use, either clang or gcc"
)
a
.
add_argument
(
"--include-py"
,
help
=
"Include path of Python"
)
a
.
add_argument
(
"--version"
,
help
=
"Python version"
)
a
.
add_argument
(
"--out"
,
help
=
"Output path"
,
default
=
"-"
)
args
=
a
.
parse_args
()
sys
.
exit
(
main
(
cc
=
args
.
cc
,
include_py
=
args
.
include_py
,
out
=
args
.
out
,
version
=
args
.
version
))
Back
|
FazBrowse Home
|
New Git URL