File: bindings.py

package info (click to toggle)
sip5 5.5.0%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 3,380 kB
  • sloc: ansic: 37,043; yacc: 6,670; python: 2,107; lex: 823; makefile: 18; cpp: 4
file content (322 lines) | stat: -rw-r--r-- 12,725 bytes parent folder | download
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
# Copyright (c) 2020, Riverbank Computing Limited
# All rights reserved.
#
# This copy of SIP is licensed for use under the terms of the SIP License
# Agreement.  See the file LICENSE for more details.
#
# This copy of SIP may also used under the terms of the GNU General Public
# License v2 or v3 as published by the Free Software Foundation which can be
# found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


import os
import sys

from .buildable import BuildableBindings
from .code_generator import (parse, generateCode, generateExtracts,
        generateAPI, generateXML, generateTypeHints)
from .configurable import Configurable, Option
from .exceptions import UserException
from .installable import Installable
from .module import copy_nonshared_sources


class Bindings(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),

        # The list of disabled feature tags.
        Option('disabled_features', 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('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__(**kwargs)

        self.project = project
        self.name = name

    def apply_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.
        if self.sip_file is None:
            self.sip_file = self.name + '.sip'

        super().apply_nonuser_defaults(tool)

    def apply_user_defaults(self, tool):
        """ Set default values for user options that haven't been set yet. """

        if self.protected_is_public is None:
            self.protected_is_public = (self.project.py_platform != 'win32')

        super().apply_user_defaults(tool)

    def generate(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

        # Parse the input file.
        pt, fq_name, uses_limited_api, sip_files, self.tags, self.disabled_features = parse(
                self.sip_file.replace('\\', '/'), True, self.tags, None,
                self.disabled_features, self.protected_is_public)

        uses_limited_api = bool(uses_limited_api)

        # 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, fq_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.
        if project.api_dir and not self.internal:
            project.progress(
                    "Generating the {0} .api file".format(buildable.target))

            generateAPI(pt,
                    os.path.join(project.build_dir, buildable.target + '.api'))

        # Generate any extracts.
        if self.generate_extracts:
            generateExtracts(pt, extracts)

        # Generate any type hints file.
        if self.pep484_pyi and not self.internal:
            project.progress(
                    "Generating the {0} .pyi file".format(buildable.target))

            pyi_path = os.path.join(buildable.build_dir,
                    buildable.target + '.pyi')

            generateTypeHints(pt, pyi_path)

            installable = Installable('pyi',
                    target_subdir=buildable.get_install_subdir())
            installable.files.append(pyi_path)
            buildable.installables.append(installable)

        # Generate the bindings.
        header, sources = generateCode(pt, buildable.build_dir,
                self.source_suffix, self.exceptions, self.tracing,
                self.release_gil, self.concatenate, self.tags,
                self.disabled_features, self.docstrings, project.py_debug,
                project.sip_module)

        if header:
            buildable.headers.append(header)

        buildable.headers.extend(self.headers)

        buildable.sources.extend(sources)

        # Add the sip module code if it is not shared.
        buildable.include_dirs.append(buildable.build_dir)

        if project.sip_module:
            # sip.h will already be in the build directory.
            buildable.include_dirs.append(project.build_dir)

            if not self.internal:
                # Add an installable for the .sip files.
                installable = buildable.get_bindings_installable('sip')

                sip_dir = os.path.dirname(self.sip_file)

                for fn in sip_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.
                    if os.path.isfile(sip_path):
                        installable.files.append(sip_path)

                buildable.installables.append(installable)
        else:
            buildable.sources.extend(
                    copy_nonshared_sources(project.abi_version,
                            buildable.build_dir))

        buildable.include_dirs.extend(self.include_dirs)
        buildable.sources.extend(self.sources)

        if self.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)

        return buildable

    def get_options(self):
        """ Return the list of configurable options. """

        options = super().get_options()
        options.extend(self._options)

        return options

    def is_buildable(self):
        """ Return True if the bindings are buildable.  This will not be called
        if the bindings have been explicitly enabled.
        """

        return True

    def verify_configuration(self, tool):
        """ Verify that the configuration is complete and consistent. """

        if tool not in Option.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)
                for o in self.extra_objects]
        self.headers = [project.project_path(h) for h in self.headers]
        self.include_dirs = [project.project_path(d)
                for d in self.include_dirs]
        self.library_dirs = [project.project_path(d)
                for d in self.library_dirs]
        self.sip_file = project.project_path(self.sip_file,
                relative_to=project.sip_files_dir)
        self.sources = [project.project_path(s) for s in self.sources]

        # Check the .sip file exists.
        if not os.path.isfile(self.sip_file):
            raise UserException(
                    "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.
        if sys.platform == 'win32':
            if self.debug:
                if not project.py_debug:
                    raise UserException(
                            "A debug version of Python must be used when "
                            "building a debug version of the {0} "
                            "bindings".format(self.name))
            elif project.py_debug:
                raise UserException(
                        "A debug version of the {0} bindings must be built "
                        "when a debug version of Python is used".format(
                                self.name))

        if not self.source_suffix:
            self.source_suffix = None