- Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathbindings.py
315 lines (236 loc) · 11.8 KB
/
bindings.py
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
# SPDX-License-Identifier: BSD-2-Clause
# Copyright (c) 2025 Phil Thompson <phil@riverbankcomputing.com>
importos
importsys
from .buildableimportBuildableBindings
from .configurableimportConfigurable, Option
from .exceptionsimportUserException
from .generatorimportparse, resolve
from .generator.outputsimport (output_api, output_code, output_extract,
output_pyi)
from .installableimportInstallable
from .moduleimportcopy_nonshared_sources
from .versionimportSIP_VERSION
classBindings(Configurable):
""" The encapsulation of a module's bindings. """
# The configurable options.
_options= (
# Any bindings level builder-specific settings.
Option('builder_settings', option_type=list),
# The list of #define names and values in the format "NAME" or
# "NAME=VALUE".
Option('define_macros', option_type=list),
# Set if exception support is enabled.
Option('exceptions', option_type=bool),
# The list of extra compiler arguments.
Option('extra_compile_args', option_type=list),
# The list of extra linker arguments.
Option('extra_link_args', option_type=list),
# The list of extra compiled object files to link.
Option('extra_objects', option_type=list),
# The list of extracts to generate.
Option('generate_extracts', option_type=list),
# The list of additional .h files.
Option('headers', option_type=list),
# The list of additional C/C++ include directories to search.
Option('include_dirs', option_type=list),
# Set if the bindings are internal. Internal bindings don't have their
# .sip, .pyi or .api files installed.
Option('internal', option_type=bool),
# The list of library names to link against.
Option('libraries', option_type=list),
# The list of C/C++ library directories to search.
Option('library_dirs', option_type=list),
# Set to always release the Python GIL.
Option('release_gil', option_type=bool),
# Set to compile the module as a static library.
Option('static', option_type=bool),
# The name of the .sip file that specifies the bindings. If it is
# relative then it is relative to the project's 'sip_files_dir'.
Option('sip_file'),
# The filename extension to use for generated source files.
Option('source_suffix'),
# The list of additional C/C++ source files to compile and link.
Option('sources', option_type=list),
# The list of tags to enable.
Option('tags', option_type=list),
# The user-configurable options. Although the use of a corresponding
# command line option will affect all sets of bindings, putting them
# here (as opposed to in Project) means they can have individual
# values specified in pyproject.toml.
Option('concatenate', option_type=int,
help="concatenate the generated bindings into N source files",
metavar="N"),
Option('debug', option_type=bool, help="build with debugging symbols"),
Option('disabled_features', option_type=list,
help="disable the TAG feature tag", metavar="TAG"),
Option('docstrings', option_type=bool, inverted=True,
help="disable the generation of docstrings"),
Option('pep484_pyi', option_type=bool,
help="enable the generation of PEP 484 .pyi files"),
Option('protected_is_public', option_type=bool,
help="enable the protected/public hack (default on non-Windows)"),
Option('protected_is_public', option_type=bool, inverted=True,
help="disable the protected/public hack (default on Windows)"),
Option('tracing', option_type=bool, help="build with tracing support"),
)
def__init__(self, project, name, **kwargs):
""" Initialise the bindings. """
super().__init__()
self.project=project
self.name=name
self.initialise_options(kwargs)
defapply_nonuser_defaults(self, tool):
""" Set default values for each non-user configurable option that
hasn't been set yet.
"""
# Provide a default .sip file name if needed.
ifself.sip_fileisNone:
self.sip_file=self.name+'.sip'
super().apply_nonuser_defaults(tool)
defapply_user_defaults(self, tool):
""" Set default values for user options that haven't been set yet. """
ifself.protected_is_publicisNone:
self.protected_is_public= (self.project.py_platform!='win32')
super().apply_user_defaults(tool)
defgenerate(self):
""" Generate the bindings source code and optional additional extracts.
and return a BuildableBindings instance containing the details of
everything needed to build the bindings.
"""
project=self.project
# The old parser had no concept of the encoding of a .sip file. For
# the moment we say that files should be UTF-8. If that proves to be a
# problem then a project-specific encoding should be able to be
# specified in pyproject.toml which would apply to all .sip files that
# make up the project.
encoding='UTF-8'
# Parse the input file.
spec, modules, sip_files=parse(self.sip_file, SIP_VERSION, encoding,
project.target_abi, self.tags, self.disabled_features,
self.protected_is_public, self._sip_include_dirs,
project.sip_module)
# Update the target ABI for the project.
ifproject.target_abiisNoneorproject.target_abi<spec.target_abi:
project.target_abi=spec.target_abi
# Resolve the types.
resolve(spec, modules)
module=spec.module
uses_limited_api=module.use_limited_apiorspec.is_composite
# The details of things that will have been generated. Note that we
# don't include anything for .api files or generic extracts as the
# arguments include a file name.
buildable=BuildableBindings(self, module.fq_py_name.name,
uses_limited_api=uses_limited_api)
buildable.builder_settings.extend(self.builder_settings)
buildable.debug=self.debug
buildable.exceptions=self.exceptions
buildable.extra_compile_args=self.extra_compile_args
buildable.extra_link_args=self.extra_link_args
buildable.extra_objects=self.extra_objects
buildable.static=self.static
# Generate any API file.
ifproject.api_dirandnotself.internal:
project.progress(
"Generating the {0} .api file".format(buildable.target))
output_api(spec,
os.path.join(project.build_dir, buildable.target+'.api'))
# Generate any extracts.
forextract_refinself.generate_extracts:
output_extract(spec, extract_ref)
# Generate any type hints file.
ifself.pep484_pyiandnotself.internal:
project.progress(
"Generating the {0} .pyi file".format(buildable.target))
pyi_path=os.path.join(buildable.build_dir,
buildable.target+'.pyi')
output_pyi(spec, project, pyi_path)
installable=Installable('pyi',
target_subdir=buildable.get_install_subdir())
installable.files.append(pyi_path)
buildable.installables.append(installable)
# Generate the bindings.
output_code(spec, self, project, buildable)
buildable.headers.extend(self.headers)
# Add the sip module code if it is not shared.
buildable.include_dirs.append(buildable.build_dir)
ifproject.sip_module:
# sip.h will already be in the build directory.
buildable.include_dirs.append(project.build_dir)
ifnotself.internal:
# Add an installable for the .sip files.
installable=buildable.get_bindings_installable('sip')
sip_dir=os.path.dirname(self.sip_file)
forfninsip_files:
sip_path=os.path.join(sip_dir, fn)
# The code generator does not report the full pathname of a
# .sip file (only names relative to the search directory in
# which it was found). Therefore we need to check if it is
# actually in the directory we are installing from and
# ignore it if not. This isn't really the right thing to
# do but is actually what we want when we have optional
# license .sip files.
ifos.path.isfile(sip_path):
installable.files.append(sip_path)
buildable.installables.append(installable)
else:
buildable.sources.extend(
copy_nonshared_sources(project.build_abi,
buildable.build_dir))
buildable.include_dirs.extend(self.include_dirs)
buildable.sources.extend(self.sources)
ifself.protected_is_public:
buildable.define_macros.append('SIP_PROTECTED_IS_PUBLIC')
buildable.define_macros.append('protected=public')
buildable.define_macros.extend(self.define_macros)
buildable.libraries.extend(self.libraries)
buildable.library_dirs.extend(self.library_dirs)
returnbuildable
defget_options(self):
""" Return the list of configurable options. """
options=super().get_options()
options.extend(self._options)
returnoptions
defis_buildable(self):
""" Return True if the bindings are buildable. This will not be called
if the bindings have been explicitly enabled.
"""
returnTrue
defverify_configuration(self, tool):
""" Verify that the configuration is complete and consistent. """
iftoolnotinOption.BUILD_TOOLS:
return
project=self.project
super().verify_configuration(tool)
# Make sure relevent paths are absolute and use native separators.
self.extra_objects= [project.project_path(o)
foroinself.extra_objects]
self.headers= [project.project_path(h) forhinself.headers]
self.include_dirs= [project.project_path(d)
fordinself.include_dirs]
self.library_dirs= [project.project_path(d)
fordinself.library_dirs]
self.sip_file=project.project_path(self.sip_file,
relative_to=project.sip_files_dir)
self.sources= [project.project_path(s) forsinself.sources]
# Check the .sip file exists.
ifnotos.path.isfile(self.sip_file):
raiseUserException(
"the file '{0}' for the {1} bindings does not "
"exist".format(self.sip_file, self.name))
# On Windows the interpreter must be a debug build if a debug version
# is to be built and vice versa.
ifsys.platform=='win32':
ifself.debug:
ifnotproject.py_debug:
raiseUserException(
"A debug version of Python must be used when "
"building a debug version of the {0} "
"bindings".format(self.name))
elifproject.py_debug:
raiseUserException(
"A debug version of the {0} bindings must be built "
"when a debug version of Python is used".format(
self.name))
ifnotself.source_suffix:
self.source_suffix=None