1# This is the PyQt5 build script.
2#
3# Copyright (c) 2021 Riverbank Computing Limited <info@riverbankcomputing.com>
4#
5# This file is part of PyQt5.
6#
7# This file may be used under the terms of the GNU General Public License
8# version 3.0 as published by the Free Software Foundation and appearing in
9# the file LICENSE included in the packaging of this file.  Please review the
10# following information to ensure the GNU General Public License version 3.0
11# requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12#
13# If you do not wish to use this file under the terms of the GPL version 3.0
14# then you may purchase a commercial license.  For more information contact
15# info@riverbankcomputing.com.
16#
17# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
18# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19
20
21import glob
22import os
23import sys
24
25from pyqtbuild import PyQtBindings, PyQtProject, QmakeTargetInstallable
26from sipbuild import (Buildable, BuildableModule, Installable, Option,
27        UserException)
28
29
30class PyQt(PyQtProject):
31    """ The PyQt5 project. """
32
33    def __init__(self):
34        """ Initialise the project. """
35
36        super().__init__(dunder_init=True, tag_prefix='Qt',
37                console_scripts=[
38                    'pylupdate5 = PyQt5.pylupdate_main:main',
39                    'pyrcc5 = PyQt5.pyrcc_main:main',
40                    'pyuic5 = PyQt5.uic.pyuic:main'])
41
42        # Each set of bindings must appear after any set they depend on.
43        self.bindings_factories = [Qt, QtCore, QtNetwork, QtGui, QtWidgets,
44            QtQml, QAxContainer, QtAndroidExtras, QtBluetooth, QtDBus,
45            QtDesigner, Enginio, QtHelp, QtMacExtras, QtMultimedia,
46            QtMultimediaWidgets, QtNfc, QtOpenGL, QtPositioning, QtLocation,
47            QtPrintSupport, QtQuick, QtQuick3D, QtQuickWidgets,
48            QtRemoteObjects, QtSensors, QtSerialPort, QtSql, QtSvg, QtTest,
49            QtTextToSpeech, QtWebChannel, QtWebKit, QtWebKitWidgets,
50            QtWebSockets, QtWinExtras, QtX11Extras, QtXml, QtXmlPatterns,
51            _QOpenGLFunctions_2_0, _QOpenGLFunctions_2_1,
52            _QOpenGLFunctions_4_1_Core, _QOpenGLFunctions_ES2, pylupdate,
53            pyrcc]
54
55    def apply_user_defaults(self, tool):
56        """ Set default values where needed. """
57
58        if self.license_dir is None:
59            self.license_dir = os.path.join(self.root_dir, 'sip')
60        else:
61            self.license_dir = os.path.abspath(self.license_dir)
62
63        super().apply_user_defaults(tool)
64
65        if not self.tools:
66            self.console_scripts = []
67
68    def get_dunder_init(self):
69        """ Return the contents of the __init__.py file to install. """
70
71        with open(os.path.join(self.root_dir, '__init__.py')) as f:
72            dunder_init = f.read()
73
74        if self.py_platform == 'win32':
75            dunder_init += """
76
77def find_qt():
78    import os, sys
79
80    qtcore_dll = '\\\\Qt5Core.dll'
81
82    dll_dir = os.path.dirname(sys.executable)
83    if not os.path.isfile(dll_dir + qtcore_dll):
84        path = os.environ['PATH']
85
86        dll_dir = os.path.dirname(__file__) + '\\\\Qt5\\\\bin'
87        if os.path.isfile(dll_dir + qtcore_dll):
88            path = dll_dir + ';' + path
89            os.environ['PATH'] = path
90        else:
91            for dll_dir in path.split(';'):
92                if os.path.isfile(dll_dir + qtcore_dll):
93                    break
94            else:
95                return
96
97    try:
98        os.add_dll_directory(dll_dir)
99    except AttributeError:
100        pass
101
102
103find_qt()
104del find_qt
105"""
106
107        return dunder_init
108
109    def get_options(self):
110        """ Return the sequence of configurable options. """
111
112        # Get the standard options.
113        options = super().get_options()
114
115        # Add our new options.
116        options.append(
117                Option('confirm_license', option_type=bool,
118                        help="confirm acceptance of the license"))
119
120        options.append(
121                Option('license_dir', option_type=str,
122                        help="the license file can be found in DIR",
123                        metavar="DIR"))
124
125        options.append(
126                Option('qt_shared', option_type=bool,
127                        help="assume Qt has been built as shared libraries"))
128
129        options.append(
130                Option('designer_plugin', option_type=bool, inverted=True,
131                        help="disable the building of the Python plugin for Qt Designer"))
132
133        options.append(
134                Option('qml_plugin', option_type=bool, inverted=True,
135                        help="disable the building of the Python plugin for qmlscene"))
136
137        options.append(
138                Option('dbus', option_type=str,
139                        help="the directory containing the dbus/dbus-python.h file",
140                        metavar="DIR"))
141
142        options.append(
143                Option('dbus_python', option_type=bool, inverted=True,
144                        help="disable the Qt support for the dbus-python package"))
145
146        options.append(
147                Option('tools', option_type=bool, inverted=True,
148                        help="disable the building of pyuic5, pyrcc5 and pylupdate5"))
149
150        return options
151
152    def update(self, tool):
153        """ Update the configuration. """
154
155        if tool not in Option.BUILD_TOOLS:
156            return
157
158        # Check we support the version of Qt.
159        if self.builder.qt_version >> 16 != 5:
160            raise UserException(
161                    "Qt v5 is required, not v{0}".format(
162                            self.builder.qt_version_str))
163
164        # Automatically confirm the license if there might not be a command
165        # line option to do so.
166        if tool == 'pep517':
167            self.confirm_license = True
168
169        self._check_license()
170
171        # Handle the platform tag (allowing for win32-g++).
172        if self.py_platform.startswith('win32'):
173            plattag = 'WS_WIN'
174        elif self.py_platform == 'darwin':
175            plattag = 'WS_MACX'
176        else:
177            plattag = 'WS_X11'
178
179        self.bindings['QtCore'].tags.append(plattag)
180
181        # Make sure the bindings are buildable.
182        super().update(tool)
183
184        # PyQtWebEngine needs to know if QtWebChannel is available.
185        if 'QtWebChannel' not in self.bindings:
186            qtcore = self.bindings.get('QtCore')
187            if qtcore is not None:
188                qtcore.disabled_features.append('PyQt_WebChannel')
189
190        # Add the composite module.
191        if 'Qt' in self.bindings:
192            self._add_composite_module(tool)
193
194        # Always install the uic module.
195        installable = Installable('uic', target_subdir='PyQt5')
196        installable.files.append(os.path.join(self.root_dir, 'pyuic', 'uic'))
197        self.installables.append(installable)
198
199        # If any set of bindings is being built as a debug version then assume
200        # the plugins and DBus support should as well.
201        for bindings in self.bindings.values():
202            if bindings.debug:
203                others_debug = True
204                break
205        else:
206            others_debug = self.py_debug
207
208        # Add the plugins.  For the moment we don't include them in wheels.
209        # This may change when we improve the bundling of Qt.
210        if tool in ('build', 'install'):
211            if self.designer_plugin and 'QtDesigner' in self.bindings:
212                self._add_plugin('designer', "Qt Designer", 'pyqt5',
213                        'designer', others_debug)
214
215            if self.qml_plugin and 'QtQml' in self.bindings:
216                self._add_plugin('qmlscene', "qmlscene", 'pyqt5qmlplugin',
217                        'PyQt5', others_debug)
218
219        # Add the dbus-python support.
220        if self.dbus_python:
221            self._add_dbus(others_debug)
222
223    def _add_composite_module(self, tool):
224        """ Add the bindings for the composite module. """
225
226        sip_file = os.path.join(self.build_dir, 'Qt.sip')
227        sip_f = self.open_for_writing(sip_file)
228
229        sip_f.write('''%CompositeModule PyQt5.Qt
230
231''')
232
233        for bindings in self.bindings.values():
234            if not bindings.internal:
235                sip_f.write(
236                        '%Include {}\n'.format(
237                                bindings.sip_file.replace('\\', '/')))
238
239        sip_f.close()
240
241        self.bindings['Qt'].sip_file = sip_file
242
243    def _add_dbus(self, debug):
244        """ Add the dbus-python support. """
245
246        self.progress(
247                "Checking to see if the dbus-python support should be built")
248
249        # See if dbus-python is installed.
250        try:
251            import dbus.mainloop
252        except ImportError:
253            self.progress(
254                    "The dbus-python package does not seem to be installed.")
255            return
256
257        dbus_module_dir = dbus.mainloop.__path__[0]
258
259        # Get the flags for the DBus library.
260        dbus_inc_dirs = []
261        dbus_lib_dirs = []
262        dbus_libs = []
263
264        args = ['pkg-config', '--cflags-only-I', '--libs dbus-1']
265
266        for line in self.read_command_pipe(args, fatal=False):
267            for flag in line.strip().split():
268                if flag.startswith('-I'):
269                    dbus_inc_dirs.append(flag[2:])
270                elif flag.startswith('-L'):
271                    dbus_lib_dirs.append(flag[2:])
272                elif flag.startswith('-l'):
273                    dbus_libs.append(flag[2:])
274
275        if not any([dbus_inc_dirs, dbus_lib_dirs, dbus_libs]):
276            self.progress("DBus v1 does not seem to be installed.")
277
278        # Try and find dbus-python.h.  The current PyPI package doesn't install
279        # it.  We look where DBus itself is installed.
280        if self.dbus:
281            dbus_inc_dirs.append(self.dbus)
282
283        for d in dbus_inc_dirs:
284            if os.path.isfile(os.path.join(d, 'dbus', 'dbus-python.h')):
285                break
286        else:
287            self.progress(
288                    "dbus/dbus-python.h could not be found and so the "
289                    "dbus-python support module will be disabled. If "
290                    "dbus-python is installed then use the --dbus argument to "
291                    "explicitly specify the directory containing "
292                    "dbus/dbus-python.h.")
293            return
294
295        # Create the buildable.
296        sources_dir = os.path.join(self.root_dir, 'dbus')
297
298        buildable = BuildableModule(self, 'dbus', 'dbus.mainloop.pyqt5',
299                uses_limited_api=True)
300        buildable.builder_settings.append('QT -= gui')
301        buildable.sources.extend(glob.glob(os.path.join(sources_dir, '*.cpp')))
302        buildable.headers.extend(glob.glob(os.path.join(sources_dir, '*.h')))
303        buildable.include_dirs.extend(dbus_inc_dirs)
304        buildable.library_dirs.extend(dbus_lib_dirs)
305        buildable.libraries.extend(dbus_libs)
306        buildable.debug = debug
307
308        self.buildables.append(buildable)
309
310    def _add_plugin(self, name, user_name, target_name, target_subdir, debug):
311        """ Add a plugin to the project buildables. """
312
313        builder = self.builder
314
315        # Check we have a shared interpreter library.
316        if not self.py_pylib_shlib:
317            self.progress("The {0} plugin was disabled because a shared Python library couldn't be found.".format(user_name))
318            return
319
320        # Where the plugin will (eventually) be installed.
321        target_plugin_dir = os.path.join(
322                builder.qt_configuration['QT_INSTALL_PLUGINS'], target_subdir)
323
324        # Create the buildable and add it to the builder.
325        buildable = Buildable(self, name)
326        self.buildables.append(buildable)
327
328        # The platform-specific name of the plugin file.
329        if self.py_platform == 'win32':
330            target_name = target_name + '.dll'
331        elif self.py_platform == 'darwin':
332            target_name = 'lib' + target_name + '.dylib'
333        else:
334            target_name = 'lib' + target_name + '.so'
335
336        # Create the corresponding installable.
337        installable = QmakeTargetInstallable(target_name, target_plugin_dir)
338        buildable.installables.append(installable)
339
340        # Create the .pro file.
341        self.progress(
342                "Generating the {0} plugin .pro file".format(user_name))
343
344        root_plugin_dir = os.path.join(self.root_dir, name)
345
346        with open(os.path.join(root_plugin_dir, name + '.pro-in')) as f:
347            prj = f.read()
348
349        prj = prj.replace('@QTCONFIG@', 'debug' if debug else 'release')
350        prj = prj.replace('@PYINCDIR@',
351                builder.qmake_quote(self.py_include_dir))
352        prj = prj.replace('@SIPINCDIR@', builder.qmake_quote(self.build_dir))
353        prj = prj.replace('@PYLINK@',
354                '-L{} -l{}'.format(self.py_pylib_dir, self.py_pylib_lib))
355        prj = prj.replace('@PYSHLIB@', self.py_pylib_shlib)
356        prj = prj.replace('@QTPLUGINDIR@',
357                builder.qmake_quote(target_plugin_dir))
358
359        # Write the .pro file.
360        pro_path = os.path.join(buildable.build_dir, name + '.pro')
361        pro_f = self.open_for_writing(pro_path)
362
363        pro_f.write(prj)
364
365        pro_f.write('''
366INCLUDEPATH += {}
367VPATH = {}
368'''.format(builder.qmake_quote(root_plugin_dir), builder.qmake_quote(root_plugin_dir)))
369
370        pro_f.write('\n'.join(builder.qmake_settings) + '\n')
371
372        pro_f.close()
373
374    def _check_license(self):
375        """ Handle the validation of the PyQt5 license. """
376
377        # We read the license.py file as data.
378        license_py = os.path.join(self.root_dir, 'license.py')
379
380        if os.path.isfile(license_py):
381            ltype = lname = lfile = None
382
383            with open(license_py) as lf:
384                for line in lf:
385                    parts = line.split('=')
386                    if len(parts) == 2:
387                        name, value = parts
388
389                        name = name.strip()
390                        value = value.strip()[1:-1]
391
392                        if name == 'LicenseType':
393                            ltype = value
394                        elif name == 'LicenseName':
395                            lname = value
396                        elif name == 'LicenseFile':
397                            lfile = value
398
399            if lname is None or lfile is None:
400                ltype = None
401        else:
402            ltype = None
403
404        # Default to the GPL.
405        if ltype is None:
406            ltype = 'GPL'
407            lname = "GNU General Public License"
408            lfile = 'pyqt-gpl.sip'
409
410        self.progress(
411                "This is the {0} version of PyQt {1} (licensed under the {2}) "
412                        "for Python {3} on {4}.".format(
413                                ltype, self.version_str, lname,
414                                sys.version.split()[0], sys.platform))
415
416        # Confirm the license if not already done.
417        if not self.confirm_license:
418            loptions = """
419Type 'L' to view the license.
420"""
421
422            sys.stdout.write(loptions)
423            sys.stdout.write("""Type 'yes' to accept the terms of the license.
424Type 'no' to decline the terms of the license.
425
426""")
427
428            while True:
429                sys.stdout.write("Do you accept the terms of the license? ")
430                sys.stdout.flush()
431
432                try:
433                    resp = sys.stdin.readline()
434                except KeyboardInterrupt:
435                    raise SystemExit
436                except:
437                    resp = ""
438
439                resp = resp.strip().lower()
440
441                if resp == "yes":
442                    break
443
444                if resp == "no":
445                    sys.exit()
446
447                if resp == 'l':
448                    os.system('more LICENSE')
449
450        # Check that the license file exists and fix its syntax.
451        src_lfile = os.path.join(self.license_dir, lfile)
452
453        if os.path.isfile(src_lfile):
454            self.progress("Found the license file '{0}'.".format(lfile))
455            self._fix_license(src_lfile,
456                    os.path.join(self.build_dir, lfile + '5'))
457
458            # Make sure sip can find the license file.
459            self.sip_include_dirs.append(self.build_dir)
460        else:
461            raise UserException(
462                    "Please copy the license file '{0}' to '{1}'".format(lfile,
463                            self.license_dir))
464
465    def _fix_license(self, src_lfile, dst_lfile):
466        """ Copy the license file and fix it so that it conforms to the SIP v5
467        syntax.
468        """
469
470        with open(src_lfile) as f:
471            f5 = self.open_for_writing(dst_lfile)
472
473            for line in f:
474                if line.startswith('%License'):
475                    anno_start = line.find('/')
476                    anno_end = line.rfind('/')
477
478                    if anno_start < 0 or anno_end < 0 or anno_start == anno_end:
479                        f5.close()
480
481                        raise UserException(
482                                "'{0}' has missing annotations".format(name))
483
484                    annos = line[anno_start + 1:anno_end].split(', ')
485                    annos5 = [anno[0].lower() + anno[1:] for anno in annos]
486
487                    f5.write('%License(')
488                    f5.write(', '.join(annos5))
489                    f5.write(')\n')
490                else:
491                    f5.write(line)
492
493            f5.close()
494
495
496class OpenGLBindings(PyQtBindings):
497    """ Encapsulate internal OpenGL functions bindings. """
498
499    def __init__(self, project, version):
500        """ Initialise the bindings. """
501
502        super().__init__(project, '_QOpenGLFunctions_' + version,
503                test_headers=['qopenglfunctions_{}.h'.format(version.lower())],
504                test_statement='new QOpenGLFunctions_{}()'.format(version),
505                internal=True)
506
507    def is_buildable(self):
508        """ Return True if the bindings are buildable. """
509
510        # Check that the QtGui bindings are being built.
511        qtgui = self.project.bindings.get('QtGui')
512        if qtgui is None:
513            return False
514
515        # Check if all OpenGL support is disabled.
516        if 'PyQt_OpenGL' in qtgui.disabled_features:
517            return False
518
519        # Check if OpenGL desktop support has been disabled.
520        if 'PyQt_Desktop_OpenGL' in qtgui.disabled_features:
521            if self.is_desktop_opengl():
522                return False
523        else:
524            if not self.is_desktop_opengl():
525                return False
526
527        # Run the standard configuration checks.
528        return super().is_buildable()
529
530    def is_desktop_opengl(self):
531        """ Return True if the bindings are for desktop OpenGL. """
532
533        return True
534
535
536class Qt(PyQtBindings):
537    """ The Qt composite module. """
538
539    def __init__(self, project):
540        """ Initialise the bindings. """
541
542        # We specify 'internal' to avoid the generation of .api and .pyi files
543        # and the installation of the .sip files.
544        super().__init__(project, 'Qt', qmake_QT=['-core', '-gui'],
545                internal=True)
546
547
548class QAxContainer(PyQtBindings):
549    """ The QAxContainer bindings. """
550
551    def __init__(self, project):
552        """ Initialise the bindings. """
553
554        super().__init__(project, 'QAxContainer', qmake_QT=['axcontainer'],
555                test_headers=['qaxobject.h'], test_statement='new QAxObject()')
556
557
558class QtAndroidExtras(PyQtBindings):
559    """ The QtAndroidExtras bindings. """
560
561    def __init__(self, project):
562        """ Initialise the bindings. """
563
564        super().__init__(project, 'QtAndroidExtras',
565                qmake_QT=['androidextras'], test_headers=['QtAndroid'],
566                test_statement='QtAndroid::androidSdkVersion()')
567
568
569class QtBluetooth(PyQtBindings):
570    """ The QtBluetooth bindings. """
571
572    def __init__(self, project):
573        """ Initialise the bindings. """
574
575        super().__init__(project, 'QtBluetooth', qmake_QT=['bluetooth'],
576                test_headers=['qbluetoothaddress.h'],
577                test_statement='new QBluetoothAddress()')
578
579
580class QtCore(PyQtBindings):
581    """ The QtCore bindings. """
582
583    def __init__(self, project):
584        """ Initialise the bindings. """
585
586        super().__init__(project, 'QtCore', qmake_QT=['-gui'])
587
588    def generate(self):
589        """ Generate the bindings source code and return the corresponding
590        buildable.
591        """
592
593        # This is re-implemented so that we can update the buildable to include
594        # the embedded sip flags.  Note that this support is deprecated and can
595        # be removed once support for sip4 has been dropped.
596
597        project = self.project
598
599        # The embedded flags.
600        sip_flags = ['-n', project.sip_module]
601
602        if project.py_debug:
603            sip_flags.append('-D')
604
605        for tag in self.tags:
606            sip_flags.append('-t')
607            sip_flags.append(tag)
608
609        for bindings in project.bindings.values():
610            for feature in bindings.disabled_features:
611                sip_flags.append('-x')
612                sip_flags.append(feature)
613
614        buildable = super().generate()
615
616        cpp = 'qpycore_post_init.cpp'
617        in_path = os.path.join(project.root_dir, 'qpy', 'QtCore', cpp + '.in')
618        out_path = os.path.join(buildable.build_dir, cpp)
619
620        out_f = project.open_for_writing(out_path)
621
622        with open(in_path) as in_f:
623            code = in_f.read()
624            code = code.replace('@@PYQT_SIP_FLAGS@@', ' '.join(sip_flags))
625            out_f.write(code)
626
627        out_f.close()
628
629        buildable.sources.append(cpp)
630
631        return buildable
632
633    def handle_test_output(self, test_output):
634        """ Handle the output from the external test program and return True if
635        the bindings are buildable.
636        """
637
638        project = self.project
639
640        if not project.qt_shared and test_output[0] == 'shared':
641            project.qt_shared = True
642
643        return super().handle_test_output(test_output[1:])
644
645
646class QtDBus(PyQtBindings):
647    """ The QtDBus bindings. """
648
649    def __init__(self, project):
650        """ Initialise the bindings. """
651
652        super().__init__(project, 'QtDBus', qmake_QT=['dbus', '-gui'],
653                test_headers=['qdbusconnection.h'],
654                test_statement='QDBusConnection::systemBus()')
655
656
657class QtDesigner(PyQtBindings):
658    """ The QtDesigner bindings. """
659
660    def __init__(self, project):
661        """ Initialise the bindings. """
662
663        super().__init__(project, 'QtDesigner', qmake_QT=['designer'],
664                test_headers=['QExtensionFactory', 'customwidget.h'],
665                test_statement='new QExtensionFactory()')
666
667    def is_buildable(self):
668        """ Return True if the bindings are buildable. """
669
670        project = self.project
671
672        if not project.qt_shared:
673            project.progress(
674                    "The QtDesigner bindings are disabled with a static Qt "
675                    "installation")
676            return False
677
678        return super().is_buildable()
679
680
681class Enginio(PyQtBindings):
682    """ The Enginio bindings. """
683
684    def __init__(self, project):
685        """ Initialise the bindings. """
686
687        super().__init__(project, 'Enginio', qmake_QT=['enginio'],
688                test_headers=['enginioclient.h'],
689                test_statement='new EnginioClient()')
690
691
692class QtGui(PyQtBindings):
693    """ The QtGui bindings. """
694
695    def __init__(self, project):
696        """ Initialise the bindings. """
697
698        super().__init__(project, 'QtGui')
699
700
701class QtHelp(PyQtBindings):
702    """ The QtHelp bindings. """
703
704    def __init__(self, project):
705        """ Initialise the bindings. """
706
707        super().__init__(project, 'QtHelp', qmake_QT=['help'],
708                test_headers=['qhelpengine.h'],
709                test_statement='new QHelpEngine("foo")')
710
711
712class QtLocation(PyQtBindings):
713    """ The QtLocation bindings. """
714
715    def __init__(self, project):
716        """ Initialise the bindings. """
717
718        super().__init__(project, 'QtLocation', qmake_QT=['location'],
719                test_headers=['qplace.h'], test_statement='new QPlace()')
720
721
722class QtMacExtras(PyQtBindings):
723    """ The QtMacExtras bindings. """
724
725    def __init__(self, project):
726        """ Initialise the bindings. """
727
728        super().__init__(project, 'QtMacExtras', qmake_QT=['macextras'],
729                test_headers=['qmacpasteboardmime.h'],
730                test_statement='class Foo : public QMacPasteboardMime {}')
731
732
733class QtMultimedia(PyQtBindings):
734    """ The QtMultimedia bindings. """
735
736    def __init__(self, project):
737        """ Initialise the bindings. """
738
739        super().__init__(project, 'QtMultimedia', qmake_QT=['multimedia'],
740                test_headers=['QAudioDeviceInfo'],
741                test_statement='new QAudioDeviceInfo()')
742
743
744class QtMultimediaWidgets(PyQtBindings):
745    """ The QtMultimediaWidgets bindings. """
746
747    def __init__(self, project):
748        """ Initialise the bindings. """
749
750        super().__init__(project, 'QtMultimediaWidgets',
751                qmake_QT=['multimediawidgets', 'multimedia'],
752                test_headers=['QVideoWidget'],
753                test_statement='new QVideoWidget()')
754
755
756class QtNetwork(PyQtBindings):
757    """ The QtNetwork bindings. """
758
759    def __init__(self, project):
760        """ Initialise the bindings. """
761
762        super().__init__(project, 'QtNetwork', qmake_QT=['network', '-gui'])
763
764
765class QtNfc(PyQtBindings):
766    """ The QtNfc bindings. """
767
768    def __init__(self, project):
769        """ Initialise the bindings. """
770
771        super().__init__(project, 'QtNfc', qmake_QT=['nfc', '-gui'],
772                test_headers=['qnearfieldmanager.h'],
773                test_statement='new QNearFieldManager()')
774
775
776class QtOpenGL(PyQtBindings):
777    """ The QtOpenGL bindings. """
778
779    def __init__(self, project):
780        """ Initialise the bindings. """
781
782        super().__init__(project, 'QtOpenGL', qmake_QT=['opengl'],
783                test_headers=['qgl.h'], test_statement='new QGLWidget()')
784
785
786class QtPositioning(PyQtBindings):
787    """ The QtPositioning bindings. """
788
789    def __init__(self, project):
790        """ Initialise the bindings. """
791
792        super().__init__(project, 'QtPositioning', qmake_QT=['positioning'],
793                test_headers=['qgeoaddress.h'],
794                test_statement='new QGeoAddress()')
795
796
797class QtPrintSupport(PyQtBindings):
798    """ The QtPrintSupport bindings. """
799
800    def __init__(self, project):
801        """ Initialise the bindings. """
802
803        super().__init__(project, 'QtPrintSupport', qmake_QT=['printsupport'])
804
805
806class QtQml(PyQtBindings):
807    """ The QtQml bindings. """
808
809    def __init__(self, project):
810        """ Initialise the bindings. """
811
812        super().__init__(project, 'QtQml', qmake_QT=['qml'],
813                test_headers=['qjsengine.h'], test_statement='new QJSEngine()')
814
815
816class QtQuick(PyQtBindings):
817    """ The QtQuick bindings. """
818
819    def __init__(self, project):
820        """ Initialise the bindings. """
821
822        super().__init__(project, 'QtQuick', qmake_QT=['quick'],
823                test_headers=['qquickwindow.h'],
824                test_statement='new QQuickWindow()')
825
826
827class QtQuick3D(PyQtBindings):
828    """ The QtQuick3D bindings. """
829
830    def __init__(self, project):
831        """ Initialise the bindings. """
832
833        super().__init__(project, 'QtQuick3D', qmake_QT=['quick3d'],
834                test_headers=['qquick3d.h'],
835                test_statement='QQuick3D::idealSurfaceFormat()')
836
837
838class QtQuickWidgets(PyQtBindings):
839    """ The QtQuickWidgets bindings. """
840
841    def __init__(self, project):
842        """ Initialise the bindings. """
843
844        super().__init__(project, 'QtQuickWidgets', qmake_QT=['quickwidgets'],
845                test_headers=['qquickwidget.h'],
846                test_statement='new QQuickWidget()')
847
848
849class QtRemoteObjects(PyQtBindings):
850    """ The QtRemoteObjects bindings. """
851
852    def __init__(self, project):
853        """ Initialise the bindings. """
854
855        super().__init__(project, 'QtRemoteObjects',
856                qmake_QT=['remoteobjects', '-gui'],
857                test_headers=['qtremoteobjectsversion.h'],
858                test_statement='const char *v = QTREMOTEOBJECTS_VERSION_STR')
859
860
861class QtSensors(PyQtBindings):
862    """ The QtSensors bindings. """
863
864    def __init__(self, project):
865        """ Initialise the bindings. """
866
867        super().__init__(project, 'QtSensors', qmake_QT=['sensors'],
868                test_headers=['qsensor.h'],
869                test_statement='new QSensor(QByteArray())')
870
871
872class QtSerialPort(PyQtBindings):
873    """ The QtSerialPort bindings. """
874
875    def __init__(self, project):
876        """ Initialise the bindings. """
877
878        super().__init__(project, 'QtSerialPort', qmake_QT=['serialport'],
879                test_headers=['qserialport.h'],
880                test_statement='new QSerialPort()')
881
882
883class QtSql(PyQtBindings):
884    """ The QtSql bindings. """
885
886    def __init__(self, project):
887        """ Initialise the bindings. """
888
889        super().__init__(project, 'QtSql', qmake_QT=['sql', 'widgets'],
890                test_headers=['qsqldatabase.h'],
891                test_statement='new QSqlDatabase()')
892
893
894class QtSvg(PyQtBindings):
895    """ The QtSvg bindings. """
896
897    def __init__(self, project):
898        """ Initialise the bindings. """
899
900        super().__init__(project, 'QtSvg', qmake_QT=['svg'],
901                test_headers=['qsvgwidget.h'],
902                test_statement='new QSvgWidget()')
903
904
905class QtTest(PyQtBindings):
906    """ The QtTest bindings. """
907
908    def __init__(self, project):
909        """ Initialise the bindings. """
910
911        super().__init__(project, 'QtTest', qmake_QT=['testlib', 'widgets'],
912                test_headers=['QtTest'], test_statement='QTest::qSleep(0)')
913
914
915class QtTextToSpeech(PyQtBindings):
916    """ The QtTextToSpeech bindings. """
917
918    def __init__(self, project):
919        """ Initialise the bindings. """
920
921        super().__init__(project, 'QtTextToSpeech',
922                qmake_QT=['texttospeech', '-gui'],
923                test_headers=['QTextToSpeech'],
924                test_statement='new QTextToSpeech()')
925
926
927class QtWebChannel(PyQtBindings):
928    """ The QtWebChannel bindings. """
929
930    def __init__(self, project):
931        """ Initialise the bindings. """
932
933        super().__init__(project, 'QtWebChannel',
934                qmake_QT=['webchannel', 'network', '-gui'],
935                test_headers=['qwebchannel.h'],
936                test_statement='new QWebChannel()')
937
938
939class QtWebKit(PyQtBindings):
940    """ The QtWebKit bindings. """
941
942    def __init__(self, project):
943        """ Initialise the bindings. """
944
945        super().__init__(project, 'QtWebKit', qmake_QT=['webkit', 'network'],
946                test_headers=['qwebkitglobal.h'],
947                test_statement='qWebKitVersion()')
948
949
950class QtWebKitWidgets(PyQtBindings):
951    """ The QtWebKitWidgets bindings. """
952
953    def __init__(self, project):
954        """ Initialise the bindings. """
955
956        super().__init__(project, 'QtWebKitWidgets',
957                qmake_QT=['webkitwidgets', 'printsupport'],
958                test_headers=['qwebpage.h'], test_statement='new QWebPage()')
959
960
961class QtWebSockets(PyQtBindings):
962    """ The QtWebSockets bindings. """
963
964    def __init__(self, project):
965        """ Initialise the bindings. """
966
967        super().__init__(project, 'QtWebSockets',
968                qmake_QT=['websockets', '-gui'], test_headers=['qwebsocket.h'],
969                test_statement='new QWebSocket()')
970
971
972class QtWidgets(PyQtBindings):
973    """ The QtWidgets bindings. """
974
975    def __init__(self, project):
976        """ Initialise the bindings. """
977
978        super().__init__(project, 'QtWidgets', qmake_QT=['widgets'],
979                test_headers=['qwidget.h'], test_statement='new QWidget()')
980
981
982class QtWinExtras(PyQtBindings):
983    """ The QtWinExtras bindings. """
984
985    def __init__(self, project):
986        """ Initialise the bindings. """
987
988        super().__init__(project, 'QtWinExtras',
989                qmake_QT=['winextras', 'widgets'], test_headers=['QtWin'],
990                test_statement='QtWin::isCompositionEnabled()')
991
992
993class QtX11Extras(PyQtBindings):
994    """ The QtX11Extras bindings. """
995
996    def __init__(self, project):
997        """ Initialise the bindings. """
998
999        super().__init__(project, 'QtX11Extras', qmake_QT=['x11extras'],
1000                test_headers=['QX11Info'],
1001                test_statement='QX11Info::display()')
1002
1003
1004class QtXml(PyQtBindings):
1005    """ The QtXml bindings. """
1006
1007    def __init__(self, project):
1008        """ Initialise the bindings. """
1009
1010        super().__init__(project, 'QtXml', qmake_QT=['xml', '-gui'],
1011                test_headers=['qdom.h'], test_statement='new QDomDocument()')
1012
1013
1014class QtXmlPatterns(PyQtBindings):
1015    """ The QtXmlPatterns bindings. """
1016
1017    def __init__(self, project):
1018        """ Initialise the bindings. """
1019
1020        super().__init__(project, 'QtXmlPatterns',
1021                qmake_QT=['xmlpatterns', '-gui', 'network'],
1022                test_headers=['qxmlname.h'], test_statement='new QXmlName()')
1023
1024
1025class _QOpenGLFunctions_2_0(OpenGLBindings):
1026    """ The _QOpenGLFunctions_2_0 bindings. """
1027
1028    def __init__(self, project):
1029        """ Initialise the bindings. """
1030
1031        super().__init__(project, '2_0')
1032
1033
1034class _QOpenGLFunctions_2_1(OpenGLBindings):
1035    """ The _QOpenGLFunctions_2_1 bindings. """
1036
1037    def __init__(self, project):
1038        """ Initialise the bindings. """
1039
1040        super().__init__(project, '2_1')
1041
1042
1043class _QOpenGLFunctions_4_1_Core(OpenGLBindings):
1044    """ The _QOpenGLFunctions_4_1_Core bindings. """
1045
1046    def __init__(self, project):
1047        """ Initialise the bindings. """
1048
1049        super().__init__(project, '4_1_Core')
1050
1051
1052class _QOpenGLFunctions_ES2(OpenGLBindings):
1053    """ The _QOpenGLFunctions_ES2 bindings. """
1054
1055    def __init__(self, project):
1056        """ Initialise the bindings. """
1057
1058        super().__init__(project, 'ES2')
1059
1060    def is_desktop_opengl(self):
1061        """ Return True if the bindings are for desktop OpenGL. """
1062
1063        return False
1064
1065
1066class pylupdate(PyQtBindings):
1067    """ The pylupdate bindings. """
1068
1069    def __init__(self, project):
1070        """ Initialise the bindings. """
1071
1072        super().__init__(project, 'pylupdate', qmake_QT=['xml', '-gui'],
1073                internal=True)
1074
1075    def generate(self):
1076        """ Generate the bindings source code and return the corresponding
1077        buildable.
1078        """
1079
1080        # This is re-implemented so that we can update the buildable to include
1081        # pylupdate_main.py.
1082
1083        project = self.project
1084
1085        buildable = super().generate()
1086
1087        installable = Installable('pylupdate_main', target_subdir='PyQt5')
1088        installable.files.append(
1089                os.path.join(project.root_dir, 'pylupdate',
1090                        'pylupdate_main.py'))
1091        buildable.installables.append(installable)
1092
1093        return buildable
1094
1095    def is_buildable(self):
1096        """ Return True if the bindings are buildable. """
1097
1098        return self.project.tools
1099
1100
1101class pyrcc(PyQtBindings):
1102    """ The pyrcc bindings. """
1103
1104    def __init__(self, project):
1105        """ Initialise the bindings. """
1106
1107        super().__init__(project, 'pyrcc', qmake_QT=['xml', '-gui'],
1108                internal=True)
1109
1110    def generate(self):
1111        """ Generate the bindings source code and return the corresponding
1112        buildable.
1113        """
1114
1115        # This is re-implemented so that we can update the buildable to include
1116        # pyrcc_main.py.
1117
1118        project = self.project
1119
1120        buildable = super().generate()
1121
1122        installable = Installable('pyrcc_main', target_subdir='PyQt5')
1123        installable.files.append(
1124                os.path.join(project.root_dir, 'pyrcc', 'pyrcc_main.py'))
1125        buildable.installables.append(installable)
1126
1127        return buildable
1128
1129    def is_buildable(self):
1130        """ Return True if the bindings are buildable. """
1131
1132        return self.project.tools
1133