1# This script generates the Makefiles for building PyQt5.
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
21from distutils import sysconfig
22import glob
23import optparse
24import os
25import shutil
26import stat
27import sys
28
29
30# Initialise the constants.
31PYQT_VERSION_STR = "5.15.4"
32SIP_MIN_VERSION = '4.19.23'
33
34
35class ModuleMetadata:
36    """ This class encapsulates the meta-data about a PyQt5 module. """
37
38    def __init__(self, qmake_QT=None, qmake_TARGET='', qpy_lib=False, cpp11=False, public=True):
39        """ Initialise the meta-data. """
40
41        # The values to update qmake's QT variable.
42        self.qmake_QT = [] if qmake_QT is None else qmake_QT
43
44        # The value to set qmake's TARGET variable to.  It defaults to the name
45        # of the module.
46        self.qmake_TARGET = qmake_TARGET
47
48        # Set if there is a qpy support library.
49        self.qpy_lib = qpy_lib
50
51        # Set if C++11 support is required.
52        self.cpp11 = cpp11
53
54        # Set if the module is public.
55        self.public = public
56
57
58# The module meta-data.
59MODULE_METADATA = {
60    'dbus':                 ModuleMetadata(qmake_QT=['-gui'],
61                                    qmake_TARGET='pyqt5'),
62    'QAxContainer':         ModuleMetadata(qmake_QT=['axcontainer']),
63    'Qt':                   ModuleMetadata(qmake_QT=['-core', '-gui']),
64    'QtAndroidExtras':      ModuleMetadata(qmake_QT=['androidextras']),
65    'QtBluetooth':          ModuleMetadata(qmake_QT=['bluetooth']),
66    'QtCore':               ModuleMetadata(qmake_QT=['-gui'], qpy_lib=True),
67    'QtDBus':               ModuleMetadata(qmake_QT=['dbus', '-gui'],
68                                    qpy_lib=True),
69    'QtDesigner':           ModuleMetadata(qmake_QT=['designer'],
70                                    qpy_lib=True),
71    'Enginio':              ModuleMetadata(qmake_QT=['enginio']),
72    'QtGui':                ModuleMetadata(qpy_lib=True),
73    'QtHelp':               ModuleMetadata(qmake_QT=['help']),
74    'QtLocation':           ModuleMetadata(qmake_QT=['location']),
75    'QtMacExtras':          ModuleMetadata(qmake_QT=['macextras']),
76    'QtMultimedia':         ModuleMetadata(qmake_QT=['multimedia']),
77    'QtMultimediaWidgets':  ModuleMetadata(
78                                    qmake_QT=['multimediawidgets',
79                                            'multimedia']),
80    'QtNetwork':            ModuleMetadata(qmake_QT=['network', '-gui']),
81    'QtNfc':                ModuleMetadata(qmake_QT=['nfc', '-gui']),
82    'QtOpenGL':             ModuleMetadata(qmake_QT=['opengl']),
83    'QtPositioning':        ModuleMetadata(qmake_QT=['positioning']),
84    'QtPrintSupport':       ModuleMetadata(qmake_QT=['printsupport']),
85    'QtQml':                ModuleMetadata(qmake_QT=['qml'], qpy_lib=True),
86    'QtQuick':              ModuleMetadata(qmake_QT=['quick'], qpy_lib=True),
87    'QtQuick3D':            ModuleMetadata(qmake_QT=['quick3d']),
88    'QtQuickWidgets':       ModuleMetadata(qmake_QT=['quickwidgets']),
89    'QtRemoteObjects':      ModuleMetadata(qmake_QT=['remoteobjects', '-gui']),
90    'QtSensors':            ModuleMetadata(qmake_QT=['sensors']),
91    'QtSerialPort':         ModuleMetadata(qmake_QT=['serialport']),
92    'QtSql':                ModuleMetadata(qmake_QT=['sql', 'widgets']),
93    'QtSvg':                ModuleMetadata(qmake_QT=['svg']),
94    'QtTest':               ModuleMetadata(qmake_QT=['testlib', 'widgets']),
95    'QtTextToSpeech':       ModuleMetadata(qmake_QT=['texttospeech', '-gui']),
96    'QtWebChannel':         ModuleMetadata(
97                                    qmake_QT=['webchannel', 'network',
98                                            '-gui']),
99    'QtWebKit':             ModuleMetadata(qmake_QT=['webkit', 'network']),
100    'QtWebKitWidgets':      ModuleMetadata(
101                                    qmake_QT=['webkitwidgets',
102                                            'printsupport']),
103    'QtWebSockets':         ModuleMetadata(qmake_QT=['websockets', '-gui']),
104    'QtWidgets':            ModuleMetadata(qmake_QT=['widgets'], qpy_lib=True),
105    'QtWinExtras':          ModuleMetadata(qmake_QT=['winextras', 'widgets']),
106    'QtX11Extras':          ModuleMetadata(qmake_QT=['x11extras']),
107    'QtXml':                ModuleMetadata(qmake_QT=['xml', '-gui']),
108    'QtXmlPatterns':        ModuleMetadata(
109                                    qmake_QT=['xmlpatterns', '-gui',
110                                            'network']),
111
112    # The OpenGL wrappers.
113    '_QOpenGLFunctions_1_0':                ModuleMetadata(public=False),
114    '_QOpenGLFunctions_1_1':                ModuleMetadata(public=False),
115    '_QOpenGLFunctions_1_2':                ModuleMetadata(public=False),
116    '_QOpenGLFunctions_1_3':                ModuleMetadata(public=False),
117    '_QOpenGLFunctions_1_4':                ModuleMetadata(public=False),
118    '_QOpenGLFunctions_1_5':                ModuleMetadata(public=False),
119    '_QOpenGLFunctions_2_0':                ModuleMetadata(public=False),
120    '_QOpenGLFunctions_2_1':                ModuleMetadata(public=False),
121    '_QOpenGLFunctions_3_0':                ModuleMetadata(public=False),
122    '_QOpenGLFunctions_3_1':                ModuleMetadata(public=False),
123    '_QOpenGLFunctions_3_2_Compatibility':  ModuleMetadata(public=False),
124    '_QOpenGLFunctions_3_2_Core':           ModuleMetadata(public=False),
125    '_QOpenGLFunctions_3_3_Compatibility':  ModuleMetadata(public=False),
126    '_QOpenGLFunctions_3_3_Core':           ModuleMetadata(public=False),
127    '_QOpenGLFunctions_4_0_Compatibility':  ModuleMetadata(public=False),
128    '_QOpenGLFunctions_4_0_Core':           ModuleMetadata(public=False),
129    '_QOpenGLFunctions_4_1_Compatibility':  ModuleMetadata(public=False),
130    '_QOpenGLFunctions_4_1_Core':           ModuleMetadata(public=False),
131    '_QOpenGLFunctions_4_2_Compatibility':  ModuleMetadata(public=False),
132    '_QOpenGLFunctions_4_2_Core':           ModuleMetadata(public=False),
133    '_QOpenGLFunctions_4_3_Compatibility':  ModuleMetadata(public=False),
134    '_QOpenGLFunctions_4_3_Core':           ModuleMetadata(public=False),
135    '_QOpenGLFunctions_4_4_Compatibility':  ModuleMetadata(public=False),
136    '_QOpenGLFunctions_4_4_Core':           ModuleMetadata(public=False),
137    '_QOpenGLFunctions_4_5_Compatibility':  ModuleMetadata(public=False),
138    '_QOpenGLFunctions_4_5_Core':           ModuleMetadata(public=False),
139    '_QOpenGLFunctions_ES2':                ModuleMetadata(public=False),
140
141    # Internal modules.
142    'pylupdate':            ModuleMetadata(qmake_QT=['xml', '-gui'],
143                                    qpy_lib=True, public=False),
144    'pyrcc':                ModuleMetadata(qmake_QT=['xml', '-gui'],
145                                    qpy_lib=True, public=False),
146}
147
148
149# The component modules that make up the composite Qt module.  SIP is broken in
150# its handling of composite module in that a component module must be %Included
151# before it is first %Imported.  In other words, a module must appear before
152# any modules that depend on it.
153COMPOSITE_COMPONENTS = (
154    'QtCore',
155    'QtAndroidExtras', 'QtDBus', 'QtGui', 'QtNetwork',
156    'QtSensors', 'QtSerialPort', 'QtMultimedia', 'QtQml', 'QtWebKit',
157    'QtWidgets', 'QtXml', 'QtXmlPatterns', 'QtAxContainer', 'QtDesigner',
158    'QtHelp', 'QtMultimediaWidgets', 'QtOpenGL',
159        'QtPrintSupport', 'QtQuick', 'QtSql', 'QtSvg', 'QtTest',
160    'QtWebKitWidgets', 'QtBluetooth', 'QtMacExtras', 'QtPositioning',
161        'QtWinExtras', 'QtX11Extras', 'QtQuick3D', 'QtQuickWidgets',
162        'QtWebSockets', 'Enginio', 'QtWebChannel',
163    'QtLocation', 'QtNfc', 'QtRemoteObjects', 'QtTextToSpeech'
164)
165
166
167def error(msg):
168    """ Display an error message and terminate.  msg is the text of the error
169    message.
170    """
171
172    sys.stderr.write(format("Error: " + msg) + "\n")
173    sys.exit(1)
174
175
176def inform(msg):
177    """ Display an information message.  msg is the text of the error message.
178    """
179
180    sys.stdout.write(format(msg) + "\n")
181
182
183def format(msg, left_margin=0, right_margin=78):
184    """ Format a message by inserting line breaks at appropriate places.  msg
185    is the text of the message.  left_margin is the position of the left
186    margin.  right_margin is the position of the right margin.  Returns the
187    formatted message.
188    """
189
190    curs = left_margin
191    fmsg = " " * left_margin
192
193    for w in msg.split():
194        l = len(w)
195        if curs != left_margin and curs + l > right_margin:
196            fmsg = fmsg + "\n" + (" " * left_margin)
197            curs = left_margin
198
199        if curs > left_margin:
200            fmsg = fmsg + " "
201            curs = curs + 1
202
203        fmsg = fmsg + w
204        curs = curs + l
205
206    return fmsg
207
208
209def version_to_sip_tag(version):
210    """ Convert a version number to a SIP tag.  version is the version number.
211    """
212
213    # Anything after Qt v5 is assumed to be Qt v6.0.
214    if version > 0x060000:
215        version = 0x060000
216
217    major = (version >> 16) & 0xff
218    minor = (version >> 8) & 0xff
219    patch = version & 0xff
220
221    # Qt v5.12.4 was the last release where we updated for a patch version.
222    if (major, minor) >= (5, 13):
223        patch = 0
224    elif (major, minor) == (5, 12):
225        if patch > 4:
226            patch = 4
227
228    return 'Qt_%d_%d_%d' % (major, minor, patch)
229
230
231def version_to_string(version, parts=3):
232    """ Convert an n-part version number encoded as a hexadecimal value to a
233    string.  version is the version number.  Returns the string.
234    """
235
236    part_list = [str((version >> 16) & 0xff)]
237
238    if parts > 1:
239        part_list.append(str((version >> 8) & 0xff))
240
241        if parts > 2:
242            part_list.append(str(version & 0xff))
243
244    return '.'.join(part_list)
245
246
247class ConfigurationFileParser:
248    """ A parser for configuration files. """
249
250    def __init__(self, config_file):
251        """ Read and parse a configuration file. """
252
253        self._config = {}
254        self._extrapolating = []
255
256        cfg = open(config_file)
257        line_nr = 0
258        last_name = None
259
260        section = ''
261        section_config = {}
262        self._config[section] = section_config
263
264        for l in cfg:
265            line_nr += 1
266
267            # Strip comments.
268            l = l.split('#')[0]
269
270            # See if this might be part of a multi-line.
271            multiline = (last_name is not None and len(l) != 0 and l[0] == ' ')
272
273            l = l.strip()
274
275            if l == '':
276                last_name = None
277                continue
278
279            # See if this is a new section.
280            if l[0] == '[' and l[-1] == ']':
281                section = l[1:-1].strip()
282                if section == '':
283                    error(
284                            "%s:%d: Empty section name." % (
285                                    config_file, line_nr))
286
287                if section in self._config:
288                    error(
289                            "%s:%d: Section '%s' defined more than once." % (
290                                    config_file, line_nr, section))
291
292                section_config = {}
293                self._config[section] = section_config
294
295                last_name = None
296                continue
297
298            parts = l.split('=', 1)
299            if len(parts) == 2:
300                name = parts[0].strip()
301                value = parts[1].strip()
302            elif multiline:
303                name = last_name
304                value = section_config[last_name]
305                value += ' ' + l
306            else:
307                name = value = ''
308
309            if name == '' or value == '':
310                error("%s:%d: Invalid line." % (config_file, line_nr))
311
312            section_config[name] = value
313            last_name = name
314
315        cfg.close()
316
317    def sections(self):
318        """ Return the list of sections, excluding the default one. """
319
320        return [s for s in self._config.keys() if s != '']
321
322    def preset(self, name, value):
323        """ Add a preset value to the configuration. """
324
325        self._config[''][name] = value
326
327    def get(self, section, name, default=None):
328        """ Get a configuration value while extrapolating. """
329
330        # Get the name from the section, or the default section.
331        value = self._config[section].get(name)
332        if value is None:
333            value = self._config[''].get(name)
334            if value is None:
335                if default is None:
336                    error(
337                            "Configuration file references non-existent name "
338                            "'%s'." % name)
339
340                return default
341
342        # Handle any extrapolations.
343        parts = value.split('%(', 1)
344        while len(parts) == 2:
345            prefix, tail = parts
346
347            parts = tail.split(')', 1)
348            if len(parts) != 2:
349                error(
350                        "Configuration file contains unterminated "
351                        "extrapolated name '%s'." % tail)
352
353            xtra_name, suffix = parts
354
355            if xtra_name in self._extrapolating:
356                error(
357                        "Configuration file contains a recursive reference to "
358                        "'%s'." % xtra_name)
359
360            self._extrapolating.append(xtra_name)
361            xtra_value = self.get(section, xtra_name)
362            self._extrapolating.pop()
363
364            value = prefix + xtra_value + suffix
365
366            parts = value.split('%(', 1)
367
368        return value
369
370    def getboolean(self, section, name, default):
371        """ Get a boolean configuration value while extrapolating. """
372
373        value = self.get(section, name, default)
374
375        # In case the default was returned.
376        if isinstance(value, bool):
377            return value
378
379        if value in ('True', 'true', '1'):
380            return True
381
382        if value in ('False', 'false', '0'):
383            return False
384
385        error(
386                "Configuration file contains invalid boolean value for "
387                "'%s'." % name)
388
389    def getlist(self, section, name, default):
390        """ Get a list configuration value while extrapolating. """
391
392        value = self.get(section, name, default)
393
394        # In case the default was returned.
395        if isinstance(value, list):
396            return value
397
398        return value.split()
399
400
401class HostPythonConfiguration:
402    """ A container for the host Python configuration. """
403
404    def __init__(self):
405        """ Initialise the configuration. """
406
407        self.platform = sys.platform
408        self.version = sys.hexversion >> 8
409
410        self.inc_dir = sysconfig.get_python_inc()
411        self.venv_inc_dir = sysconfig.get_python_inc(prefix=sys.prefix)
412        self.module_dir = sysconfig.get_python_lib(plat_specific=1)
413        self.debug = hasattr(sys, 'gettotalrefcount')
414
415        if sys.platform == 'win32':
416            bin_dir = sys.exec_prefix
417
418            try:
419                # Python v3.3 and later.
420                base_prefix = sys.base_prefix
421
422                if sys.exec_prefix != sys.base_exec_prefix:
423                    bin_dir += '\\Scripts'
424
425            except AttributeError:
426                try:
427                    # virtualenv for Python v2.
428                    base_prefix = sys.real_prefix
429                    bin_dir += '\\Scripts'
430
431                except AttributeError:
432                    # We can't detect the base prefix in Python v3 prior to
433                    # v3.3.
434                    base_prefix = sys.prefix
435
436            self.bin_dir = bin_dir
437            self.data_dir = sys.prefix
438            self.lib_dir = base_prefix + '\\libs'
439        else:
440            self.bin_dir = sys.exec_prefix + '/bin'
441            self.data_dir = sys.prefix + '/share'
442            self.lib_dir = sys.prefix + '/lib'
443
444        # The name of the interpreter used by the pyuic5 wrapper.
445        if sys.platform == 'darwin':
446            # The installation of MacOS's python is a mess that changes from
447            # version to version and where sys.executable is useless.
448
449            py_major = self.version >> 16
450            py_minor = (self.version >> 8) & 0xff
451
452            # In Python v3.4 and later there is no pythonw.
453            if (py_major == 3 and py_minor >= 4) or py_major >= 4:
454                exe = "python"
455            else:
456                exe = "pythonw"
457
458            self.pyuic_interpreter = '%s%d.%d' % (exe, py_major, py_minor)
459        else:
460            self.pyuic_interpreter = sys.executable
461
462
463class TargetQtConfiguration:
464    """ A container for the target Qt configuration. """
465
466    def __init__(self, qmake):
467        """ Initialise the configuration.  qmake is the full pathname of the
468        qmake executable that will provide the configuration.
469        """
470
471        inform("Querying qmake about your Qt installation...")
472
473        pipe = os.popen(' '.join([qmake, '-query']))
474
475        for l in pipe:
476            l = l.strip()
477
478            tokens = l.split(':', 1)
479            if isinstance(tokens, list):
480                if len(tokens) != 2:
481                    error("Unexpected output from qmake: '%s'\n" % l)
482
483                name, value = tokens
484            else:
485                name = tokens
486                value = None
487
488            name = name.replace('/', '_')
489
490            setattr(self, name, value)
491
492        pipe.close()
493
494
495class TargetConfiguration:
496    """ A container for configuration information about the target. """
497
498    def __init__(self):
499        """ Initialise the configuration with default values. """
500
501        # Values based on the host Python configuration.
502        py_config = HostPythonConfiguration()
503        self.py_debug = py_config.debug
504        self.py_inc_dir = py_config.inc_dir
505        self.py_venv_inc_dir = py_config.venv_inc_dir
506        self.py_lib_dir = py_config.lib_dir
507        self.py_platform = py_config.platform
508        self.py_version = py_config.version
509        self.pyqt_bin_dir = py_config.bin_dir
510        self.pyqt_module_dir = py_config.module_dir
511        self.pyqt_stubs_dir = os.path.join(py_config.module_dir, 'PyQt5')
512        self.pyqt_sip_dir = os.path.join(py_config.data_dir, 'sip', 'PyQt5')
513        self.pyuic_interpreter = py_config.pyuic_interpreter
514
515        # Remaining values.
516        self.abi_version = None
517        self.dbus_inc_dirs = []
518        self.dbus_lib_dirs = []
519        self.dbus_libs = []
520        self.debug = False
521        self.designer_plugin_dir = ''
522        self.license_dir = source_path('sip')
523        self.link_full_dll = False
524        self.no_designer_plugin = False
525        self.no_docstrings = False
526        self.no_pydbus = False
527        self.no_qml_plugin = False
528        self.no_tools = False
529        self.prot_is_public = (self.py_platform.startswith('linux') or self.py_platform.startswith('freebsd') or self.py_platform == 'darwin')
530        self.qmake = self._find_exe('qmake')
531        self.qmake_spec = ''
532        self.qmake_spec_default = ''
533        self.qmake_variables = []
534        self.qml_debug = False
535        self.py_pylib_dir = ''
536        self.py_pylib_lib = ''
537        self.py_pyshlib = ''
538        self.pydbus_inc_dir = ''
539        self.pydbus_module_dir = ''
540        self.pyqt_disabled_features = []
541        self.pyqt_modules = []
542        self.qml_plugin_dir = ''
543        self.qsci_api = False
544        self.qsci_api_dir = ''
545        self.qt_shared = False
546        self.qt_version = 0
547        self.sip = self._find_exe('sip5', 'sip')
548        self.sip_inc_dir = None
549        self.static = False
550        self.sysroot = ''
551        self.vend_enabled = False
552        self.vend_inc_dir = ''
553        self.vend_lib_dir = ''
554
555    def from_configuration_file(self, config_file):
556        """ Initialise the configuration with values from a file.  config_file
557        is the name of the configuration file.
558        """
559
560        inform("Reading configuration from %s..." % config_file)
561
562        parser = ConfigurationFileParser(config_file)
563
564        # Populate some presets from the command line.
565        version = version_to_string(self.py_version).split('.')
566        parser.preset('py_major', version[0])
567        parser.preset('py_minor', version[1])
568
569        parser.preset('sysroot', self.sysroot)
570
571        # Find the section corresponding to the version of Qt.
572        qt_major = self.qt_version >> 16
573        section = None
574        latest_section = -1
575
576        for name in parser.sections():
577            parts = name.split()
578            if len(parts) != 2 or parts[0] != 'Qt':
579                continue
580
581            section_qt_version = version_from_string(parts[1])
582            if section_qt_version is None:
583                continue
584
585            # Major versions must match.
586            if section_qt_version >> 16 != self.qt_version >> 16:
587                continue
588
589            # It must be no later that the version of Qt.
590            if section_qt_version > self.qt_version:
591                continue
592
593            # Save it if it is the latest so far.
594            if section_qt_version > latest_section:
595                section = name
596                latest_section = section_qt_version
597
598        if section is None:
599            error("%s does not define a section that covers Qt v%s." % (config_file, version_to_string(self.qt_version)))
600
601        self.py_platform = parser.get(section, 'py_platform', self.py_platform)
602        self.py_debug = parser.get(section, 'py_debug', self.py_debug)
603        self.py_inc_dir = parser.get(section, 'py_inc_dir', self.py_inc_dir)
604        self.py_venv_inc_dir = self.py_inc_dir
605        self.py_pylib_dir = parser.get(section, 'py_pylib_dir',
606                self.py_pylib_dir)
607        self.py_pylib_lib = parser.get(section, 'py_pylib_lib',
608                self.py_pylib_lib)
609        self.py_pyshlib = parser.get(section, 'py_pyshlib', self.py_pyshlib)
610
611        self.qt_shared = parser.getboolean(section, 'qt_shared',
612                self.qt_shared)
613
614        self.pyqt_disabled_features = parser.getlist(section,
615                'pyqt_disabled_features', self.pyqt_disabled_features)
616        self.pyqt_modules = parser.getlist(section, 'pyqt_modules',
617                self.pyqt_modules)
618        self.pyqt_module_dir = parser.get(section, 'pyqt_module_dir',
619                self.pyqt_module_dir)
620        self.pyqt_bin_dir = parser.get(section, 'pyqt_bin_dir',
621                self.pyqt_bin_dir)
622        self.pyqt_stubs_dir = parser.get(section, 'pyqt_stubs_dir',
623                self.pyqt_stubs_dir)
624        self.pyqt_sip_dir = parser.get(section, 'pyqt_sip_dir',
625                self.pyqt_sip_dir)
626        self.pyuic_interpreter = parser.get(section, 'pyuic_interpreter',
627                self.pyuic_interpreter)
628
629    def from_introspection(self, verbose, debug):
630        """ Initialise the configuration by introspecting the system. """
631
632        # Check that the enum module is available.
633        try:
634            import enum
635        except ImportError:
636            error(
637                    "Unable to import enum.  Please install the enum34 "
638                    "package from PyPI.")
639
640        # Get the details of the Python interpreter library.
641        py_major = self.py_version >> 16
642        py_minor = (self.py_version >> 8) & 0x0ff
643
644        if sys.platform == 'win32':
645            debug_suffix = self.get_win32_debug_suffix()
646
647            # See if we are using the limited API.
648            limited = (py_major == 3 and py_minor >= 4)
649            if self.py_debug or self.link_full_dll:
650                limited = False
651
652            if limited:
653                pylib_lib = 'python%d%s' % (py_major, debug_suffix)
654            else:
655                pylib_lib = 'python%d%d%s' % (py_major, py_minor, debug_suffix)
656
657            pylib_dir = self.py_lib_dir
658
659            # Assume Python is a DLL on Windows.
660            pyshlib = pylib_lib
661        else:
662            abi = getattr(sys, 'abiflags', '')
663            pylib_lib = 'python%d.%d%s' % (py_major, py_minor, abi)
664            pylib_dir = pyshlib = ''
665
666            # Use distutils to get the additional configuration.
667            ducfg = sysconfig.get_config_vars()
668
669            config_args = ducfg.get('CONFIG_ARGS', '')
670
671            dynamic_pylib = '--enable-shared' in config_args
672            if not dynamic_pylib:
673                dynamic_pylib = '--enable-framework' in config_args
674
675            if dynamic_pylib:
676                pyshlib = ducfg.get('LDLIBRARY', '')
677
678                exec_prefix = ducfg['exec_prefix']
679                multiarch = ducfg.get('MULTIARCH', '')
680                libdir = ducfg['LIBDIR']
681
682                if glob.glob('%s/lib/libpython%d.%d*' % (exec_prefix, py_major, py_minor)):
683                    pylib_dir = exec_prefix + '/lib'
684                elif multiarch != '' and glob.glob('%s/lib/%s/libpython%d.%d*' % (exec_prefix, multiarch, py_major, py_minor)):
685                    pylib_dir = exec_prefix + '/lib/' + multiarch
686                elif glob.glob('%s/libpython%d.%d*' % (libdir, py_major, py_minor)):
687                    pylib_dir = libdir
688
689        self.py_pylib_dir = pylib_dir
690        self.py_pylib_lib = pylib_lib
691        self.py_pyshlib = pyshlib
692
693        # Apply sysroot where necessary.
694        if self.sysroot != '':
695            self.py_inc_dir = self._apply_sysroot(self.py_inc_dir)
696            self.py_venv_inc_dir = self._apply_sysroot(self.py_venv_inc_dir)
697            self.py_pylib_dir = self._apply_sysroot(self.py_pylib_dir)
698            self.pyqt_bin_dir = self._apply_sysroot(self.pyqt_bin_dir)
699            self.pyqt_module_dir = self._apply_sysroot(self.pyqt_module_dir)
700            self.pyqt_stubs_dir = self._apply_sysroot(self.pyqt_stubs_dir)
701            self.pyqt_sip_dir = self._apply_sysroot(self.pyqt_sip_dir)
702
703        inform("Determining the details of your Qt installation...")
704
705        # Compile and run the QtCore test program.
706        test = compile_test_program(self, verbose, 'QtCore', debug=debug)
707        if test is None:
708            error("Failed to determine the detail of your Qt installation. Try again using the --verbose flag to see more detail about the problem.")
709
710        lines = run_test_program('QtCore', test, verbose)
711
712        self.qt_shared = (lines[0] == 'shared')
713        self.pyqt_disabled_features = lines[1:]
714
715        if self.pyqt_disabled_features:
716            inform("Disabled QtCore features: %s" % ', '.join(
717                    self.pyqt_disabled_features))
718
719    def _apply_sysroot(self, dir_name):
720        """ Replace any leading sys.prefix of a directory name with sysroot.
721        """
722
723        if dir_name.startswith(sys.prefix):
724            dir_name = self.sysroot + dir_name[len(sys.prefix):]
725
726        return dir_name
727
728    def get_win32_debug_suffix(self):
729        """ Return the debug-dependent suffix appended to the name of Windows
730        libraries.
731        """
732
733        return '_d' if self.py_debug else ''
734
735    def get_qt_configuration(self):
736        """ Get the Qt configuration that can be extracted from qmake. """
737
738        # Query qmake.
739        qt_config = TargetQtConfiguration(self.qmake)
740
741        self.qt_version = 0
742        try:
743            qt_version_str = qt_config.QT_VERSION
744            for v in qt_version_str.split('.'):
745                self.qt_version *= 256
746                self.qt_version += int(v)
747        except AttributeError:
748            qt_version_str = "3"
749
750        # Check the Qt version number as soon as possible.
751        if self.qt_version < 0x050000:
752            error(
753                    "PyQt5 requires Qt v5.0 or later. You seem to be using "
754                    "v%s. Use the --qmake flag to specify the correct version "
755                    "of qmake." % qt_version_str)
756
757        self.designer_plugin_dir = qt_config.QT_INSTALL_PLUGINS + '/designer'
758        self.qml_plugin_dir = qt_config.QT_INSTALL_PLUGINS + '/PyQt5'
759
760        if self.sysroot == '':
761            self.sysroot = qt_config.QT_SYSROOT
762
763        # By default, install the API file if QScintilla seems to be installed
764        # in the default location.
765        self.qsci_api_dir = os.path.join(qt_config.QT_INSTALL_DATA, 'qsci')
766        self.qsci_api = os.path.isdir(self.qsci_api_dir)
767
768        # Save the default qmake spec. and finalise the value we want to use.
769        self.qmake_spec_default = qt_config.QMAKE_SPEC
770
771        # On Windows for Qt versions prior to v5.9.0 we need to be explicit
772        # about the qmake spec.
773        if self.qt_version < 0x050900 and self.py_platform == 'win32':
774            if self.py_version >= 0x030500:
775                self.qmake_spec = 'win32-msvc2015'
776            elif self.py_version >= 0x030300:
777                self.qmake_spec = 'win32-msvc2010'
778            elif self.py_version >= 0x020600:
779                self.qmake_spec = 'win32-msvc2008'
780            elif self.py_version >= 0x020400:
781                self.qmake_spec = 'win32-msvc.net'
782            else:
783                self.qmake_spec = 'win32-msvc'
784        else:
785            # Otherwise use the default.
786            self.qmake_spec = self.qmake_spec_default
787
788        # The binary OS/X Qt installer used to default to XCode.  If so then
789        # use macx-clang.
790        if self.qmake_spec == 'macx-xcode':
791            # This will exist (and we can't check anyway).
792            self.qmake_spec = 'macx-clang'
793
794    def post_configuration(self):
795        """ Handle any remaining default configuration after having read a
796        configuration file or introspected the system.
797        """
798
799        # The platform may have changed so update the default.
800        if self.py_platform.startswith('linux') or self.py_platform.startswith('freebsd') or self.py_platform == 'darwin':
801            self.prot_is_public = True
802
803        self.vend_inc_dir = self.py_venv_inc_dir
804        self.vend_lib_dir = self.py_lib_dir
805
806    def apply_pre_options(self, opts):
807        """ Apply options from the command line that influence subsequent
808        configuration.  opts are the command line options.
809        """
810
811        # On Windows the interpreter must be a debug build if a debug version
812        # is to be built and vice versa.
813        if sys.platform == 'win32':
814            if opts.debug:
815                if not self.py_debug:
816                    error(
817                            "A debug version of Python must be used when "
818                            "--debug is specified.")
819            elif self.py_debug:
820                error(
821                        "--debug must be specified when a debug version of "
822                        "Python is used.")
823
824        self.debug = opts.debug
825
826        # Get the target Python version.
827        if opts.target_py_version is not None:
828            self.py_version = opts.target_py_version
829
830        # Get the system root.
831        if opts.sysroot is not None:
832            self.sysroot = opts.sysroot
833
834        # Determine how to run qmake.
835        if opts.qmake is not None:
836            self.qmake = opts.qmake
837
838            # On Windows add the directory that probably contains the Qt DLLs
839            # to PATH.
840            if sys.platform == 'win32':
841                path = os.environ['PATH']
842                path = os.path.dirname(self.qmake) + ';' + path
843                os.environ['PATH'] = path
844
845        if self.qmake is None:
846            error(
847                    "Use the --qmake argument to explicitly specify a working "
848                    "Qt qmake.")
849
850        if opts.qmakespec is not None:
851            self.qmake_spec = opts.qmakespec
852
853        if opts.sipincdir is not None:
854            self.sip_inc_dir = opts.sipincdir
855
856    def apply_post_options(self, opts):
857        """ Apply options from the command line that override the previous
858        configuration.  opts are the command line options.
859        """
860
861        self.pyqt_disabled_features.extend(opts.disabled_features)
862
863        if opts.assumeshared:
864            self.qt_shared = True
865
866        if opts.bindir is not None:
867            self.pyqt_bin_dir = opts.bindir
868
869        if opts.licensedir is not None:
870            self.license_dir = opts.licensedir
871
872        if opts.link_full_dll:
873            self.link_full_dll = True
874
875        if opts.designerplugindir is not None:
876            self.designer_plugin_dir = opts.designerplugindir
877
878        if opts.qmlplugindir is not None:
879            self.qml_plugin_dir = opts.qmlplugindir
880
881        if opts.destdir is not None:
882            self.pyqt_module_dir = opts.destdir
883
884        if len(opts.modules) > 0:
885            self.pyqt_modules = opts.modules
886
887        if opts.nodesignerplugin:
888            self.no_designer_plugin = True
889
890        if opts.nodocstrings:
891            self.no_docstrings = True
892
893        if opts.nopydbus:
894            self.no_pydbus = True
895
896        if opts.noqmlplugin:
897            self.no_qml_plugin = True
898
899        if opts.notools:
900            self.no_tools = True
901
902        if opts.protispublic is not None:
903            self.prot_is_public = opts.protispublic
904
905        if opts.pydbusincdir is not None:
906            self.pydbus_inc_dir = opts.pydbusincdir
907
908        if opts.pyuicinterpreter is not None:
909            self.pyuic_interpreter = opts.pyuicinterpreter
910
911        if opts.qml_debug:
912            self.qml_debug = True
913
914        if opts.qsciapidir is not None:
915            self.qsci_api_dir = opts.qsciapidir
916
917            # Assume we want to install the API file if we have provided an
918            # installation directory.
919            self.qsci_api = True
920
921        if opts.qsciapi is not None:
922            self.qsci_api = opts.qsciapi
923
924        if opts.qsciapidir is not None:
925            self.qsci_api_dir = opts.qsciapidir
926
927        if opts.stubsdir is not None:
928            self.pyqt_stubs_dir = opts.stubsdir
929        elif not opts.install_stubs:
930            self.pyqt_stubs_dir = ''
931
932        if opts.sip is not None:
933            self.sip = opts.sip
934
935        if opts.abi_version is not None:
936            if not self.using_sip5():
937                error("The --abi-version argument can only be used with sip5.")
938
939            self.abi_version = opts.abi_version
940
941        if opts.sipdir is not None:
942            self.pyqt_sip_dir = opts.sipdir
943        elif not opts.install_sipfiles:
944            self.pyqt_sip_dir = ''
945
946        if opts.static:
947            self.static = True
948
949        if opts.vendenabled:
950            self.vend_enabled = True
951
952        if opts.vendincdir is not None:
953            self.vend_inc_dir = opts.vendincdir
954
955        if opts.vendlibdir is not None:
956            self.vend_lib_dir = opts.vendlibdir
957
958        # Handle any conflicts.
959        if not self.qt_shared:
960            if not self.static:
961                error(
962                        "Qt has been built as static libraries so the "
963                        "--static argument should be used.")
964
965        if self.vend_enabled and self.static:
966            error(
967                    "Using the VendorID package when building static "
968                    "libraries makes no sense.")
969
970    def get_pylib_link_arguments(self, name=True):
971        """ Return a string to append to qmake's LIBS macro to link against the
972        Python interpreter library.
973        """
974
975        args = qmake_quote('-L' + self.py_pylib_dir)
976
977        if name:
978            args += ' -l' + self.py_pylib_lib
979
980        return args
981
982    def add_sip_h_directives(self, pro_lines):
983        """ Add the directives required by sip.h to a sequence of .pro file
984        lines.
985        """
986
987        # Make sure the include directory is searched before the Python include
988        # directory if they are different.
989        pro_lines.append('INCLUDEPATH += %s' % qmake_quote(self.sip_inc_dir))
990        if self.py_inc_dir != self.sip_inc_dir:
991            pro_lines.append('INCLUDEPATH += %s' % qmake_quote(self.py_inc_dir))
992
993        # Python.h on Windows seems to embed the need for pythonXY.lib, so tell
994        # it where it is.
995        if not self.static:
996            pro_lines.extend(['win32 {',
997                    '    LIBS += ' + self.get_pylib_link_arguments(name=False),
998                    '}'])
999
1000    def using_sip5(self):
1001        """ Return True if sip5 is being used. """
1002
1003        return os.path.basename(self.sip).startswith('sip5')
1004
1005    @staticmethod
1006    def _find_exe(*exes):
1007        """ Find an executable, ie. the first on the path. """
1008
1009        path_dirs = os.environ.get('PATH', '').split(os.pathsep)
1010
1011        for exe in exes:
1012            # Strip any surrounding quotes.
1013            if exe.startswith('"') and exe.endswith('"'):
1014                exe = exe[1:-1]
1015
1016            if sys.platform == 'win32':
1017                exe = exe + '.exe'
1018
1019            for d in path_dirs:
1020                exe_path = os.path.join(d, exe)
1021
1022                if os.access(exe_path, os.X_OK):
1023                    return exe_path
1024
1025        return None
1026
1027
1028def create_optparser(target_config):
1029    """ Create the parser for the command line.  target_config is the target
1030    configuration containing default values.
1031    """
1032
1033    def store_abspath(option, opt_str, value, parser):
1034        setattr(parser.values, option.dest, os.path.abspath(value))
1035
1036    def store_abspath_dir(option, opt_str, value, parser):
1037        if not os.path.isdir(value):
1038            raise optparse.OptionValueError("'%s' is not a directory" % value)
1039        setattr(parser.values, option.dest, os.path.abspath(value))
1040
1041    def store_abspath_exe(option, opt_str, value, parser):
1042        if not os.access(value, os.X_OK):
1043            raise optparse.OptionValueError("'%s' is not an executable" % value)
1044        setattr(parser.values, option.dest, os.path.abspath(value))
1045
1046    def store_abspath_file(option, opt_str, value, parser):
1047        if not os.path.isfile(value):
1048            raise optparse.OptionValueError("'%s' is not a file" % value)
1049        setattr(parser.values, option.dest, os.path.abspath(value))
1050
1051    def store_version(option, opt_str, value, parser):
1052        version = version_from_string(value)
1053        if version is None:
1054            raise optparse.OptionValueError(
1055                    "'%s' is not a valid version number" % value)
1056        setattr(parser.values, option.dest, version)
1057
1058    p = optparse.OptionParser(usage="python %prog [opts] [name=value] "
1059            "[name+=value]", version=PYQT_VERSION_STR)
1060
1061    # Note: we don't use %default to be compatible with Python 2.3.
1062    p.add_option("--abi-version", dest='abi_version', default=None,
1063            metavar="VERSION",
1064            help="the SIP ABI version to use (sip5 only)")
1065    p.add_option("--static", "-k", dest='static', default=False,
1066            action='store_true',
1067            help="build modules as static libraries")
1068    p.add_option("--no-docstrings", dest='nodocstrings', default=False,
1069            action='store_true',
1070            help="disable the generation of docstrings")
1071    p.add_option("--trace", "-r", dest='tracing', default=False,
1072            action='store_true',
1073            help="build modules with tracing enabled")
1074    p.add_option("--debug", "-u", dest='debug', default=False,
1075            action='store_true',
1076            help="build modules with debugging symbols")
1077    p.add_option("--qml-debug", dest='qml_debug', default=False,
1078            action='store_true',
1079            help="enable the QML debugging infrastructure")
1080    p.add_option("--verbose", "-w", dest='verbose', default=False,
1081            action='store_true',
1082            help="enable verbose output during configuration")
1083
1084    p.add_option("--concatenate", "-c", dest='concat', default=False,
1085            action='store_true',
1086            help="concatenate each module's C++ source files")
1087    p.add_option("--concatenate-split", "-j", dest='split', type='int',
1088            default=1, metavar="N",
1089            help="split the concatenated C++ source files into N pieces "
1090                    "[default: 1]")
1091
1092    # Configuration.
1093    g = optparse.OptionGroup(p, title="Configuration")
1094    g.add_option("--confirm-license", dest='license_confirmed', default=False,
1095            action='store_true',
1096            help="confirm acceptance of the license")
1097    g.add_option("--license-dir", dest='licensedir', type='string',
1098            default=None, action='callback', callback=store_abspath,
1099            metavar="DIR",
1100            help="the license file can be found in DIR [default: "
1101                    "%s]" % target_config.license_dir)
1102    g.add_option("--target-py-version", dest='target_py_version',
1103            type='string', action='callback', callback=store_version,
1104            metavar="VERSION",
1105            help="the major.minor version of the target Python [default: "
1106                    "%s]" % version_to_string(target_config.py_version,
1107                            parts=2))
1108    g.add_option("--link-full-dll", dest='link_full_dll',
1109            default=False, action='store_true',
1110            help="on Windows link against the full Python DLL rather than the "
1111                    "limited API DLL")
1112    g.add_option("--sysroot", dest='sysroot', type='string', action='callback',
1113            callback=store_abspath_dir, metavar="DIR",
1114            help="DIR is the target system root directory")
1115    g.add_option("--spec", dest='qmakespec', default=None, action='store',
1116            metavar="SPEC",
1117            help="pass -spec SPEC to qmake")
1118    g.add_option("--disable", dest='disabled_modules', default=[],
1119            action='append', metavar="MODULE",
1120            help="disable the specified MODULE [default: checks for all "
1121                    "modules will be enabled]")
1122    g.add_option("--disable-feature", dest='disabled_features', default=[],
1123            action='append', metavar="FEATURE",
1124            help="disable the specified FEATURE")
1125    g.add_option("--enable", "-e", dest='modules', default=[], action='append',
1126            metavar="MODULE",
1127            help="enable checks for the specified MODULE [default: checks for "
1128                    "all modules will be enabled]")
1129    g.add_option("--no-designer-plugin", dest='nodesignerplugin',
1130            default=False, action='store_true',
1131            help="disable the building of the Python plugin for Qt Designer "
1132                    "[default: enabled]")
1133    g.add_option("--no-qml-plugin", dest='noqmlplugin', default=False,
1134            action='store_true',
1135            help="disable the building of the Python plugin for qmlscene "
1136                    "[default: enabled]")
1137    g.add_option("--assume-shared", dest='assumeshared', default=False,
1138            action='store_true',
1139            help="assume that the Qt libraries have been built as shared "
1140                    "libraries [default: check]")
1141    g.add_option("--no-timestamp", "-T", dest='notimestamp', default=False,
1142            action='store_true',
1143            help="suppress timestamps in the header comments of generated "
1144                    "code [default: include timestamps]")
1145    g.add_option("--configuration", dest='config_file', type='string',
1146            action='callback', callback=store_abspath_file, metavar="FILE",
1147            help="FILE contains the target configuration")
1148
1149    g.add_option("--protected-is-public", dest='protispublic', default=None,
1150            action='store_true',
1151            help="enable building with 'protected' redefined as 'public' "
1152                    "[default: %s]" %
1153                            "enabled" if target_config.prot_is_public
1154                            else "disabled")
1155    g.add_option("--protected-not-public", dest='protispublic', default=None,
1156            action='store_false',
1157            help="disable building with 'protected' redefined as 'public'")
1158
1159    g.add_option("--pyuic5-interpreter", dest='pyuicinterpreter',
1160            type='string', default=None, action='callback',
1161            callback=store_abspath_exe, metavar="FILE",
1162            help="the name of the Python interpreter to run the pylupdate5, "
1163                    "pyrcc5 and pyuic5 wrappers is FILE [default: %s]" %
1164                            target_config.pyuic_interpreter)
1165
1166    g.add_option("--qmake", "-q", dest='qmake', type='string', default=None,
1167            action='callback', callback=store_abspath_exe, metavar="FILE",
1168            help="the pathname of qmake is FILE [default: "
1169                    "%s]" % (target_config.qmake or "search PATH"))
1170
1171    g.add_option("--sip", dest='sip', type='string', default=None,
1172            action='callback', callback=store_abspath_exe, metavar="FILE",
1173            help="the pathname of sip is FILE [default: "
1174                    "%s]" % (target_config.sip or "None"))
1175    g.add_option("--sip-incdir", dest='sipincdir', type='string',
1176            default=None, action='callback', callback=store_abspath_dir,
1177            metavar="DIR",
1178            help="the directory containing the sip.h header file is DIR "
1179                    "[default: %s]" % target_config.sip_inc_dir)
1180    g.add_option("--allow-sip-warnings", dest='fatal_warnings',
1181            default=True, action='store_false',
1182            help="allow sip to issue non-fatal warning messages "
1183                    "[default: warning messages are treated as errors]")
1184
1185    g.add_option("--no-python-dbus", dest='nopydbus',
1186            default=False, action='store_true',
1187            help="disable the Qt support for the standard Python DBus "
1188                    "bindings [default: enabled]")
1189    g.add_option("--dbus", "-s", dest='pydbusincdir', type='string',
1190            default=None, action='callback', callback=store_abspath_dir,
1191            metavar="DIR",
1192            help="the directory containing the dbus/dbus-python.h header is "
1193            "DIR [default: supplied by pkg-config]")
1194    p.add_option_group(g)
1195
1196    # Installation.
1197    g = optparse.OptionGroup(p, title="Installation")
1198    g.add_option("--bindir", "-b", dest='bindir', type='string', default=None,
1199            action='callback', callback=store_abspath, metavar="DIR",
1200            help="install pyuic5, pyrcc5 and pylupdate5 in DIR [default: "
1201                    "%s]" % target_config.pyqt_bin_dir)
1202    g.add_option("--destdir", "-d", dest='destdir', type='string',
1203            default=None, action='callback', callback=store_abspath,
1204            metavar="DIR",
1205            help="install the PyQt5 Python package in DIR [default: "
1206                    "%s]" % target_config.pyqt_module_dir)
1207    g.add_option("--designer-plugindir", dest='designerplugindir',
1208            type='string', default=None, action='callback',
1209            callback=store_abspath, metavar="DIR",
1210            help="install the Python plugin for Qt Designer in DIR "
1211                    "[default: QT_INSTALL_PLUGINS/designer]")
1212    g.add_option("--qml-plugindir", dest='qmlplugindir', type='string',
1213            default=None, action='callback', callback=store_abspath,
1214            metavar="DIR",
1215            help="install the Python plugin for qmlscene in DIR "
1216                    "[default: QT_INSTALL_PLUGINS/PyQt5]")
1217    g.add_option("--no-sip-files", action="store_false", default=True,
1218            dest="install_sipfiles", help="disable the installation of the "
1219            ".sip files [default: enabled]")
1220    g.add_option("--sipdir", "-v", dest='sipdir', type='string', default=None,
1221            action='callback', callback=store_abspath, metavar="DIR",
1222            help="install the PyQt5 .sip files in DIR [default: %s]" %
1223                    target_config.pyqt_sip_dir)
1224    g.add_option("--no-dist-info", action="store_false", default=True,
1225            dest="distinfo",
1226            help="do not install the dist-info directory")
1227    g.add_option("--no-stubs", action="store_false", default=True,
1228            dest="install_stubs", help="disable the installation of the PEP "
1229            "484 stub files [default: enabled]")
1230    g.add_option("--stubsdir", dest='stubsdir', type='string', default=None,
1231            action='callback', callback=store_abspath, metavar="DIR",
1232            help="install the PEP 484 stub files in DIR [default: "
1233                    "%s]" % target_config.pyqt_stubs_dir)
1234    g.add_option("--no-tools", action="store_true", default=False,
1235            dest="notools",
1236            help="disable the building of pyuic5, pyrcc5 and pylupdate5 "
1237                    "[default: enabled]")
1238    p.add_option_group(g)
1239
1240    # Vendor ID.
1241    g = optparse.OptionGroup(p, title="VendorID support")
1242    g.add_option("--vendorid", "-i", dest='vendenabled', default=False,
1243            action='store_true',
1244            help="enable checking of signed interpreters using the VendorID "
1245                    "package [default: %s]" %
1246                    "enabled" if target_config.vend_enabled else "disabled")
1247    g.add_option("--vendorid-incdir", "-l", dest='vendincdir', type='string',
1248            default=None, action='callback', callback=store_abspath_dir,
1249            metavar="DIR",
1250            help="the VendorID header file is installed in DIR [default: "
1251                    "%s]" % target_config.vend_inc_dir)
1252    g.add_option("--vendorid-libdir", "-m", dest='vendlibdir', type='string',
1253            default=None, action='callback', callback=store_abspath_dir,
1254            metavar="DIR",
1255            help="the VendorID library is installed in DIR [default: "
1256                    "%s]" % target_config.vend_lib_dir)
1257    p.add_option_group(g)
1258
1259    # QScintilla.
1260    g = optparse.OptionGroup(p, title="QScintilla support")
1261    g.add_option("--qsci-api", "-a", dest='qsciapi', default=None,
1262            action='store_true',
1263            help="always install the PyQt API file for QScintilla [default: "
1264                    "install only if QScintilla installed]")
1265    g.add_option("--no-qsci-api", dest='qsciapi', default=None,
1266            action='store_false',
1267            help="do not install the PyQt API file for QScintilla [default: "
1268                    "install only if QScintilla installed]")
1269    g.add_option("--qsci-api-destdir", "-n", dest='qsciapidir', type='string',
1270            default=None, action='callback', callback=store_abspath,
1271            metavar="DIR",
1272            help="install the PyQt5 API file for QScintilla in DIR [default: "
1273                    "QT_INSTALL_DATA/qsci]")
1274    p.add_option_group(g)
1275
1276    return p
1277
1278
1279def check_modules(target_config, disabled_modules, verbose):
1280    """ Check which modules can be built and update the target configuration
1281    accordingly.  target_config is the target configuration.  disabled_modules
1282    is the list of modules that have been explicitly disabled.  verbose is set
1283    if the output is to be displayed.
1284    """
1285
1286    target_config.pyqt_modules.append('QtCore')
1287
1288    check_module(target_config, disabled_modules, verbose, 'QtGui')
1289    check_module(target_config, disabled_modules, verbose, 'QtHelp',
1290            'qhelpengine.h', 'new QHelpEngine("foo")')
1291    check_module(target_config, disabled_modules, verbose, 'QtMultimedia',
1292            'QAudioDeviceInfo', 'new QAudioDeviceInfo()')
1293    check_module(target_config, disabled_modules, verbose,
1294            'QtMultimediaWidgets', 'QVideoWidget', 'new QVideoWidget()')
1295    check_module(target_config, disabled_modules, verbose, 'QtNetwork')
1296    check_module(target_config, disabled_modules, verbose, 'QtOpenGL', 'qgl.h',
1297            'new QGLWidget()')
1298    check_module(target_config, disabled_modules, verbose, 'QtPrintSupport')
1299    check_module(target_config, disabled_modules, verbose, 'QtQml',
1300            'qjsengine.h', 'new QJSEngine()')
1301    check_module(target_config, disabled_modules, verbose, 'QtQuick',
1302            'qquickwindow.h', 'new QQuickWindow()')
1303    check_module(target_config, disabled_modules, verbose, 'QtSql',
1304            'qsqldatabase.h', 'new QSqlDatabase()')
1305    check_module(target_config, disabled_modules, verbose, 'QtSvg',
1306            'qsvgwidget.h', 'new QSvgWidget()')
1307    check_module(target_config, disabled_modules, verbose, 'QtTest', 'QtTest',
1308            'QTest::qSleep(0)')
1309    check_module(target_config, disabled_modules, verbose, 'QtWebKit',
1310            'qwebkitglobal.h', 'qWebKitVersion()')
1311    check_module(target_config, disabled_modules, verbose, 'QtWebKitWidgets',
1312            'qwebpage.h', 'new QWebPage()')
1313    check_module(target_config, disabled_modules, verbose, 'QtWidgets',
1314            'qwidget.h', 'new QWidget()')
1315    check_module(target_config, disabled_modules, verbose, 'QtXml', 'qdom.h',
1316            'new QDomDocument()')
1317    check_module(target_config, disabled_modules, verbose, 'QtXmlPatterns',
1318            'qxmlname.h', 'new QXmlName()')
1319
1320    if target_config.qt_shared:
1321        check_module(target_config, disabled_modules, verbose, 'QtDesigner',
1322                ('QExtensionFactory', 'customwidget.h'),
1323                'new QExtensionFactory()')
1324    else:
1325        inform("QtDesigner module disabled with static Qt libraries.")
1326
1327    check_module(target_config, disabled_modules, verbose, 'QAxContainer',
1328            'qaxobject.h', 'new QAxObject()')
1329
1330    check_module(target_config, disabled_modules, verbose, 'QtDBus',
1331            'qdbusconnection.h', 'QDBusConnection::systemBus()')
1332
1333    if target_config.qt_version >= 0x050100:
1334        check_5_1_modules(target_config, disabled_modules, verbose)
1335
1336    if target_config.qt_version >= 0x050200:
1337        check_5_2_modules(target_config, disabled_modules, verbose)
1338
1339    if target_config.qt_version >= 0x050300:
1340        check_5_3_modules(target_config, disabled_modules, verbose)
1341
1342    if target_config.qt_version >= 0x050400:
1343        check_5_4_modules(target_config, disabled_modules, verbose)
1344
1345    if target_config.qt_version >= 0x050500:
1346        check_5_5_modules(target_config, disabled_modules, verbose)
1347
1348    if target_config.qt_version >= 0x050c00:
1349        check_5_12_modules(target_config, disabled_modules, verbose)
1350
1351    if target_config.qt_version >= 0x050f00:
1352        check_5_15_modules(target_config, disabled_modules, verbose)
1353
1354    # QtWebEngine needs to know if QtWebChannel is available.
1355    if 'QtWebChannel' not in target_config.pyqt_modules:
1356        target_config.pyqt_disabled_features.append('PyQt_WebChannel')
1357
1358
1359def check_5_1_modules(target_config, disabled_modules, verbose):
1360    """ Check which modules introduced in Qt v5.1 can be built and update the
1361    target configuration accordingly.  target_config is the target
1362    configuration.  disabled_modules is the list of modules that have been
1363    explicitly disabled.  verbose is set if the output is to be displayed.
1364    """
1365
1366    # Check the OpenGL functions.
1367    if 'PyQt_OpenGL' in target_config.pyqt_disabled_features:
1368        pass
1369    elif 'PyQt_Desktop_OpenGL' in target_config.pyqt_disabled_features:
1370        check_module(target_config, disabled_modules, verbose,
1371                '_QOpenGLFunctions_ES2', 'qopenglfunctions_es2.h',
1372                'new QOpenGLFunctions_ES2()')
1373    else:
1374        desktop_versions = (
1375                '1_0', '1_1', '1_2', '1_3', '1_4', '1_5',
1376                '2_0', '2_1',
1377                '3_0', '3_1',
1378                '3_2_Compatibility', '3_2_Core',
1379                '3_3_Compatibility', '3_3_Core',
1380                '4_0_Compatibility', '4_0_Core',
1381                '4_1_Compatibility', '4_1_Core',
1382                '4_2_Compatibility', '4_2_Core',
1383                '4_3_Compatibility', '4_3_Core',
1384                '4_4_Compatibility', '4_4_Core',
1385                '4_5_Compatibility', '4_5_Core')
1386
1387        for ogl in desktop_versions:
1388            ogl_module = '_QOpenGLFunctions_' + ogl
1389            ogl_h = 'qopenglfunctions_' + ogl.lower() + '.h'
1390            ogl_ctor = 'new QOpenGLFunctions_' + ogl + '()'
1391
1392            check_module(target_config, disabled_modules, verbose, ogl_module,
1393                    ogl_h, ogl_ctor)
1394
1395    check_module(target_config, disabled_modules, verbose, 'QtSensors',
1396            'qsensor.h', 'new QSensor(QByteArray())')
1397    check_module(target_config, disabled_modules, verbose, 'QtSerialPort',
1398            'qserialport.h', 'new QSerialPort()')
1399    check_module(target_config, disabled_modules, verbose, 'QtX11Extras',
1400            'QX11Info', 'QX11Info::display()')
1401
1402
1403def check_5_2_modules(target_config, disabled_modules, verbose):
1404    """ Check which modules introduced in Qt v5.2 can be built and update the
1405    target configuration accordingly.  target_config is the target
1406    configuration.  disabled_modules is the list of modules that have been
1407    explicitly disabled.  verbose is set if the output is to be displayed.
1408    """
1409
1410    check_module(target_config, disabled_modules, verbose, 'QtBluetooth',
1411            'qbluetoothaddress.h', 'new QBluetoothAddress()')
1412    check_module(target_config, disabled_modules, verbose, 'QtMacExtras',
1413            'qmacpasteboardmime.h', 'class Foo : public QMacPasteboardMime {}')
1414    check_module(target_config, disabled_modules, verbose, 'QtPositioning',
1415            'qgeoaddress.h', 'new QGeoAddress()')
1416    check_module(target_config, disabled_modules, verbose, 'QtWinExtras',
1417            'QtWin', 'QtWin::isCompositionEnabled()')
1418
1419
1420def check_5_3_modules(target_config, disabled_modules, verbose):
1421    """ Check which modules introduced in Qt v5.3 can be built and update the
1422    target configuration accordingly.  target_config is the target
1423    configuration.  disabled_modules is the list of modules that have been
1424    explicitly disabled.  verbose is set if the output is to be displayed.
1425    """
1426
1427    check_module(target_config, disabled_modules, verbose, 'QtQuickWidgets',
1428            'qquickwidget.h', 'new QQuickWidget()')
1429    check_module(target_config, disabled_modules, verbose, 'QtWebSockets',
1430            'qwebsocket.h', 'new QWebSocket()')
1431    check_module(target_config, disabled_modules, verbose, 'Enginio',
1432            'enginioclient.h', 'new EnginioClient()')
1433
1434
1435def check_5_4_modules(target_config, disabled_modules, verbose):
1436    """ Check which modules introduced in Qt v5.4 can be built and update the
1437    target configuration accordingly.  target_config is the target
1438    configuration.  disabled_modules is the list of modules that have been
1439    explicitly disabled.  verbose is set if the output is to be displayed.
1440    """
1441
1442    check_module(target_config, disabled_modules, verbose, 'QtWebChannel',
1443            'qwebchannel.h', 'new QWebChannel()')
1444
1445
1446def check_5_5_modules(target_config, disabled_modules, verbose):
1447    """ Check which modules introduced in Qt v5.5 can be built and update the
1448    target configuration accordingly.  target_config is the target
1449    configuration.  disabled_modules is the list of modules that have been
1450    explicitly disabled.  verbose is set if the output is to be displayed.
1451    """
1452
1453    check_module(target_config, disabled_modules, verbose, 'QtLocation',
1454            'qplace.h', 'new QPlace()')
1455    check_module(target_config, disabled_modules, verbose, 'QtNfc',
1456            'qnearfieldmanager.h', 'new QNearFieldManager()')
1457
1458
1459def check_5_12_modules(target_config, disabled_modules, verbose):
1460    """ Check which modules introduced in Qt v5.12 can be built and update the
1461    target configuration accordingly.  target_config is the target
1462    configuration.  disabled_modules is the list of modules that have been
1463    explicitly disabled.  verbose is set if the output is to be displayed.
1464    """
1465
1466    check_module(target_config, disabled_modules, verbose, 'QtRemoteObjects',
1467            'qtremoteobjectsversion.h',
1468            'const char *v = QTREMOTEOBJECTS_VERSION_STR')
1469
1470
1471def check_5_15_modules(target_config, disabled_modules, verbose):
1472    """ Check which modules introduced in Qt v5.15 can be built and update the
1473    target configuration accordingly.  target_config is the target
1474    configuration.  disabled_modules is the list of modules that have been
1475    explicitly disabled.  verbose is set if the output is to be displayed.
1476    """
1477
1478    check_module(target_config, disabled_modules, verbose, 'QtQuick3D',
1479            'qquick3d.h', 'QQuick3D::idealSurfaceFormat()')
1480    check_module(target_config, disabled_modules, verbose, 'QtTextToSpeech',
1481            'QTextToSpeech', 'new QTextToSpeech()')
1482
1483
1484def generate_makefiles(target_config, verbose, parts, tracing, fatal_warnings, distinfo):
1485    """ Generate the makefiles to build everything.  target_config is the
1486    target configuration.  verbose is set if the output is to be displayed.
1487    parts is the number of parts the generated code should be split into.
1488    tracing is set if the generated code should include tracing calls.
1489    fatal_warnings is set if warnings are fatal.  distinfo is set if a
1490    .dist-info directory should be created.
1491    """
1492
1493    # For the top-level .pro file.
1494    toplevel_pro = 'PyQt5.pro'
1495    subdirs = []
1496
1497    # Set the SIP platform, version and feature flags.
1498    sip_flags = get_sip_flags(target_config)
1499
1500    # Go through the modules.
1501    pyqt_modules = list(target_config.pyqt_modules)
1502
1503    # Add the internal modules if they are required.
1504    if not target_config.no_tools:
1505        if "QtXml" in target_config.pyqt_modules:
1506            pyqt_modules.append('pylupdate')
1507            pyqt_modules.append('pyrcc')
1508
1509    for mname in pyqt_modules:
1510        metadata = MODULE_METADATA[mname]
1511
1512        if metadata.qpy_lib:
1513            sp_qpy_dir = source_path('qpy', mname)
1514
1515            qpy_c_sources = [os.path.relpath(f, mname)
1516                    for f in matching_files(os.path.join(sp_qpy_dir, '*.c'))]
1517            qpy_cpp_sources = [os.path.relpath(f, mname)
1518                    for f in matching_files(os.path.join(sp_qpy_dir, '*.cpp'))]
1519            qpy_headers = [os.path.relpath(f, mname)
1520                    for f in matching_files(os.path.join(sp_qpy_dir, '*.h'))]
1521
1522            qpy_sources = qpy_c_sources + qpy_cpp_sources
1523        else:
1524            qpy_sources = []
1525            qpy_headers = []
1526
1527        generate_sip_module_code(target_config, verbose, parts, tracing, mname,
1528                fatal_warnings, sip_flags, metadata.public, qpy_sources,
1529                qpy_headers)
1530        subdirs.append(mname)
1531
1532    # Generate the composite module.
1533    qtmod_sipdir = os.path.join('sip', 'Qt')
1534    mk_clean_dir(qtmod_sipdir)
1535
1536    qtmod_sipfile = os.path.join(qtmod_sipdir, 'Qtmod.sip')
1537    f = open_for_writing(qtmod_sipfile)
1538
1539    f.write('''%CompositeModule PyQt5.Qt
1540
1541''')
1542
1543    for mname in COMPOSITE_COMPONENTS:
1544        if mname in target_config.pyqt_modules:
1545            f.write('%%Include %s/%smod.sip\n' % (mname, mname))
1546
1547    f.close()
1548
1549    generate_sip_module_code(target_config, verbose, parts, tracing, 'Qt',
1550            fatal_warnings, sip_flags, False)
1551    if "QtCore" in target_config.pyqt_modules:
1552        subdirs.append('Qt')
1553
1554    # Generate the top-level __init__.py.
1555    inf = open(source_path('__init__.py'))
1556    contents = inf.read()
1557    inf.close()
1558
1559    inf = open_for_writing('__init__.py')
1560    inf.write(contents)
1561
1562    if target_config.py_platform == 'win32':
1563        # On Windows we try and make sure the Qt DLLs can be found, either any
1564        # bundled copies or an existing installation (using the traditional
1565        # Windows DLL search).  We don't raise an exception in case the
1566        # application has already taken steps to resolve this which we don't
1567        # know about.
1568        inf.write("""
1569
1570def find_qt():
1571    import os, sys
1572
1573    qtcore_dll = '\\\\Qt5Core.dll'
1574
1575    dll_dir = os.path.dirname(sys.executable)
1576    if not os.path.isfile(dll_dir + qtcore_dll):
1577        path = os.environ['PATH']
1578
1579        dll_dir = os.path.dirname(__file__) + '\\\\Qt5\\\\bin'
1580        if os.path.isfile(dll_dir + qtcore_dll):
1581            path = dll_dir + ';' + path
1582            os.environ['PATH'] = path
1583        else:
1584            for dll_dir in path.split(';'):
1585                if os.path.isfile(dll_dir + qtcore_dll):
1586                    break
1587            else:
1588                return
1589
1590    try:
1591        os.add_dll_directory(dll_dir)
1592    except AttributeError:
1593        pass
1594
1595
1596find_qt()
1597del find_qt
1598""")
1599
1600    inf.close()
1601
1602    # Generate any executable wrappers.
1603    wrappers = []
1604    if not target_config.no_tools:
1605        if "QtXml" in target_config.pyqt_modules:
1606            # Generate the pylupdate5 and pyrcc5 wrappers.
1607            for tool in ('pylupdate', 'pyrcc'):
1608                wrappers.append((tool,
1609                        generate_tool_wrapper(target_config, tool + '5',
1610                                'PyQt5.%s_main' % tool)))
1611
1612        if "QtCore" in target_config.pyqt_modules:
1613            # Generate the pyuic5 wrapper.
1614            wrappers.append(('pyuic',
1615                    generate_tool_wrapper(target_config, 'pyuic5',
1616                            'PyQt5.uic.pyuic')))
1617
1618    # Generate the Qt Designer plugin.
1619    if not target_config.no_designer_plugin and 'QtDesigner' in target_config.pyqt_modules:
1620        if generate_plugin_makefile(target_config, verbose, 'designer', target_config.designer_plugin_dir, "Qt Designer"):
1621            subdirs.append('designer')
1622
1623    # Generate the qmlscene plugin.
1624    if not target_config.no_qml_plugin and 'QtQml' in target_config.pyqt_modules:
1625        if generate_plugin_makefile(target_config, verbose, 'qmlscene', target_config.qml_plugin_dir, "qmlscene"):
1626            subdirs.append('qmlscene')
1627
1628            rewrite_qmldir(target_config, 'Charts',
1629                    source_path('examples', 'quick', 'tutorials', 'extending',
1630                            'chapter6-plugins'))
1631
1632    # Generate the Python dbus module.
1633    if target_config.pydbus_module_dir != '':
1634        mname = 'dbus'
1635
1636        mk_dir(mname)
1637        sp_src_dir = source_path(mname)
1638
1639        lib_dirs = ['-L' + l for l in target_config.dbus_lib_dirs]
1640        lib_names = ['-l' + l for l in target_config.dbus_libs]
1641        libs = ' '.join(lib_dirs + lib_names)
1642
1643        generate_module_makefile(target_config, verbose, mname,
1644                include_paths=target_config.dbus_inc_dirs, libs=libs,
1645                install_path=target_config.pydbus_module_dir,
1646                src_dir=sp_src_dir)
1647
1648        subdirs.append(mname)
1649
1650    # Generate the top-level .pro file.
1651    all_installs = []
1652
1653    inform("Generating the top-level .pro file...")
1654    out_f = open_for_writing(toplevel_pro)
1655
1656    root_dir = qmake_quote(target_config.pyqt_module_dir + '/PyQt5')
1657
1658    for mname in pyqt_modules:
1659        all_installs.append(
1660                root_dir + '/' + module_file_name(target_config, mname))
1661
1662    # all_installs.append(root_dir + '/' + module_file_name(target_config, 'Qt'))
1663
1664    out_f.write('''TEMPLATE = subdirs
1665CONFIG += ordered nostrip
1666SUBDIRS = %s
1667''' % (' '.join(subdirs)))
1668
1669    if "QtCore" in target_config.pyqt_modules:
1670        out_f.write('''
1671init_py.files = %s
1672init_py.path = %s
1673INSTALLS += init_py
1674''' % (source_path('__init__.py'), root_dir))
1675
1676    # all_installs.append(root_dir + '/__init__.py')
1677
1678        if not target_config.no_tools:
1679            # Install the uic module.
1680            out_f.write('''
1681uic_package.files = %s
1682uic_package.path = %s
1683INSTALLS += uic_package
1684''' % (source_path('pyuic', 'uic'), root_dir))
1685
1686    # all_installs.append(root_dir + '/uic')
1687
1688    # Install the tool main scripts and wrappers.
1689    if wrappers:
1690        wrapper_exes = []
1691        for tool, wrapper in wrappers:
1692            if tool != 'pyuic':
1693                tool_main = tool + '_main.py'
1694
1695                out_f.write('''
1696%s.files = %s
1697%s.path = %s
1698INSTALLS += %s
1699''' % (tool, source_path('sip', tool, tool_main), tool, root_dir, tool))
1700
1701                all_installs.append(root_dir + '/' + tool_main)
1702
1703            wrapper_exes.append(wrapper)
1704            all_installs.append(target_config.pyqt_bin_dir + '/' + wrapper)
1705
1706        out_f.write('''
1707tools.files = %s
1708tools.path = %s
1709INSTALLS += tools
1710''' % (' '.join(wrapper_exes), qmake_quote(target_config.pyqt_bin_dir)))
1711
1712    # Install the .sip files.
1713    if target_config.pyqt_sip_dir:
1714        for mname, metadata in MODULE_METADATA.items():
1715            if mname not in pyqt_modules:
1716                continue
1717            if metadata.public and mname != 'Qt':
1718                sip_files = matching_files(source_path('sip', mname, '*.sip'))
1719
1720                if len(sip_files) != 0:
1721                    mdir = target_config.pyqt_sip_dir + '/' + mname
1722
1723                    out_f.write('''
1724sip%s.path = %s
1725sip%s.files = %s
1726INSTALLS += sip%s
1727''' % (
1728    mname, qmake_quote(mdir),
1729    mname, ' '.join([qmake_quote(s) for s in sip_files]),
1730    mname
1731))
1732
1733                    all_installs.append(mdir)
1734
1735    # Install the stub files.
1736    if target_config.pyqt_stubs_dir:
1737        pyi_names = [mname + '.pyi'
1738                for mname in target_config.pyqt_modules if mname[0] != '_']
1739
1740        out_f.write('''
1741pep484_stubs.files = %s
1742pep484_stubs.path = %s
1743INSTALLS += pep484_stubs
1744''' % (' '.join(pyi_names),
1745            qmake_quote(target_config.pyqt_stubs_dir)))
1746
1747        all_installs.extend(
1748                [target_config.pyqt_stubs_dir + '/' + pyi
1749                        for pyi in pyi_names])
1750
1751    # Install the QScintilla .api file.
1752    if target_config.qsci_api:
1753        api_dir = target_config.qsci_api_dir + '/api/python'
1754        api_list = ' '.join(['%s.api' % m for m in target_config.pyqt_modules])
1755
1756        out_f.write('''
1757qscintilla_api.files = %s
1758qscintilla_api.path = %s
1759INSTALLS += qscintilla_api
1760''' % (api_list, qmake_quote(api_dir)))
1761
1762    # all_installs.append(api_dir + '/PyQt5.api')
1763
1764    if distinfo:
1765        # The command to run to generate the .dist-info directory.
1766        distinfo_dir = os.path.join(target_config.pyqt_module_dir,
1767                'PyQt5-' + PYQT_VERSION_STR + '.dist-info')
1768
1769        run_mk_distinfo = '%s %s \\"$(INSTALL_ROOT)\\" %s installed.txt' % (
1770                sys.executable, source_path('mk_distinfo.py'), distinfo_dir)
1771
1772        out_f.write('''
1773distinfo.extra = %s
1774distinfo.path = %s
1775INSTALLS += distinfo
1776''' % (run_mk_distinfo, root_dir))
1777
1778        # Create the file containing all installed files.
1779        installed = open('installed.txt', 'w')
1780
1781        for install in all_installs:
1782            installed.write(install + '\n')
1783
1784        installed.close()
1785
1786    out_f.close()
1787
1788    # Make the wrappers executable on platforms that support it.  If we did it
1789    # after running qmake then (on Linux) the execute bits would be stripped on
1790    # installation.
1791    if target_config.py_platform != 'win32':
1792        for tool, wrapper in wrappers:
1793            inform("Making the %s wrapper executable..." % wrapper)
1794
1795            sbuf = os.stat(wrapper)
1796            mode = sbuf.st_mode
1797            mode |= (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
1798            os.chmod(wrapper, mode)
1799
1800    # Generate the makefiles.
1801    inform("Generating the Makefiles...")
1802    run_qmake(target_config, verbose, toplevel_pro, recursive=True)
1803
1804
1805def generate_plugin_makefile(target_config, verbose, plugin_dir, install_dir, plugin_name):
1806    """ Generate the makefile for a plugin that embeds the Python interpreter.
1807    target_config is the target configuration.  verbose is set if the output is
1808    to be displayed.  plugin_dir is the name of the directory containing the
1809    plugin implementation.  install_dir is the name of the directory that the
1810    plugin will be installed in.  plugin_name is a descriptive name of the
1811    plugin to be used in user messages.  Returns True if the makefile could be
1812    generated.
1813    """
1814
1815    # Check we have a shared interpreter library.
1816    if target_config.py_pyshlib == '':
1817        inform("The %s plugin was disabled because a dynamic Python library couldn't be found." % plugin_name)
1818        return False
1819
1820    # Create the qmake project file.
1821    inform("Generating the %s plugin .pro file..." % plugin_name)
1822
1823    sp_plugin_dir = source_path(plugin_dir)
1824
1825    fin = open(os.path.join(sp_plugin_dir, '%s.pro-in' % plugin_dir))
1826    prj = fin.read()
1827    fin.close()
1828
1829    prj = prj.replace('@QTCONFIG@',
1830            'debug' if target_config.debug else 'release')
1831    prj = prj.replace('@PYINCDIR@', qmake_quote(target_config.py_inc_dir))
1832    prj = prj.replace('@SIPINCDIR@', qmake_quote(target_config.sip_inc_dir))
1833    prj = prj.replace('@PYLINK@', target_config.get_pylib_link_arguments())
1834    prj = prj.replace('@PYSHLIB@', target_config.py_pyshlib)
1835    prj = prj.replace('@QTPLUGINDIR@', qmake_quote(install_dir))
1836
1837    pro_name = os.path.join(plugin_dir, '%s.pro' % plugin_dir)
1838
1839    mk_dir(plugin_dir)
1840    fout = open_for_writing(pro_name)
1841    fout.write(prj)
1842
1843    if sp_plugin_dir != plugin_dir:
1844        fout.write('''
1845INCLUDEPATH += %s
1846VPATH = %s
1847''' % (qmake_quote(sp_plugin_dir), qmake_quote(sp_plugin_dir)))
1848
1849    fout.write('\n'.join(target_config.qmake_variables) + '\n')
1850
1851    fout.close()
1852
1853    return True
1854
1855
1856def pro_sources(src_dir, other_headers=None, other_sources=None):
1857    """ Return the HEADERS, SOURCES and OBJECTIVE_SOURCES variables for a .pro
1858    file by introspecting a directory.  src_dir is the name of the directory.
1859    other_headers is an optional list of other header files.  other_sources is
1860    an optional list of other source files.
1861    """
1862
1863    pro_lines = []
1864
1865    headers = [os.path.basename(f) for f in matching_files('%s/*.h' % src_dir)]
1866
1867    if other_headers is not None:
1868        headers += other_headers
1869
1870    if len(headers) != 0:
1871        pro_lines.append('HEADERS = %s' % ' '.join(headers))
1872
1873    sources = [os.path.basename(f) for f in matching_files('%s/*.c' % src_dir)]
1874
1875    for f in matching_files('%s/*.cpp' % src_dir):
1876        f = os.path.basename(f)
1877
1878        # Exclude any moc generated C++ files that might be around from a
1879        # previous build.
1880        if not f.startswith('moc_'):
1881            sources.append(f)
1882
1883    if other_sources is not None:
1884        sources += other_sources
1885
1886    if len(sources) != 0:
1887        pro_lines.append('SOURCES = %s' % ' '.join([qmake_quote(s) for s in sources]))
1888
1889    objective_sources = [
1890            os.path.basename(f) for f in matching_files('%s/*.mm' % src_dir)]
1891
1892    if len(objective_sources) != 0:
1893        pro_lines.append('OBJECTIVE_SOURCES = %s' % ' '.join([qmake_quote(s) for s in objective_sources]))
1894
1895    return pro_lines
1896
1897
1898def module_file_name(target_config, name):
1899    """ Return the name of a file implementing a module. """
1900
1901    if sys.platform == 'win32':
1902        fs = '{}.lib' if target_config.static else '{}.pyd'
1903    else:
1904        fs = 'lib{}.a' if target_config.static else '{}.so'
1905
1906    return fs.format(name)
1907
1908
1909def generate_tool_wrapper(target_config, wrapper, module):
1910    """ Create a platform dependent executable wrapper for a tool module.
1911    target_config is the target configuration.  wrapper is the name of the
1912    wrapper without any extension.  module is the tool module.  Returns the
1913    platform specific name of the wrapper.
1914    """
1915
1916    if target_config.py_platform == 'win32':
1917        wrapper += '.bat'
1918
1919    inform("Generating the %s wrapper..." % wrapper)
1920
1921    exe = quote(target_config.pyuic_interpreter)
1922
1923    wf = open_for_writing(wrapper)
1924
1925    if target_config.py_platform == 'win32':
1926        wf.write('@%s -m %s %%1 %%2 %%3 %%4 %%5 %%6 %%7 %%8 %%9\n' % (exe, module))
1927    else:
1928        wf.write('#!/bin/sh\n')
1929        wf.write('exec %s -m %s ${1+"$@"}\n' % (exe, module))
1930
1931    wf.close()
1932
1933    return wrapper
1934
1935
1936def rewrite_qmldir(target_config, module, module_dir):
1937    """ Re-write a qmldir file for a module that used the qmlscene plugin.
1938    target_config is the target configuration.  module is the name of the QML
1939    module.  module_dir is the name of the directory containing the QML module.
1940    """
1941
1942    qmldir_fn = os.path.join(module_dir, module, 'qmldir')
1943
1944    inform("Re-writing %s..." % qmldir_fn)
1945
1946    qmldir = open_for_writing(qmldir_fn)
1947    qmldir.write('module %s\nplugin pyqt5qmlplugin %s\n' % (module, target_config.qml_plugin_dir))
1948    qmldir.close()
1949
1950
1951def quote(path):
1952    """ Return a path with quotes added if it contains spaces.  path is the
1953    path.
1954    """
1955
1956    if ' ' in path:
1957        path = '"%s"' % path
1958
1959    return path
1960
1961
1962def qmake_quote(path):
1963    """ Return a path quoted for qmake if it contains spaces.  path is the
1964    path.
1965    """
1966
1967    if ' ' in path:
1968        path = '$$quote(%s)' % path
1969
1970    return path
1971
1972
1973def inform_user(target_config, sip_version):
1974    """ Tell the user the values that are going to be used.  target_config is
1975    the target configuration.  sip_version is the SIP version string.
1976    """
1977
1978    inform("Qt v%s is being used." %
1979            version_to_string(target_config.qt_version))
1980
1981    inform("The qmake executable is %s." % target_config.qmake)
1982
1983    inform(
1984            "Qt is built as a %s library." % (
1985                    "shared" if target_config.qt_shared else "static"))
1986
1987    if target_config.sysroot != '':
1988        inform("The system root directory is %s." % target_config.sysroot)
1989
1990    inform("SIP %s is being used." % sip_version)
1991    inform("The sip executable is %s." % target_config.sip)
1992    inform("These PyQt5 modules will be built: %s." % ', '.join(target_config.pyqt_modules))
1993    inform("The PyQt5 Python package will be installed in %s." % target_config.pyqt_module_dir)
1994
1995    if target_config.debug:
1996        inform("A debug version of PyQt5 will be built.")
1997
1998    if target_config.py_debug:
1999        inform("A debug build of Python is being used.")
2000
2001    if target_config.no_docstrings:
2002        inform("PyQt5 is being built without generated docstrings.")
2003    else:
2004        inform("PyQt5 is being built with generated docstrings.")
2005
2006    if target_config.prot_is_public:
2007        inform("PyQt5 is being built with 'protected' redefined as 'public'.")
2008
2009    if target_config.no_designer_plugin:
2010        inform("The Designer plugin will not be built.")
2011    else:
2012        inform("The Designer plugin will be installed in %s." %
2013                target_config.designer_plugin_dir)
2014
2015    if target_config.no_qml_plugin:
2016        inform("The qmlscene plugin will not be built.")
2017    else:
2018        inform("The qmlscene plugin will be installed in %s." %
2019                target_config.qml_plugin_dir)
2020
2021    if target_config.qsci_api:
2022        inform(
2023                "The QScintilla API file will be installed in %s." %
2024                        os.path.join(
2025                                target_config.qsci_api_dir, 'api', 'python'))
2026
2027    if target_config.pyqt_stubs_dir:
2028        inform("The PyQt5 PEP 484 stub files will be installed in %s." %
2029                target_config.pyqt_stubs_dir)
2030
2031    if target_config.pydbus_module_dir:
2032        inform(
2033                "The dbus support module will be installed in %s." %
2034                        target_config.pydbus_module_dir)
2035
2036    if target_config.pyqt_sip_dir:
2037        inform("The PyQt5 .sip files will be installed in %s." %
2038                target_config.pyqt_sip_dir)
2039
2040    if target_config.no_tools:
2041        inform("pyuic5, pyrcc5 and pylupdate5 will not be built.")
2042    else:
2043        inform("pyuic5, pyrcc5 and pylupdate5 will be installed in %s." %
2044                target_config.pyqt_bin_dir)
2045
2046        inform("The interpreter used by pyuic5 is %s." %
2047                target_config.pyuic_interpreter)
2048
2049    if target_config.vend_enabled:
2050        inform("PyQt5 will only be usable with signed interpreters.")
2051
2052
2053def run_qmake(target_config, verbose, pro_name, makefile_name='', fatal=True, recursive=False):
2054    """ Run qmake against a .pro file.  target_config is the target
2055    configuration.  verbose is set if the output is to be displayed.  pro_name
2056    is the name of the .pro file.  makefile_name is the name of the makefile
2057    to generate (and defaults to Makefile).  fatal is set if a qmake failure is
2058    considered a fatal error, otherwise False is returned if qmake fails.
2059    recursive is set to use the -recursive flag.
2060    """
2061
2062    # qmake doesn't behave consistently if it is not run from the directory
2063    # containing the .pro file - so make sure it is.
2064    pro_dir, pro_file = os.path.split(pro_name)
2065    if pro_dir != '':
2066        cwd = os.getcwd()
2067        os.chdir(pro_dir)
2068    else:
2069        cwd = None
2070
2071    mf = makefile_name if makefile_name != '' else 'Makefile'
2072
2073    remove_file(mf)
2074
2075    args = [quote(target_config.qmake)]
2076
2077    if target_config.qmake_spec != target_config.qmake_spec_default:
2078        args.append('-spec')
2079        args.append(target_config.qmake_spec)
2080
2081    if makefile_name != '':
2082        args.append('-o')
2083        args.append(makefile_name)
2084
2085    if recursive:
2086        args.append('-recursive')
2087
2088    args.append(pro_file)
2089
2090    run_command(' '.join(args), verbose)
2091
2092    if not os.access(mf, os.F_OK):
2093        if fatal:
2094            error(
2095                    "%s failed to create a makefile from %s." %
2096                            (target_config.qmake, pro_name))
2097
2098        return False
2099
2100    # Restore the current directory.
2101    if cwd is not None:
2102        os.chdir(cwd)
2103
2104    return True
2105
2106
2107def run_make(target_config, verbose, exe, makefile_name):
2108    """ Run make against a makefile to create an executable.  target_config is
2109    the target configuration.  verbose is set if the output is to be displayed.
2110    exe is the platform independent name of the executable that will be
2111    created.  makefile_name is the name of the makefile.  Returns the platform
2112    specific name of the executable, or None if an executable wasn't created.
2113    """
2114
2115    # Guess the name of make and set the default target and platform specific
2116    # name of the executable.
2117    if target_config.py_platform == 'win32':
2118        if target_config.qmake_spec == 'win32-g++':
2119            make = 'mingw32-make'
2120        else:
2121            make = 'nmake'
2122
2123        if target_config.debug:
2124            makefile_target = 'debug'
2125            platform_exe = os.path.join('debug', exe + '.exe')
2126        else:
2127            makefile_target = 'release'
2128            platform_exe = os.path.join('release', exe + '.exe')
2129    else:
2130        make = 'make'
2131        makefile_target = ''
2132
2133        if target_config.py_platform == 'darwin':
2134            platform_exe = os.path.join(exe + '.app', 'Contents', 'MacOS', exe)
2135        else:
2136            platform_exe = os.path.join('.', exe)
2137
2138    remove_file(platform_exe)
2139
2140    args = [make, '-f', makefile_name]
2141
2142    if makefile_target != '':
2143        args.append(makefile_target)
2144
2145    run_command(' '.join(args), verbose)
2146
2147    return platform_exe if os.access(platform_exe, os.X_OK) else None
2148
2149
2150def run_command(cmd, verbose):
2151    """ Run a command and display the output if requested.  cmd is the command
2152    to run.  verbose is set if the output is to be displayed.
2153    """
2154
2155    if verbose:
2156        sys.stdout.write(cmd + "\n")
2157
2158    fout = get_command_output(cmd, and_stderr=True)
2159
2160    # Read stdout and stderr until there is no more output.
2161    lout = fout.readline()
2162    while lout:
2163        if verbose:
2164            if sys.hexversion >= 0x03000000:
2165                sys.stdout.write(str(lout, encoding=sys.stdout.encoding))
2166            else:
2167                sys.stdout.write(lout)
2168
2169        lout = fout.readline()
2170
2171    close_command_pipe(fout)
2172
2173
2174def remove_file(fname):
2175    """ Remove a file which may or may not exist.  fname is the name of the
2176    file.
2177    """
2178
2179    try:
2180        os.remove(fname)
2181    except OSError:
2182        pass
2183
2184
2185def check_vendorid(target_config):
2186    """ See if the VendorID library and include file can be found.
2187    target_config is the target configuration.
2188    """
2189
2190    if target_config.py_version >= 0x030000:
2191        # VendorID doesn't support Python v3.
2192        target_config.vend_enabled = False
2193    elif target_config.vend_enabled:
2194        if os.access(os.path.join(target_config.vend_inc_dir, 'vendorid.h'), os.F_OK):
2195            if glob.glob(os.path.join(target_config.vend_lib_dir, '*vendorid*')):
2196                inform("The VendorID package was found.")
2197            else:
2198                target_config.vend_enabled = False
2199                inform(
2200                        "The VendorID library could not be found in %s and so "
2201                        "signed interpreter checking will be disabled. If the "
2202                        "VendorID package is installed then use the "
2203                        "--vendorid-libdir argument to explicitly specify the "
2204                        "correct directory." % target_config.vend_lib_dir)
2205        else:
2206            target_config.vend_enabled = False
2207            inform(
2208                    "vendorid.h could not be found in %s and so signed "
2209                    "interpreter checking will be disabled. If the VendorID "
2210                    "package is installed then use the --vendorid-incdir "
2211                    "argument to explicitly specify the correct directory." %
2212                            target_config.vend_inc_dir)
2213
2214
2215def get_command_output(cmd, and_stderr=False):
2216    """ Return a pipe from which a command's output can be read.  cmd is the
2217    command.  and_stderr is set if the output should include stderr as well as
2218    stdout.
2219    """
2220
2221    try:
2222        import subprocess
2223    except ImportError:
2224        if and_stderr:
2225            _, sout = os.popen4(cmd)
2226        else:
2227            _, sout, _ = os.popen3(cmd)
2228
2229        return sout
2230
2231    if and_stderr:
2232        stderr = subprocess.STDOUT
2233    else:
2234        stderr = subprocess.PIPE
2235
2236    p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
2237            stdout=subprocess.PIPE, stderr=stderr)
2238
2239    return p.stdout
2240
2241
2242def close_command_pipe(pipe):
2243    """ Close the pipe returned by get_command_output(). """
2244
2245    pipe.close()
2246
2247    try:
2248        os.wait()
2249    except:
2250        pass
2251
2252
2253def source_path(*names):
2254    """ Return the native path for a list of components rooted at the directory
2255    containing this script.  names is the sequence of component names.
2256    """
2257
2258    path = [os.path.dirname(os.path.abspath(__file__))] + list(names)
2259
2260    return os.path.join(*path)
2261
2262
2263def check_dbus(target_config, verbose):
2264    """ See if the DBus support module should be built and update the target
2265    configuration accordingly.  target_config is the target configuration.
2266    verbose is set if the output is to be displayed.
2267    """
2268
2269    if target_config.no_pydbus or not os.path.isdir(source_path('dbus')):
2270        return
2271
2272    inform("Checking to see if the dbus support module should be built...")
2273
2274    cmd = 'pkg-config --cflags-only-I --libs dbus-1'
2275
2276    if verbose:
2277        sys.stdout.write(cmd + "\n")
2278
2279    sout = get_command_output(cmd)
2280    iflags = sout.read().strip()
2281    close_command_pipe(sout)
2282
2283    if not iflags:
2284        inform("DBus v1 does not seem to be installed.")
2285        return
2286
2287    if sys.hexversion >= 0x03000000:
2288        iflags = iflags.decode()
2289
2290    for f in iflags.split():
2291        if f.startswith('-I'):
2292            target_config.dbus_inc_dirs.append(f[2:])
2293        elif f.startswith('-L'):
2294            target_config.dbus_lib_dirs.append(f[2:])
2295        elif f.startswith('-l'):
2296            target_config.dbus_libs.append(f[2:])
2297
2298    try:
2299        import dbus.mainloop
2300    except:
2301        inform("The Python dbus module doesn't seem to be installed.")
2302        return
2303
2304    target_config.pydbus_module_dir = dbus.mainloop.__path__[0]
2305
2306    # Try and find dbus-python.h.  We don't use pkg-config because it is broken
2307    # for dbus-python (at least for versions up to and including v0.81.0).
2308    # Instead we look where DBus itself is installed - which in most cases will
2309    # be where dbus-python is also installed.
2310    if target_config.pydbus_inc_dir != '':
2311        target_config.dbus_inc_dirs = [target_config.pydbus_inc_dir]
2312
2313    for d in target_config.dbus_inc_dirs:
2314        if os.access(os.path.join(d, 'dbus', 'dbus-python.h'), os.F_OK):
2315            break
2316    else:
2317        inform(
2318                "dbus/dbus-python.h could not be found and so the DBus "
2319                "support module will be disabled. If dbus-python v0.80 or "
2320                "later is installed then use the --dbus argument to "
2321                "explicitly specify the directory containing "
2322                "dbus/dbus-python.h.")
2323        target_config.pydbus_module_dir = ''
2324
2325
2326def check_module(target_config, disabled_modules, verbose, mname, incfile=None, test=None):
2327    """ See if a module can be built and, if so, add it to the target
2328    configurations list of modules.  target_config is the target configuration.
2329    disabled_modules is the list of modules that have been explicitly disabled.
2330    verbose is set if the output is to be displayed.  mname is the name of the
2331    module.  incfile is the name of the include file (or sequence of multiple
2332    include files) needed for the test.  test is a C++ statement being used for
2333    the test.  If either incfile or test are None then there is a test program
2334    that needs to be run and its output captured.
2335    """
2336
2337    if mname in disabled_modules:
2338        return
2339
2340    # Check the module's main .sip file exists.
2341    if not os.access(source_path('sip', mname, mname + 'mod.sip'), os.F_OK):
2342        return
2343
2344    if verbose:
2345        sys.stdout.write('\n')
2346
2347    inform("Checking to see if the %s module should be built..." % mname)
2348
2349    if incfile is None or test is None:
2350        source = None
2351    else:
2352        if isinstance(incfile, str):
2353            incfile = [incfile]
2354
2355        incfile = ['#include<%s>' % i for i in incfile]
2356
2357        source = '''%s
2358
2359int main(int, char **)
2360{
2361    %s;
2362}
2363''' % ('\n'.join(incfile), test)
2364
2365    test = compile_test_program(target_config, verbose, mname, source)
2366    if test is None:
2367        return
2368
2369    # If there was an explicit test program then run it to get the disabled
2370    # features.
2371    if source is None:
2372        for disabled in run_test_program(mname, test, verbose):
2373            if disabled:
2374                inform("Disabled %s feature: %s" % (mname, disabled))
2375                target_config.pyqt_disabled_features.append(disabled)
2376
2377    # Include the module in the build.
2378    target_config.pyqt_modules.append(mname)
2379
2380
2381def compile_test_program(target_config, verbose, mname, source=None, debug=None):
2382    """ Compile the source of a Qt program and return the name of the
2383    executable or None if it couldn't be created.  target_config is the target
2384    configuration.  verbose is set if the output is to be displayed.  mname is
2385    the name of the PyQt module being tested.  source is the C++ source of the
2386    program.  If it is None then the source is expected to be found in the
2387    config-tests directory.  debug is set if debug, rather than release, mode
2388    is to be used.  If it is None then the mode is taken from the target
2389    configuration.
2390    """
2391
2392    metadata = MODULE_METADATA[mname]
2393
2394    # The derived file names.
2395    name = 'cfgtest_' + mname
2396    name_pro = name + '.pro'
2397    name_makefile = name + '.mk'
2398    name_source = name + '.cpp'
2399
2400    # Create the source file if necessary.
2401    if source is None:
2402        name_source = source_path('config-tests', name_source)
2403    else:
2404        f = open_for_writing(name_source)
2405        f.write(source)
2406        f.close()
2407
2408    # Create the .pro file.
2409    pro_lines = []
2410    pro_add_qt_dependencies(target_config, metadata, pro_lines, debug)
2411    pro_lines.append('TARGET = %s' % name)
2412
2413    pro_lines.append('SOURCES = %s' % qmake_quote(name_source))
2414
2415    f = open_for_writing(name_pro)
2416    f.write('\n'.join(pro_lines))
2417    f.close()
2418
2419    if not run_qmake(target_config, verbose, name_pro, name_makefile, fatal=False):
2420        return None
2421
2422    return run_make(target_config, verbose, name, name_makefile)
2423
2424
2425def run_test_program(mname, test, verbose):
2426    """ Run a test program and return the output as a list of lines.  mname is
2427    the name of the PyQt module being tested.  test is the name of the test
2428    executable.  verbose is set if the output is to be displayed.
2429    """
2430
2431    out_file = 'cfgtest_' + mname + '.out'
2432
2433    # Create the output file, first making sure it doesn't exist.
2434    remove_file(out_file)
2435    run_command(test + ' ' + out_file, verbose)
2436
2437    if not os.access(out_file, os.F_OK):
2438        error("%s failed to create %s. Make sure your Qt installation is correct." % (test, out_file))
2439
2440    # Read the details.
2441    f = open(out_file)
2442    lines = f.read().strip()
2443    f.close()
2444
2445    return lines.split('\n') if lines else []
2446
2447
2448def pro_add_qt_dependencies(target_config, metadata, pro_lines, debug=None):
2449    """ Add the Qt dependencies of a module to a .pro file.  target_config is
2450    the target configuration.  metadata is the module's meta-data.  pro_lines
2451    is the list of lines making up the .pro file that is updated.  debug is set
2452    if debug, rather than release, mode is to be used.  If it is None then the
2453    mode is taken from the target configuration.
2454    """
2455
2456    if debug is None:
2457        debug = target_config.debug
2458
2459    add = []
2460    remove = []
2461    for qt in metadata.qmake_QT:
2462        if qt.startswith('-'):
2463            remove.append(qt[1:])
2464        else:
2465            add.append(qt)
2466
2467    if len(remove) != 0:
2468        pro_lines.append('QT -= %s' % ' '.join(remove))
2469
2470    if len(add) != 0:
2471        pro_lines.append('QT += %s' % ' '.join(add))
2472
2473    pro_lines.append(
2474            'CONFIG += %s' % ('debug' if debug else 'release'))
2475
2476    if metadata.cpp11:
2477        pro_lines.append('CONFIG += c++11')
2478
2479    pro_lines.extend(target_config.qmake_variables)
2480
2481
2482def get_sip_flags(target_config):
2483    """ Return the SIP platform, version and feature flags.  target_config is
2484    the target configuration.
2485    """
2486
2487    sip_flags = ['-n', 'PyQt5.sip']
2488
2489    # If we don't check for signed interpreters, we exclude the 'VendorID'
2490    # feature
2491    if target_config.py_version < 0x030000 and not target_config.vend_enabled:
2492        sip_flags.append('-x')
2493        sip_flags.append('VendorID')
2494
2495    # Handle Python debug builds.
2496    if target_config.py_debug:
2497        sip_flags.append('-D')
2498
2499    # Handle the platform tag.  (Allow for win32-g++.)
2500    if target_config.py_platform.startswith('win32'):
2501        plattag = 'WS_WIN'
2502    elif target_config.py_platform == 'darwin':
2503        plattag = 'WS_MACX'
2504    else:
2505        plattag = 'WS_X11'
2506
2507    sip_flags.append('-t')
2508    sip_flags.append(plattag)
2509
2510    # Handle the Qt version tag.
2511    sip_flags.append('-t')
2512    sip_flags.append(version_to_sip_tag(target_config.qt_version))
2513
2514    # Handle any feature flags.
2515    for xf in target_config.pyqt_disabled_features:
2516        sip_flags.append('-x')
2517        sip_flags.append(xf)
2518
2519    # Handle the version specific Python features.
2520    if target_config.py_version < 0x030000:
2521        sip_flags.append('-x')
2522        sip_flags.append('Py_v3')
2523
2524    return sip_flags
2525
2526
2527def mk_clean_dir(name):
2528    """ Create a clean (ie. empty) directory.  name is the name of the
2529    directory.
2530    """
2531
2532    try:
2533        shutil.rmtree(name)
2534    except:
2535        pass
2536
2537    try:
2538        os.makedirs(name)
2539    except:
2540        error("Unable to create the %s directory." % name)
2541
2542
2543def mk_dir(name):
2544    """ Ensure a directory exists, creating it if necessary.  name is the name
2545    of the directory.
2546    """
2547
2548    try:
2549        os.makedirs(name)
2550    except:
2551        pass
2552
2553
2554def generate_sip_module_code(target_config, verbose, parts, tracing, mname, fatal_warnings, sip_flags, doc_support, qpy_sources=None, qpy_headers=None):
2555    """ Generate the code for a module.  target_config is the target
2556    configuration.  verbose is set if the output is to be displayed.  parts is
2557    the number of parts the generated code should be split into.  tracing is
2558    set if the generated code should include tracing calls.  mname is the name
2559    of the module to generate the code for.  fatal_warnings is set if warnings
2560    are fatal.  sip_flags is the list of flags to pass to sip.  doc_support
2561    is set if documentation support is to be generated for the module.
2562    qpy_sources is the optional list of QPy support code source files.
2563    qpy_headers is the optional list of QPy support code header files.
2564    """
2565
2566    inform("Generating the C++ source for the %s module..." % mname)
2567
2568    mk_clean_dir(mname)
2569
2570    # Build the SIP command line.
2571    argv = [target_config.sip, '-w']
2572
2573    if target_config.abi_version:
2574        argv.append('--abi-version')
2575        argv.append(target_config.abi_version)
2576
2577    argv.extend(sip_flags)
2578
2579    if fatal_warnings:
2580        argv.append('-f')
2581
2582    if target_config.prot_is_public:
2583        argv.append('-P');
2584
2585    if parts != 0:
2586        argv.append('-j')
2587        argv.append(str(parts))
2588
2589    if tracing:
2590        argv.append('-r')
2591
2592    if doc_support:
2593        if not target_config.no_docstrings:
2594            argv.append('-o');
2595
2596        if target_config.qsci_api:
2597            argv.append('-a')
2598            argv.append(mname + '.api')
2599
2600        if target_config.pyqt_stubs_dir:
2601            argv.append('-y')
2602            argv.append(mname + '.pyi')
2603
2604    # Pass the absolute pathname so that #line files are absolute.
2605    argv.append('-c')
2606    argv.append(os.path.abspath(mname))
2607
2608    argv.append('-I')
2609    argv.append('sip')
2610
2611    sp_sip_dir = source_path('sip')
2612    if sp_sip_dir != 'sip':
2613        # SIP assumes POSIX style separators.
2614        sp_sip_dir = sp_sip_dir.replace('\\', '/')
2615        argv.append('-I')
2616        argv.append(sp_sip_dir)
2617
2618    # The .sip files for the Qt modules will be in the out-of-tree directory.
2619    if mname == 'Qt':
2620        sip_dir = 'sip'
2621    else:
2622        sip_dir = sp_sip_dir
2623
2624    # Add the name of the .sip file.
2625    argv.append('%s/%s/%smod.sip' % (sip_dir, mname, mname))
2626
2627    run_command(' '.join([quote(a) for a in argv]), verbose)
2628
2629    # Check the result.
2630    if mname == 'Qt':
2631        file_check = 'sip%scmodule.c' % mname
2632    else:
2633        file_check = 'sipAPI%s.h' % mname
2634
2635    if not os.access(os.path.join(mname, file_check), os.F_OK):
2636        error("Unable to create the C++ code.")
2637
2638    # Embed the sip flags.
2639    if mname == 'QtCore':
2640        inform("Embedding sip flags...")
2641
2642        in_f = open(source_path('qpy', 'QtCore', 'qpycore_post_init.cpp.in'))
2643        out_f = open_for_writing(
2644                os.path.join('QtCore', 'qpycore_post_init.cpp'))
2645
2646        for line in in_f:
2647            line = line.replace('@@PYQT_SIP_FLAGS@@', ' '.join(sip_flags))
2648            out_f.write(line)
2649
2650        in_f.close()
2651        out_f.close()
2652
2653    # Generate the makefile.
2654    include_paths = []
2655    libs = ''
2656
2657    if target_config.vend_enabled:
2658        if mname == 'QtCore':
2659            include_paths.append(target_config.vend_inc_dir)
2660            libs = '-L%s -lvendorid' % target_config.vend_lib_dir
2661
2662    generate_module_makefile(target_config, verbose, mname,
2663            include_paths=include_paths, libs=libs, qpy_sources=qpy_sources,
2664            qpy_headers=qpy_headers)
2665
2666
2667def generate_module_makefile(target_config, verbose, mname, include_paths=None, libs='', install_path='', src_dir='', qpy_sources=None, qpy_headers=None):
2668    """ Generate the makefile for a module.  target_config is the target
2669    configuration.  verbose is set if the output is to be displayed.  mname is
2670    the name of the module.  include_paths is an optional list of values of
2671    INCLUDEPATH.  libs is an optional additional value of LIBS.  install_path
2672    is the optional name of the directory that the module will be installed in.
2673    src_dir is the optional source directory (by default the sources are
2674    assumed to be in the module directory).  qpy_sources is the optional list
2675    of QPy support code source files.  qpy_headers is the optional list of QPy
2676    support code header files.
2677    """
2678
2679    if verbose:
2680        sys.stdout.write('\n')
2681
2682    inform("Generating the .pro file for the %s module..." % mname)
2683
2684    if src_dir == '':
2685        src_dir = mname
2686
2687    target_name = mname
2688
2689    metadata = MODULE_METADATA[mname]
2690
2691    if metadata.qmake_TARGET != '':
2692        target_name = metadata.qmake_TARGET
2693
2694    pro_lines = ['TEMPLATE = lib']
2695
2696    # Note some version of Qt5 (probably incorrectly) implements
2697    # 'plugin_bundle' instead of 'plugin' so we specify both.
2698    pro_lines.append('CONFIG += warn_on exceptions_off %s' % ('staticlib hide_symbols' if target_config.static else 'plugin plugin_bundle'))
2699
2700    pro_add_qt_dependencies(target_config, metadata, pro_lines)
2701
2702    if target_config.qml_debug:
2703        pro_lines.append('CONFIG += qml_debug')
2704
2705    # Work around QTBUG-39300.
2706    pro_lines.append('CONFIG -= android_install')
2707
2708    pro_lines.append('TARGET = %s' % target_name)
2709
2710    if not target_config.static:
2711        debug_suffix = target_config.get_win32_debug_suffix()
2712
2713        # For Qt v5.5 make sure these frameworks are already loaded by the time
2714        # the libqcocoa.dylib plugin gets loaded.  This problem seems to be
2715        # fixed in Qt v5.6.
2716        extra_lflags = ''
2717
2718        if mname == 'QtGui':
2719            # Note that this workaround is flawed because it looks at the PyQt
2720            # configuration rather than the Qt configuration.  It will fail if
2721            # the user is building a PyQt without the QtDBus module against a
2722            # Qt with the QtDBus library.  However it will be fine for the
2723            # common case where the PyQt configuration reflects the Qt
2724            # configuration.
2725            fwks = []
2726            for m in ('QtPrintSupport', 'QtDBus', 'QtWidgets'):
2727                if m in target_config.pyqt_modules:
2728                    fwks.append('-framework ' + m)
2729
2730            if len(fwks) != 0:
2731                extra_lflags = 'QMAKE_LFLAGS += "%s"\n        ' % ' '.join(fwks)
2732
2733        # Without the 'no_check_exist' magic the target.files must exist when
2734        # qmake is run otherwise the install and uninstall targets are not
2735        # generated.
2736        shared = '''
2737win32 {
2738    PY_MODULE = %s%s.pyd
2739    PY_MODULE_SRC = $(DESTDIR_TARGET)
2740} else {
2741    PY_MODULE = %s.so
2742
2743    macx {
2744        PY_MODULE_SRC = $(TARGET).plugin/Contents/MacOS/$(TARGET)
2745
2746        QMAKE_LFLAGS += "-undefined dynamic_lookup"
2747
2748        equals(QT_MINOR_VERSION, 5) {
2749            %sQMAKE_RPATHDIR += $$[QT_INSTALL_LIBS]
2750        }
2751    } else {
2752        PY_MODULE_SRC = $(TARGET)
2753    }
2754}
2755
2756QMAKE_POST_LINK = $(COPY_FILE) $$PY_MODULE_SRC $$PY_MODULE
2757
2758target.CONFIG = no_check_exist
2759target.files = $$PY_MODULE
2760''' % (target_name, debug_suffix, target_name, extra_lflags)
2761
2762        pro_lines.extend(shared.split('\n'))
2763
2764    if install_path == '':
2765        install_path = target_config.pyqt_module_dir + '/PyQt5'
2766
2767    install_path = install_path.replace('\\', '/')
2768
2769    pro_lines.append('target.path = %s' % install_path)
2770    pro_lines.append('INSTALLS += target')
2771
2772    # This optimisation could apply to other platforms.
2773    if not target_config.static:
2774        if target_config.py_version >= 0x030000:
2775            entry_point = 'PyInit_%s' % target_name
2776        else:
2777            entry_point = 'init%s' % target_name
2778
2779        exp = open_for_writing(os.path.join(mname, target_name + '.exp'))
2780        exp.write('{ global: %s; local: *; };' % entry_point)
2781        exp.close()
2782
2783        pro_lines.append('QMAKE_LFLAGS += -Wl,--version-script=%s.exp' % target_name)
2784
2785    if target_config.prot_is_public:
2786        pro_lines.append('DEFINES += SIP_PROTECTED_IS_PUBLIC protected=public')
2787
2788    # This is needed for Windows.
2789    pro_lines.append('INCLUDEPATH += .')
2790
2791    target_config.add_sip_h_directives(pro_lines)
2792
2793    if metadata.qpy_lib:
2794        # This is the easiest way to make sure it is set for handwritten code.
2795        if not target_config.py_debug:
2796            pro_lines.append('DEFINES += Py_LIMITED_API=0x03040000')
2797
2798        pro_lines.append('INCLUDEPATH += %s' %
2799                qmake_quote(os.path.relpath(source_path('qpy', mname), mname)))
2800
2801    if include_paths:
2802        pro_lines.append(
2803                'INCLUDEPATH += ' + ' '.join(
2804                        [qmake_quote(p) for p in include_paths]))
2805
2806    if libs != '':
2807        pro_lines.append('LIBS += %s' % libs)
2808
2809    if src_dir != mname:
2810        pro_lines.append('INCLUDEPATH += %s' % qmake_quote(src_dir))
2811        pro_lines.append('VPATH = %s' % qmake_quote(src_dir))
2812
2813    pro_lines.extend(pro_sources(src_dir, qpy_headers, qpy_sources))
2814
2815    pro_name = os.path.join(mname, mname + '.pro')
2816
2817    pro = open_for_writing(pro_name)
2818    pro.write('\n'.join(pro_lines))
2819    pro.write('\n')
2820    pro.close()
2821
2822
2823def fix_license(src_lfile, dst_lfile):
2824    """ Fix the license file, if there is one, so that it conforms to the SIP
2825    v5 syntax.  src_lfile is the name of the license file.  dst_lfile is the
2826    name of the fixed license file.
2827    """
2828
2829    f = open(src_lfile)
2830    f5 = open_for_writing(dst_lfile)
2831
2832    for line in f:
2833        if line.startswith('%License'):
2834            anno_start = line.find('/')
2835            anno_end = line.rfind('/')
2836
2837            if anno_start < 0 or anno_end < 0 or anno_start == anno_end:
2838                error("%s has missing annotations." % name)
2839
2840            annos = line[anno_start + 1:anno_end].split(', ')
2841            annos5 = [anno[0].lower() + anno[1:] for anno in annos]
2842
2843            f5.write('%License(')
2844            f5.write(', '.join(annos5))
2845            f5.write(')\n')
2846        else:
2847            f5.write(line)
2848
2849    f5.close()
2850    f.close()
2851
2852
2853def check_license(target_config, license_confirmed):
2854    """ Handle the validation of the PyQt5 license.  target_config is the
2855    target configuration.  license_confirmed is set if the user has already
2856    accepted the license.
2857    """
2858
2859    try:
2860        import license
2861        ltype = license.LicenseType
2862        lname = license.LicenseName
2863
2864        try:
2865            lfile = license.LicenseFile
2866        except AttributeError:
2867            lfile = None
2868    except ImportError:
2869        ltype = None
2870
2871    if ltype is None:
2872        ltype = 'GPL'
2873        lname = "GNU General Public License"
2874        lfile = 'pyqt-gpl.sip'
2875
2876    inform(
2877            "This is the %s version of PyQt %s (licensed under the %s) for "
2878            "Python %s on %s." %
2879                    (ltype, PYQT_VERSION_STR, lname, sys.version.split()[0],
2880                            sys.platform))
2881
2882    # Confirm the license if not already done.
2883    if not license_confirmed:
2884        loptions = """
2885Type 'L' to view the license.
2886"""
2887
2888        sys.stdout.write(loptions)
2889        sys.stdout.write("""Type 'yes' to accept the terms of the license.
2890Type 'no' to decline the terms of the license.
2891
2892""")
2893
2894        while 1:
2895            sys.stdout.write("Do you accept the terms of the license? ")
2896            sys.stdout.flush()
2897
2898            try:
2899                resp = sys.stdin.readline()
2900            except KeyboardInterrupt:
2901                raise SystemExit
2902            except:
2903                resp = ""
2904
2905            resp = resp.strip().lower()
2906
2907            if resp == "yes":
2908                break
2909
2910            if resp == "no":
2911                sys.exit(0)
2912
2913            if resp == 'l':
2914                os.system('more LICENSE')
2915
2916    # Check that the license file exists and fix its syntax.
2917    sip_dir = 'sip'
2918    mk_dir(sip_dir)
2919
2920    src_lfile = os.path.join(target_config.license_dir, lfile)
2921
2922    if os.access(src_lfile, os.F_OK):
2923        inform("Found the license file %s." % lfile)
2924        fix_license(src_lfile, os.path.join(sip_dir, lfile + '5'))
2925    else:
2926        error(
2927                "Please copy the license file %s to %s." % (lfile,
2928                        target_config.license_dir))
2929
2930
2931def check_qt(target_config):
2932    """ Check the Qt installation.  target_config is the target configuration.
2933    """
2934
2935    # Starting with v4.7, Qt (when built with MinGW) assumes that stack frames
2936    # are 16 byte aligned because it uses SSE.  However the Python Windows
2937    # installers are built with 4 byte aligned stack frames.  We therefore need
2938    # to tweak the g++ flags to deal with it.
2939    if target_config.qmake_spec == 'win32-g++':
2940        target_config.qmake_variables.append('QMAKE_CFLAGS += -mstackrealign')
2941        target_config.qmake_variables.append('QMAKE_CXXFLAGS += -mstackrealign')
2942
2943
2944def check_python(target_config):
2945    """ Check the Python installation.  target_config is the target
2946    configuration.
2947    """
2948
2949    # Check the Python version number.  This allows us to assume relative
2950    # imports and ElemenTree are available.
2951    if target_config.py_version < 0x020600:
2952        error("PyQt5 requires Python v2.6 or later.")
2953
2954
2955def check_sip(target_config, verbose):
2956    """ Check that the version of sip is good enough and return its version.
2957    target_config is the target configuration.
2958    """
2959
2960    if target_config.sip is None:
2961        error(
2962                "Make sure you have a working sip on your PATH or use the "
2963                "--sip argument to explicitly specify a working sip.")
2964
2965    pipe = os.popen(' '.join([quote(target_config.sip), '-V']))
2966
2967    for l in pipe:
2968        version_str = l.strip()
2969        break
2970    else:
2971        error("'%s -V' did not generate any output." % target_config.sip)
2972
2973    pipe.close()
2974
2975    if '.dev' in version_str or 'snapshot' in version_str:
2976        # We only need to distinguish between sip v4 and sip v5.
2977        if target_config.using_sip5():
2978            version = 0x050000
2979        else:
2980            version = 0x040000
2981    else:
2982        version = version_from_string(version_str)
2983        if version is None:
2984            error(
2985                    "'%s -V' generated unexpected output: '%s'." % (
2986                            target_config.sip, version_str))
2987
2988        min_version = version_from_string(SIP_MIN_VERSION)
2989        if version < min_version:
2990            error(
2991                    "This version of PyQt5 requires sip %s or later." %
2992                            SIP_MIN_VERSION)
2993
2994    if version >= 0x050000:
2995        # Install the sip.h file for the private sip module.
2996        if target_config.sip_inc_dir is None:
2997            target_config.sip_inc_dir = os.path.join(
2998                    os.path.abspath(os.getcwd()), 'include')
2999
3000            inform("Installing sip.h in %s..." % target_config.sip_inc_dir)
3001
3002            os.makedirs(target_config.sip_inc_dir, exist_ok=True)
3003
3004            argv = ['sip-module', '--sip-h']
3005
3006            if target_config.abi_version:
3007                argv.append('--abi-version')
3008                argv.append(target_config.abi_version)
3009
3010            argv.append('--target-dir')
3011            argv.append(quote(target_config.sip_inc_dir)),
3012            argv.append('PyQt5.sip')
3013
3014            run_command(' '.join(argv), verbose)
3015
3016            if not os.access(os.path.join(target_config.sip_inc_dir, 'sip.h'), os.F_OK):
3017                error(
3018                        "sip-module failed to install sip.h in %s." %
3019                                target_config.sip_inc_dir)
3020    else:
3021        if target_config.sip_inc_dir is None:
3022            target_config.sip_inc_dir = target_config.py_venv_inc_dir
3023
3024    return version_str
3025
3026
3027def version_from_string(version_str):
3028    """ Convert a version string of the form m.n or m.n.o to an encoded version
3029    number (or None if it was an invalid format).  version_str is the version
3030    string.
3031    """
3032
3033    parts = version_str.split('.')
3034    if not isinstance(parts, list):
3035        return None
3036
3037    if len(parts) == 2:
3038        parts.append('0')
3039
3040    if len(parts) != 3:
3041        return None
3042
3043    version = 0
3044
3045    for part in parts:
3046        try:
3047            v = int(part)
3048        except ValueError:
3049            return None
3050
3051        version = (version << 8) + v
3052
3053    return version
3054
3055
3056def open_for_writing(fname):
3057    """ Return a file opened for writing while handling the most common problem
3058    of not having write permission on the directory.  fname is the name of the
3059    file to open.
3060    """
3061    try:
3062        return open(fname, 'w')
3063    except IOError:
3064        error(
3065                "There was an error creating %s.  Make sure you have write "
3066                "permission on the parent directory." % fname)
3067
3068
3069def matching_files(pattern):
3070    """ Return a reproducable list of files that match a pattern. """
3071
3072    return sorted(glob.glob(pattern))
3073
3074
3075def main(argv):
3076    """ Create the configuration module module.  argv is the list of command
3077    line arguments.
3078    """
3079
3080    # Create the default target configuration.
3081    target_config = TargetConfiguration()
3082
3083    # Parse the command line.
3084    parser = create_optparser(target_config)
3085    opts, target_config.qmake_variables = parser.parse_args()
3086
3087    target_config.apply_pre_options(opts)
3088
3089    # Query qmake for the basic configuration information.
3090    target_config.get_qt_configuration()
3091
3092    # Update the target configuration.
3093    if opts.config_file is not None:
3094        target_config.from_configuration_file(opts.config_file)
3095    else:
3096        target_config.from_introspection(opts.verbose, opts.debug)
3097
3098    target_config.post_configuration()
3099
3100    target_config.apply_post_options(opts)
3101
3102    # Check the licenses are compatible.
3103    check_license(target_config, opts.license_confirmed)
3104
3105    # Check Python is what we need.
3106    check_python(target_config)
3107
3108    # Check SIP is what we need.
3109    sip_version = check_sip(target_config, opts.verbose)
3110
3111    # Check Qt is what we need.
3112    check_qt(target_config)
3113
3114    # Check for the VendorID package.
3115    check_vendorid(target_config)
3116
3117    # Check which modules to build if we haven't been told.
3118    if len(target_config.pyqt_modules) == 0:
3119        check_modules(target_config, opts.disabled_modules, opts.verbose)
3120    else:
3121        # Check that the user supplied module names are valid.
3122        for mname in target_config.pyqt_modules:
3123            if mname not in MODULE_METADATA:
3124                error("'%s' is not a valid module name." % mname)
3125
3126    check_dbus(target_config, opts.verbose)
3127
3128    # Tell the user what's been found.
3129    inform_user(target_config, sip_version)
3130
3131    # Generate the makefiles.
3132    generate_makefiles(target_config, opts.verbose,
3133            opts.split if opts.concat else 0, opts.tracing,
3134            opts.fatal_warnings, opts.distinfo)
3135
3136
3137###############################################################################
3138# The script starts here.
3139###############################################################################
3140
3141if __name__ == '__main__':
3142    try:
3143        main(sys.argv)
3144    except SystemExit:
3145        raise
3146    except:
3147        sys.stderr.write(
3148"""An internal error occured.  Please report all the output from the program,
3149including the following traceback, to support@riverbankcomputing.com.
3150""")
3151        raise
3152