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 == '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 == '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        pyqt_modules.append('pylupdate')
1506        pyqt_modules.append('pyrcc')
1507
1508    for mname in pyqt_modules:
1509        metadata = MODULE_METADATA[mname]
1510
1511        if metadata.qpy_lib:
1512            sp_qpy_dir = source_path('qpy', mname)
1513
1514            qpy_c_sources = [os.path.relpath(f, mname)
1515                    for f in matching_files(os.path.join(sp_qpy_dir, '*.c'))]
1516            qpy_cpp_sources = [os.path.relpath(f, mname)
1517                    for f in matching_files(os.path.join(sp_qpy_dir, '*.cpp'))]
1518            qpy_headers = [os.path.relpath(f, mname)
1519                    for f in matching_files(os.path.join(sp_qpy_dir, '*.h'))]
1520
1521            qpy_sources = qpy_c_sources + qpy_cpp_sources
1522        else:
1523            qpy_sources = []
1524            qpy_headers = []
1525
1526        generate_sip_module_code(target_config, verbose, parts, tracing, mname,
1527                fatal_warnings, sip_flags, metadata.public, qpy_sources,
1528                qpy_headers)
1529        subdirs.append(mname)
1530
1531    # Generate the composite module.
1532    qtmod_sipdir = os.path.join('sip', 'Qt')
1533    mk_clean_dir(qtmod_sipdir)
1534
1535    qtmod_sipfile = os.path.join(qtmod_sipdir, 'Qtmod.sip')
1536    f = open_for_writing(qtmod_sipfile)
1537
1538    f.write('''%CompositeModule PyQt5.Qt
1539
1540''')
1541
1542    for mname in COMPOSITE_COMPONENTS:
1543        if mname in target_config.pyqt_modules:
1544            f.write('%%Include %s/%smod.sip\n' % (mname, mname))
1545
1546    f.close()
1547
1548    generate_sip_module_code(target_config, verbose, parts, tracing, 'Qt',
1549            fatal_warnings, sip_flags, False)
1550    subdirs.append('Qt')
1551
1552    # Generate the top-level __init__.py.
1553    inf = open(source_path('__init__.py'))
1554    contents = inf.read()
1555    inf.close()
1556
1557    inf = open_for_writing('__init__.py')
1558    inf.write(contents)
1559
1560    if target_config.py_platform == 'win32':
1561        # On Windows we try and make sure the Qt DLLs can be found, either any
1562        # bundled copies or an existing installation (using the traditional
1563        # Windows DLL search).  We don't raise an exception in case the
1564        # application has already taken steps to resolve this which we don't
1565        # know about.
1566        inf.write("""
1567
1568def find_qt():
1569    import os, sys
1570
1571    qtcore_dll = '\\\\Qt5Core.dll'
1572
1573    dll_dir = os.path.dirname(sys.executable)
1574    if not os.path.isfile(dll_dir + qtcore_dll):
1575        path = os.environ['PATH']
1576
1577        dll_dir = os.path.dirname(__file__) + '\\\\Qt5\\\\bin'
1578        if os.path.isfile(dll_dir + qtcore_dll):
1579            path = dll_dir + ';' + path
1580            os.environ['PATH'] = path
1581        else:
1582            for dll_dir in path.split(';'):
1583                if os.path.isfile(dll_dir + qtcore_dll):
1584                    break
1585            else:
1586                return
1587
1588    try:
1589        os.add_dll_directory(dll_dir)
1590    except AttributeError:
1591        pass
1592
1593
1594find_qt()
1595del find_qt
1596""")
1597
1598    inf.close()
1599
1600    # Generate any executable wrappers.
1601    wrappers = []
1602    if not target_config.no_tools:
1603        # Generate the pylupdate5 and pyrcc5 wrappers.
1604        for tool in ('pylupdate', 'pyrcc'):
1605            wrappers.append((tool,
1606                    generate_tool_wrapper(target_config, tool + '5',
1607                            'PyQt5.%s_main' % tool)))
1608
1609        # Generate the pyuic5 wrapper.
1610        wrappers.append(('pyuic',
1611                generate_tool_wrapper(target_config, 'pyuic5',
1612                        'PyQt5.uic.pyuic')))
1613
1614    # Generate the Qt Designer plugin.
1615    if not target_config.no_designer_plugin and 'QtDesigner' in target_config.pyqt_modules:
1616        if generate_plugin_makefile(target_config, verbose, 'designer', target_config.designer_plugin_dir, "Qt Designer"):
1617            subdirs.append('designer')
1618
1619    # Generate the qmlscene plugin.
1620    if not target_config.no_qml_plugin and 'QtQml' in target_config.pyqt_modules:
1621        if generate_plugin_makefile(target_config, verbose, 'qmlscene', target_config.qml_plugin_dir, "qmlscene"):
1622            subdirs.append('qmlscene')
1623
1624            rewrite_qmldir(target_config, 'Charts',
1625                    source_path('examples', 'quick', 'tutorials', 'extending',
1626                            'chapter6-plugins'))
1627
1628    # Generate the QScintilla API file.
1629    if target_config.qsci_api:
1630        inform("Generating the QScintilla API file...")
1631        f = open_for_writing('PyQt5.api')
1632
1633        for mname in target_config.pyqt_modules:
1634            if MODULE_METADATA[mname].public:
1635                api = open(mname + '.api')
1636
1637                for l in api:
1638                    f.write('PyQt5.' + l)
1639
1640                api.close()
1641                os.remove(mname + '.api')
1642
1643        f.close()
1644
1645    # Generate the Python dbus module.
1646    if target_config.pydbus_module_dir != '':
1647        mname = 'dbus'
1648
1649        mk_dir(mname)
1650        sp_src_dir = source_path(mname)
1651
1652        lib_dirs = ['-L' + l for l in target_config.dbus_lib_dirs]
1653        lib_names = ['-l' + l for l in target_config.dbus_libs]
1654        libs = ' '.join(lib_dirs + lib_names)
1655
1656        generate_module_makefile(target_config, verbose, mname,
1657                include_paths=target_config.dbus_inc_dirs, libs=libs,
1658                install_path=target_config.pydbus_module_dir,
1659                src_dir=sp_src_dir)
1660
1661        subdirs.append(mname)
1662
1663    # Generate the top-level .pro file.
1664    all_installs = []
1665
1666    inform("Generating the top-level .pro file...")
1667    out_f = open_for_writing(toplevel_pro)
1668
1669    root_dir = qmake_quote(target_config.pyqt_module_dir + '/PyQt5')
1670
1671    for mname in pyqt_modules:
1672        all_installs.append(
1673                root_dir + '/' + module_file_name(target_config, mname))
1674
1675    all_installs.append(root_dir + '/' + module_file_name(target_config, 'Qt'))
1676
1677    out_f.write('''TEMPLATE = subdirs
1678CONFIG += ordered nostrip
1679SUBDIRS = %s
1680
1681init_py.files = __init__.py
1682init_py.path = %s
1683INSTALLS += init_py
1684''' % (' '.join(subdirs), root_dir))
1685
1686    all_installs.append(root_dir + '/__init__.py')
1687
1688    # Install the uic module.
1689    out_f.write('''
1690uic_package.files = %s
1691uic_package.path = %s
1692INSTALLS += uic_package
1693''' % (source_path('pyuic', 'uic'), root_dir))
1694
1695    all_installs.append(root_dir + '/uic')
1696
1697    # Install the tool main scripts and wrappers.
1698    if wrappers:
1699        wrapper_exes = []
1700        for tool, wrapper in wrappers:
1701            if tool != 'pyuic':
1702                tool_main = tool + '_main.py'
1703
1704                out_f.write('''
1705%s.files = %s
1706%s.path = %s
1707INSTALLS += %s
1708''' % (tool, source_path('sip', tool, tool_main), tool, root_dir, tool))
1709
1710                all_installs.append(root_dir + '/' + tool_main)
1711
1712            wrapper_exes.append(wrapper)
1713            all_installs.append(target_config.pyqt_bin_dir + '/' + wrapper)
1714
1715        out_f.write('''
1716tools.files = %s
1717tools.path = %s
1718INSTALLS += tools
1719''' % (' '.join(wrapper_exes), qmake_quote(target_config.pyqt_bin_dir)))
1720
1721    # Install the .sip files.
1722    if target_config.pyqt_sip_dir:
1723        for mname, metadata in MODULE_METADATA.items():
1724            if metadata.public and mname != 'Qt':
1725                sip_files = matching_files(source_path('sip', mname, '*.sip'))
1726
1727                if len(sip_files) != 0:
1728                    mdir = target_config.pyqt_sip_dir + '/' + mname
1729
1730                    out_f.write('''
1731sip%s.path = %s
1732sip%s.files = %s
1733INSTALLS += sip%s
1734''' % (
1735    mname, qmake_quote(mdir),
1736    mname, ' '.join([qmake_quote(s) for s in sip_files]),
1737    mname
1738))
1739
1740                    all_installs.append(mdir)
1741
1742    # Install the stub files.
1743    if target_config.py_version >= 0x030500 and target_config.pyqt_stubs_dir:
1744        pyi_names = [mname + '.pyi'
1745                for mname in target_config.pyqt_modules if mname[0] != '_']
1746
1747        out_f.write('''
1748pep484_stubs.files = %s
1749pep484_stubs.path = %s
1750INSTALLS += pep484_stubs
1751''' % (' '.join(pyi_names),
1752            qmake_quote(target_config.pyqt_stubs_dir)))
1753
1754        all_installs.extend(
1755                [target_config.pyqt_stubs_dir + '/' + pyi
1756                        for pyi in pyi_names])
1757
1758    # Install the QScintilla .api file.
1759    if target_config.qsci_api:
1760        api_dir = target_config.qsci_api_dir + '/api/python'
1761
1762        out_f.write('''
1763qscintilla_api.files = PyQt5.api
1764qscintilla_api.path = %s
1765INSTALLS += qscintilla_api
1766''' % qmake_quote(api_dir))
1767
1768        all_installs.append(api_dir + '/PyQt5.api')
1769
1770    if distinfo:
1771        # The command to run to generate the .dist-info directory.
1772        distinfo_dir = os.path.join(target_config.pyqt_module_dir,
1773                'PyQt5-' + PYQT_VERSION_STR + '.dist-info')
1774
1775        run_mk_distinfo = '%s %s \\"$(INSTALL_ROOT)\\" %s installed.txt' % (
1776                sys.executable, source_path('mk_distinfo.py'), distinfo_dir)
1777
1778        out_f.write('''
1779distinfo.extra = %s
1780distinfo.path = %s
1781INSTALLS += distinfo
1782''' % (run_mk_distinfo, root_dir))
1783
1784        # Create the file containing all installed files.
1785        installed = open('installed.txt', 'w')
1786
1787        for install in all_installs:
1788            installed.write(install + '\n')
1789
1790        installed.close()
1791
1792    out_f.close()
1793
1794    # Make the wrappers executable on platforms that support it.  If we did it
1795    # after running qmake then (on Linux) the execute bits would be stripped on
1796    # installation.
1797    if target_config.py_platform != 'win32':
1798        for tool, wrapper in wrappers:
1799            inform("Making the %s wrapper executable..." % wrapper)
1800
1801            sbuf = os.stat(wrapper)
1802            mode = sbuf.st_mode
1803            mode |= (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
1804            os.chmod(wrapper, mode)
1805
1806    # Generate the makefiles.
1807    inform("Generating the Makefiles...")
1808    run_qmake(target_config, verbose, toplevel_pro, recursive=True)
1809
1810
1811def generate_plugin_makefile(target_config, verbose, plugin_dir, install_dir, plugin_name):
1812    """ Generate the makefile for a plugin that embeds the Python interpreter.
1813    target_config is the target configuration.  verbose is set if the output is
1814    to be displayed.  plugin_dir is the name of the directory containing the
1815    plugin implementation.  install_dir is the name of the directory that the
1816    plugin will be installed in.  plugin_name is a descriptive name of the
1817    plugin to be used in user messages.  Returns True if the makefile could be
1818    generated.
1819    """
1820
1821    # Check we have a shared interpreter library.
1822    if target_config.py_pyshlib == '':
1823        inform("The %s plugin was disabled because a dynamic Python library couldn't be found." % plugin_name)
1824        return False
1825
1826    # Create the qmake project file.
1827    inform("Generating the %s plugin .pro file..." % plugin_name)
1828
1829    sp_plugin_dir = source_path(plugin_dir)
1830
1831    fin = open(os.path.join(sp_plugin_dir, '%s.pro-in' % plugin_dir))
1832    prj = fin.read()
1833    fin.close()
1834
1835    prj = prj.replace('@QTCONFIG@',
1836            'debug' if target_config.debug else 'release')
1837    prj = prj.replace('@PYINCDIR@', qmake_quote(target_config.py_inc_dir))
1838    prj = prj.replace('@SIPINCDIR@', qmake_quote(target_config.sip_inc_dir))
1839    prj = prj.replace('@PYLINK@', target_config.get_pylib_link_arguments())
1840    prj = prj.replace('@PYSHLIB@', target_config.py_pyshlib)
1841    prj = prj.replace('@QTPLUGINDIR@', qmake_quote(install_dir))
1842
1843    pro_name = os.path.join(plugin_dir, '%s.pro' % plugin_dir)
1844
1845    mk_dir(plugin_dir)
1846    fout = open_for_writing(pro_name)
1847    fout.write(prj)
1848
1849    if sp_plugin_dir != plugin_dir:
1850        fout.write('''
1851INCLUDEPATH += %s
1852VPATH = %s
1853''' % (qmake_quote(sp_plugin_dir), qmake_quote(sp_plugin_dir)))
1854
1855    fout.write('\n'.join(target_config.qmake_variables) + '\n')
1856
1857    fout.close()
1858
1859    return True
1860
1861
1862def pro_sources(src_dir, other_headers=None, other_sources=None):
1863    """ Return the HEADERS, SOURCES and OBJECTIVE_SOURCES variables for a .pro
1864    file by introspecting a directory.  src_dir is the name of the directory.
1865    other_headers is an optional list of other header files.  other_sources is
1866    an optional list of other source files.
1867    """
1868
1869    pro_lines = []
1870
1871    headers = [os.path.basename(f) for f in matching_files('%s/*.h' % src_dir)]
1872
1873    if other_headers is not None:
1874        headers += other_headers
1875
1876    if len(headers) != 0:
1877        pro_lines.append('HEADERS = %s' % ' '.join(headers))
1878
1879    sources = [os.path.basename(f) for f in matching_files('%s/*.c' % src_dir)]
1880
1881    for f in matching_files('%s/*.cpp' % src_dir):
1882        f = os.path.basename(f)
1883
1884        # Exclude any moc generated C++ files that might be around from a
1885        # previous build.
1886        if not f.startswith('moc_'):
1887            sources.append(f)
1888
1889    if other_sources is not None:
1890        sources += other_sources
1891
1892    if len(sources) != 0:
1893        pro_lines.append('SOURCES = %s' % ' '.join([qmake_quote(s) for s in sources]))
1894
1895    objective_sources = [
1896            os.path.basename(f) for f in matching_files('%s/*.mm' % src_dir)]
1897
1898    if len(objective_sources) != 0:
1899        pro_lines.append('OBJECTIVE_SOURCES = %s' % ' '.join([qmake_quote(s) for s in objective_sources]))
1900
1901    return pro_lines
1902
1903
1904def module_file_name(target_config, name):
1905    """ Return the name of a file implementing a module. """
1906
1907    if sys.platform == 'win32':
1908        fs = '{}.lib' if target_config.static else '{}.pyd'
1909    else:
1910        fs = 'lib{}.a' if target_config.static else '{}.so'
1911
1912    return fs.format(name)
1913
1914
1915def generate_tool_wrapper(target_config, wrapper, module):
1916    """ Create a platform dependent executable wrapper for a tool module.
1917    target_config is the target configuration.  wrapper is the name of the
1918    wrapper without any extension.  module is the tool module.  Returns the
1919    platform specific name of the wrapper.
1920    """
1921
1922    if target_config.py_platform == 'win32':
1923        wrapper += '.bat'
1924
1925    inform("Generating the %s wrapper..." % wrapper)
1926
1927    exe = quote(target_config.pyuic_interpreter)
1928
1929    wf = open_for_writing(wrapper)
1930
1931    if target_config.py_platform == 'win32':
1932        wf.write('@%s -m %s %%1 %%2 %%3 %%4 %%5 %%6 %%7 %%8 %%9\n' % (exe, module))
1933    else:
1934        wf.write('#!/bin/sh\n')
1935        wf.write('exec %s -m %s ${1+"$@"}\n' % (exe, module))
1936
1937    wf.close()
1938
1939    return wrapper
1940
1941
1942def rewrite_qmldir(target_config, module, module_dir):
1943    """ Re-write a qmldir file for a module that used the qmlscene plugin.
1944    target_config is the target configuration.  module is the name of the QML
1945    module.  module_dir is the name of the directory containing the QML module.
1946    """
1947
1948    qmldir_fn = os.path.join(module_dir, module, 'qmldir')
1949
1950    inform("Re-writing %s..." % qmldir_fn)
1951
1952    qmldir = open_for_writing(qmldir_fn)
1953    qmldir.write('module %s\nplugin pyqt5qmlplugin %s\n' % (module, target_config.qml_plugin_dir))
1954    qmldir.close()
1955
1956
1957def quote(path):
1958    """ Return a path with quotes added if it contains spaces.  path is the
1959    path.
1960    """
1961
1962    if ' ' in path:
1963        path = '"%s"' % path
1964
1965    return path
1966
1967
1968def qmake_quote(path):
1969    """ Return a path quoted for qmake if it contains spaces.  path is the
1970    path.
1971    """
1972
1973    if ' ' in path:
1974        path = '$$quote(%s)' % path
1975
1976    return path
1977
1978
1979def inform_user(target_config, sip_version):
1980    """ Tell the user the values that are going to be used.  target_config is
1981    the target configuration.  sip_version is the SIP version string.
1982    """
1983
1984    inform("Qt v%s is being used." %
1985            version_to_string(target_config.qt_version))
1986
1987    inform("The qmake executable is %s." % target_config.qmake)
1988
1989    inform(
1990            "Qt is built as a %s library." % (
1991                    "shared" if target_config.qt_shared else "static"))
1992
1993    if target_config.sysroot != '':
1994        inform("The system root directory is %s." % target_config.sysroot)
1995
1996    inform("SIP %s is being used." % sip_version)
1997    inform("The sip executable is %s." % target_config.sip)
1998    inform("These PyQt5 modules will be built: %s." % ', '.join(target_config.pyqt_modules))
1999    inform("The PyQt5 Python package will be installed in %s." % target_config.pyqt_module_dir)
2000
2001    if target_config.debug:
2002        inform("A debug version of PyQt5 will be built.")
2003
2004    if target_config.py_debug:
2005        inform("A debug build of Python is being used.")
2006
2007    if target_config.no_docstrings:
2008        inform("PyQt5 is being built without generated docstrings.")
2009    else:
2010        inform("PyQt5 is being built with generated docstrings.")
2011
2012    if target_config.prot_is_public:
2013        inform("PyQt5 is being built with 'protected' redefined as 'public'.")
2014
2015    if target_config.no_designer_plugin:
2016        inform("The Designer plugin will not be built.")
2017    else:
2018        inform("The Designer plugin will be installed in %s." %
2019                target_config.designer_plugin_dir)
2020
2021    if target_config.no_qml_plugin:
2022        inform("The qmlscene plugin will not be built.")
2023    else:
2024        inform("The qmlscene plugin will be installed in %s." %
2025                target_config.qml_plugin_dir)
2026
2027    if target_config.qsci_api:
2028        inform(
2029                "The QScintilla API file will be installed in %s." %
2030                        os.path.join(
2031                                target_config.qsci_api_dir, 'api', 'python'))
2032
2033    if target_config.py_version >= 0x030500 and target_config.pyqt_stubs_dir:
2034        inform("The PyQt5 PEP 484 stub files will be installed in %s." %
2035                target_config.pyqt_stubs_dir)
2036
2037    if target_config.pydbus_module_dir:
2038        inform(
2039                "The dbus support module will be installed in %s." %
2040                        target_config.pydbus_module_dir)
2041
2042    if target_config.pyqt_sip_dir:
2043        inform("The PyQt5 .sip files will be installed in %s." %
2044                target_config.pyqt_sip_dir)
2045
2046    if target_config.no_tools:
2047        inform("pyuic5, pyrcc5 and pylupdate5 will not be built.")
2048    else:
2049        inform("pyuic5, pyrcc5 and pylupdate5 will be installed in %s." %
2050                target_config.pyqt_bin_dir)
2051
2052        inform("The interpreter used by pyuic5 is %s." %
2053                target_config.pyuic_interpreter)
2054
2055    if target_config.vend_enabled:
2056        inform("PyQt5 will only be usable with signed interpreters.")
2057
2058
2059def run_qmake(target_config, verbose, pro_name, makefile_name='', fatal=True, recursive=False):
2060    """ Run qmake against a .pro file.  target_config is the target
2061    configuration.  verbose is set if the output is to be displayed.  pro_name
2062    is the name of the .pro file.  makefile_name is the name of the makefile
2063    to generate (and defaults to Makefile).  fatal is set if a qmake failure is
2064    considered a fatal error, otherwise False is returned if qmake fails.
2065    recursive is set to use the -recursive flag.
2066    """
2067
2068    # qmake doesn't behave consistently if it is not run from the directory
2069    # containing the .pro file - so make sure it is.
2070    pro_dir, pro_file = os.path.split(pro_name)
2071    if pro_dir != '':
2072        cwd = os.getcwd()
2073        os.chdir(pro_dir)
2074    else:
2075        cwd = None
2076
2077    mf = makefile_name if makefile_name != '' else 'Makefile'
2078
2079    remove_file(mf)
2080
2081    args = [quote(target_config.qmake)]
2082
2083    if target_config.qmake_spec != target_config.qmake_spec_default:
2084        args.append('-spec')
2085        args.append(target_config.qmake_spec)
2086
2087    if makefile_name != '':
2088        args.append('-o')
2089        args.append(makefile_name)
2090
2091    if recursive:
2092        args.append('-recursive')
2093
2094    args.append(pro_file)
2095
2096    run_command(' '.join(args), verbose)
2097
2098    if not os.access(mf, os.F_OK):
2099        if fatal:
2100            error(
2101                    "%s failed to create a makefile from %s." %
2102                            (target_config.qmake, pro_name))
2103
2104        return False
2105
2106    # Restore the current directory.
2107    if cwd is not None:
2108        os.chdir(cwd)
2109
2110    return True
2111
2112
2113def run_make(target_config, verbose, exe, makefile_name):
2114    """ Run make against a makefile to create an executable.  target_config is
2115    the target configuration.  verbose is set if the output is to be displayed.
2116    exe is the platform independent name of the executable that will be
2117    created.  makefile_name is the name of the makefile.  Returns the platform
2118    specific name of the executable, or None if an executable wasn't created.
2119    """
2120
2121    # Guess the name of make and set the default target and platform specific
2122    # name of the executable.
2123    if target_config.py_platform == 'win32':
2124        if target_config.qmake_spec == 'win32-g++':
2125            make = 'mingw32-make'
2126        else:
2127            make = 'nmake'
2128
2129        if target_config.debug:
2130            makefile_target = 'debug'
2131            platform_exe = os.path.join('debug', exe + '.exe')
2132        else:
2133            makefile_target = 'release'
2134            platform_exe = os.path.join('release', exe + '.exe')
2135    else:
2136        make = 'make'
2137        makefile_target = ''
2138
2139        if target_config.py_platform == 'darwin':
2140            platform_exe = os.path.join(exe + '.app', 'Contents', 'MacOS', exe)
2141        else:
2142            platform_exe = os.path.join('.', exe)
2143
2144    remove_file(platform_exe)
2145
2146    args = [make, '-f', makefile_name]
2147
2148    if makefile_target != '':
2149        args.append(makefile_target)
2150
2151    run_command(' '.join(args), verbose)
2152
2153    return platform_exe if os.access(platform_exe, os.X_OK) else None
2154
2155
2156def run_command(cmd, verbose):
2157    """ Run a command and display the output if requested.  cmd is the command
2158    to run.  verbose is set if the output is to be displayed.
2159    """
2160
2161    if verbose:
2162        sys.stdout.write(cmd + "\n")
2163
2164    fout = get_command_output(cmd, and_stderr=True)
2165
2166    # Read stdout and stderr until there is no more output.
2167    lout = fout.readline()
2168    while lout:
2169        if verbose:
2170            if sys.hexversion >= 0x03000000:
2171                sys.stdout.write(str(lout, encoding=sys.stdout.encoding))
2172            else:
2173                sys.stdout.write(lout)
2174
2175        lout = fout.readline()
2176
2177    close_command_pipe(fout)
2178
2179
2180def remove_file(fname):
2181    """ Remove a file which may or may not exist.  fname is the name of the
2182    file.
2183    """
2184
2185    try:
2186        os.remove(fname)
2187    except OSError:
2188        pass
2189
2190
2191def check_vendorid(target_config):
2192    """ See if the VendorID library and include file can be found.
2193    target_config is the target configuration.
2194    """
2195
2196    if target_config.py_version >= 0x030000:
2197        # VendorID doesn't support Python v3.
2198        target_config.vend_enabled = False
2199    elif target_config.vend_enabled:
2200        if os.access(os.path.join(target_config.vend_inc_dir, 'vendorid.h'), os.F_OK):
2201            if glob.glob(os.path.join(target_config.vend_lib_dir, '*vendorid*')):
2202                inform("The VendorID package was found.")
2203            else:
2204                target_config.vend_enabled = False
2205                inform(
2206                        "The VendorID library could not be found in %s and so "
2207                        "signed interpreter checking will be disabled. If the "
2208                        "VendorID package is installed then use the "
2209                        "--vendorid-libdir argument to explicitly specify the "
2210                        "correct directory." % target_config.vend_lib_dir)
2211        else:
2212            target_config.vend_enabled = False
2213            inform(
2214                    "vendorid.h could not be found in %s and so signed "
2215                    "interpreter checking will be disabled. If the VendorID "
2216                    "package is installed then use the --vendorid-incdir "
2217                    "argument to explicitly specify the correct directory." %
2218                            target_config.vend_inc_dir)
2219
2220
2221def get_command_output(cmd, and_stderr=False):
2222    """ Return a pipe from which a command's output can be read.  cmd is the
2223    command.  and_stderr is set if the output should include stderr as well as
2224    stdout.
2225    """
2226
2227    try:
2228        import subprocess
2229    except ImportError:
2230        if and_stderr:
2231            _, sout = os.popen4(cmd)
2232        else:
2233            _, sout, _ = os.popen3(cmd)
2234
2235        return sout
2236
2237    if and_stderr:
2238        stderr = subprocess.STDOUT
2239    else:
2240        stderr = subprocess.PIPE
2241
2242    p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
2243            stdout=subprocess.PIPE, stderr=stderr)
2244
2245    return p.stdout
2246
2247
2248def close_command_pipe(pipe):
2249    """ Close the pipe returned by get_command_output(). """
2250
2251    pipe.close()
2252
2253    try:
2254        os.wait()
2255    except:
2256        pass
2257
2258
2259def source_path(*names):
2260    """ Return the native path for a list of components rooted at the directory
2261    containing this script.  names is the sequence of component names.
2262    """
2263
2264    path = [os.path.dirname(os.path.abspath(__file__))] + list(names)
2265
2266    return os.path.join(*path)
2267
2268
2269def check_dbus(target_config, verbose):
2270    """ See if the DBus support module should be built and update the target
2271    configuration accordingly.  target_config is the target configuration.
2272    verbose is set if the output is to be displayed.
2273    """
2274
2275    if target_config.no_pydbus or not os.path.isdir(source_path('dbus')):
2276        return
2277
2278    inform("Checking to see if the dbus support module should be built...")
2279
2280    cmd = 'pkg-config --cflags-only-I --libs dbus-1'
2281
2282    if verbose:
2283        sys.stdout.write(cmd + "\n")
2284
2285    sout = get_command_output(cmd)
2286    iflags = sout.read().strip()
2287    close_command_pipe(sout)
2288
2289    if not iflags:
2290        inform("DBus v1 does not seem to be installed.")
2291        return
2292
2293    if sys.hexversion >= 0x03000000:
2294        iflags = iflags.decode()
2295
2296    for f in iflags.split():
2297        if f.startswith('-I'):
2298            target_config.dbus_inc_dirs.append(f[2:])
2299        elif f.startswith('-L'):
2300            target_config.dbus_lib_dirs.append(f[2:])
2301        elif f.startswith('-l'):
2302            target_config.dbus_libs.append(f[2:])
2303
2304    try:
2305        import dbus.mainloop
2306    except:
2307        inform("The Python dbus module doesn't seem to be installed.")
2308        return
2309
2310    target_config.pydbus_module_dir = dbus.mainloop.__path__[0]
2311
2312    # Try and find dbus-python.h.  We don't use pkg-config because it is broken
2313    # for dbus-python (at least for versions up to and including v0.81.0).
2314    # Instead we look where DBus itself is installed - which in most cases will
2315    # be where dbus-python is also installed.
2316    if target_config.pydbus_inc_dir != '':
2317        target_config.dbus_inc_dirs = [target_config.pydbus_inc_dir]
2318
2319    for d in target_config.dbus_inc_dirs:
2320        if os.access(os.path.join(d, 'dbus', 'dbus-python.h'), os.F_OK):
2321            break
2322    else:
2323        inform(
2324                "dbus/dbus-python.h could not be found and so the DBus "
2325                "support module will be disabled. If dbus-python v0.80 or "
2326                "later is installed then use the --dbus argument to "
2327                "explicitly specify the directory containing "
2328                "dbus/dbus-python.h.")
2329        target_config.pydbus_module_dir = ''
2330
2331
2332def check_module(target_config, disabled_modules, verbose, mname, incfile=None, test=None):
2333    """ See if a module can be built and, if so, add it to the target
2334    configurations list of modules.  target_config is the target configuration.
2335    disabled_modules is the list of modules that have been explicitly disabled.
2336    verbose is set if the output is to be displayed.  mname is the name of the
2337    module.  incfile is the name of the include file (or sequence of multiple
2338    include files) needed for the test.  test is a C++ statement being used for
2339    the test.  If either incfile or test are None then there is a test program
2340    that needs to be run and its output captured.
2341    """
2342
2343    if mname in disabled_modules:
2344        return
2345
2346    # Check the module's main .sip file exists.
2347    if not os.access(source_path('sip', mname, mname + 'mod.sip'), os.F_OK):
2348        return
2349
2350    if verbose:
2351        sys.stdout.write('\n')
2352
2353    inform("Checking to see if the %s module should be built..." % mname)
2354
2355    if incfile is None or test is None:
2356        source = None
2357    else:
2358        if isinstance(incfile, str):
2359            incfile = [incfile]
2360
2361        incfile = ['#include<%s>' % i for i in incfile]
2362
2363        source = '''%s
2364
2365int main(int, char **)
2366{
2367    %s;
2368}
2369''' % ('\n'.join(incfile), test)
2370
2371    test = compile_test_program(target_config, verbose, mname, source)
2372    if test is None:
2373        return
2374
2375    # If there was an explicit test program then run it to get the disabled
2376    # features.
2377    if source is None:
2378        for disabled in run_test_program(mname, test, verbose):
2379            if disabled:
2380                inform("Disabled %s feature: %s" % (mname, disabled))
2381                target_config.pyqt_disabled_features.append(disabled)
2382
2383    # Include the module in the build.
2384    target_config.pyqt_modules.append(mname)
2385
2386
2387def compile_test_program(target_config, verbose, mname, source=None, debug=None):
2388    """ Compile the source of a Qt program and return the name of the
2389    executable or None if it couldn't be created.  target_config is the target
2390    configuration.  verbose is set if the output is to be displayed.  mname is
2391    the name of the PyQt module being tested.  source is the C++ source of the
2392    program.  If it is None then the source is expected to be found in the
2393    config-tests directory.  debug is set if debug, rather than release, mode
2394    is to be used.  If it is None then the mode is taken from the target
2395    configuration.
2396    """
2397
2398    metadata = MODULE_METADATA[mname]
2399
2400    # The derived file names.
2401    name = 'cfgtest_' + mname
2402    name_pro = name + '.pro'
2403    name_makefile = name + '.mk'
2404    name_source = name + '.cpp'
2405
2406    # Create the source file if necessary.
2407    if source is None:
2408        name_source = source_path('config-tests', name_source)
2409    else:
2410        f = open_for_writing(name_source)
2411        f.write(source)
2412        f.close()
2413
2414    # Create the .pro file.
2415    pro_lines = []
2416    pro_add_qt_dependencies(target_config, metadata, pro_lines, debug)
2417    pro_lines.append('TARGET = %s' % name)
2418
2419    pro_lines.append('SOURCES = %s' % qmake_quote(name_source))
2420
2421    f = open_for_writing(name_pro)
2422    f.write('\n'.join(pro_lines))
2423    f.close()
2424
2425    if not run_qmake(target_config, verbose, name_pro, name_makefile, fatal=False):
2426        return None
2427
2428    return run_make(target_config, verbose, name, name_makefile)
2429
2430
2431def run_test_program(mname, test, verbose):
2432    """ Run a test program and return the output as a list of lines.  mname is
2433    the name of the PyQt module being tested.  test is the name of the test
2434    executable.  verbose is set if the output is to be displayed.
2435    """
2436
2437    out_file = 'cfgtest_' + mname + '.out'
2438
2439    # Create the output file, first making sure it doesn't exist.
2440    remove_file(out_file)
2441    run_command(test + ' ' + out_file, verbose)
2442
2443    if not os.access(out_file, os.F_OK):
2444        error("%s failed to create %s. Make sure your Qt installation is correct." % (test, out_file))
2445
2446    # Read the details.
2447    f = open(out_file)
2448    lines = f.read().strip()
2449    f.close()
2450
2451    return lines.split('\n') if lines else []
2452
2453
2454def pro_add_qt_dependencies(target_config, metadata, pro_lines, debug=None):
2455    """ Add the Qt dependencies of a module to a .pro file.  target_config is
2456    the target configuration.  metadata is the module's meta-data.  pro_lines
2457    is the list of lines making up the .pro file that is updated.  debug is set
2458    if debug, rather than release, mode is to be used.  If it is None then the
2459    mode is taken from the target configuration.
2460    """
2461
2462    if debug is None:
2463        debug = target_config.debug
2464
2465    add = []
2466    remove = []
2467    for qt in metadata.qmake_QT:
2468        if qt.startswith('-'):
2469            remove.append(qt[1:])
2470        else:
2471            add.append(qt)
2472
2473    if len(remove) != 0:
2474        pro_lines.append('QT -= %s' % ' '.join(remove))
2475
2476    if len(add) != 0:
2477        pro_lines.append('QT += %s' % ' '.join(add))
2478
2479    pro_lines.append(
2480            'CONFIG += %s' % ('debug' if debug else 'release'))
2481
2482    if metadata.cpp11:
2483        pro_lines.append('CONFIG += c++11')
2484
2485    pro_lines.extend(target_config.qmake_variables)
2486
2487
2488def get_sip_flags(target_config):
2489    """ Return the SIP platform, version and feature flags.  target_config is
2490    the target configuration.
2491    """
2492
2493    sip_flags = ['-n', 'PyQt5.sip']
2494
2495    # If we don't check for signed interpreters, we exclude the 'VendorID'
2496    # feature
2497    if target_config.py_version < 0x030000 and not target_config.vend_enabled:
2498        sip_flags.append('-x')
2499        sip_flags.append('VendorID')
2500
2501    # Handle Python debug builds.
2502    if target_config.py_debug:
2503        sip_flags.append('-D')
2504
2505    # Handle the platform tag.  (Allow for win32-g++.)
2506    if target_config.py_platform.startswith('win32'):
2507        plattag = 'WS_WIN'
2508    elif target_config.py_platform == 'darwin':
2509        plattag = 'WS_MACX'
2510    else:
2511        plattag = 'WS_X11'
2512
2513    sip_flags.append('-t')
2514    sip_flags.append(plattag)
2515
2516    # Handle the Qt version tag.
2517    sip_flags.append('-t')
2518    sip_flags.append(version_to_sip_tag(target_config.qt_version))
2519
2520    # Handle any feature flags.
2521    for xf in target_config.pyqt_disabled_features:
2522        sip_flags.append('-x')
2523        sip_flags.append(xf)
2524
2525    # Handle the version specific Python features.
2526    if target_config.py_version < 0x030000:
2527        sip_flags.append('-x')
2528        sip_flags.append('Py_v3')
2529
2530    return sip_flags
2531
2532
2533def mk_clean_dir(name):
2534    """ Create a clean (ie. empty) directory.  name is the name of the
2535    directory.
2536    """
2537
2538    try:
2539        shutil.rmtree(name)
2540    except:
2541        pass
2542
2543    try:
2544        os.makedirs(name)
2545    except:
2546        error("Unable to create the %s directory." % name)
2547
2548
2549def mk_dir(name):
2550    """ Ensure a directory exists, creating it if necessary.  name is the name
2551    of the directory.
2552    """
2553
2554    try:
2555        os.makedirs(name)
2556    except:
2557        pass
2558
2559
2560def generate_sip_module_code(target_config, verbose, parts, tracing, mname, fatal_warnings, sip_flags, doc_support, qpy_sources=None, qpy_headers=None):
2561    """ Generate the code for a module.  target_config is the target
2562    configuration.  verbose is set if the output is to be displayed.  parts is
2563    the number of parts the generated code should be split into.  tracing is
2564    set if the generated code should include tracing calls.  mname is the name
2565    of the module to generate the code for.  fatal_warnings is set if warnings
2566    are fatal.  sip_flags is the list of flags to pass to sip.  doc_support
2567    is set if documentation support is to be generated for the module.
2568    qpy_sources is the optional list of QPy support code source files.
2569    qpy_headers is the optional list of QPy support code header files.
2570    """
2571
2572    inform("Generating the C++ source for the %s module..." % mname)
2573
2574    mk_clean_dir(mname)
2575
2576    # Build the SIP command line.
2577    argv = [target_config.sip, '-w']
2578
2579    if target_config.abi_version:
2580        argv.append('--abi-version')
2581        argv.append(target_config.abi_version)
2582
2583    argv.extend(sip_flags)
2584
2585    if fatal_warnings:
2586        argv.append('-f')
2587
2588    if target_config.prot_is_public:
2589        argv.append('-P');
2590
2591    if parts != 0:
2592        argv.append('-j')
2593        argv.append(str(parts))
2594
2595    if tracing:
2596        argv.append('-r')
2597
2598    if doc_support:
2599        if not target_config.no_docstrings:
2600            argv.append('-o');
2601
2602        if target_config.qsci_api:
2603            argv.append('-a')
2604            argv.append(mname + '.api')
2605
2606        if target_config.py_version >= 0x030500 and target_config.pyqt_stubs_dir:
2607            argv.append('-y')
2608            argv.append(mname + '.pyi')
2609
2610    # Pass the absolute pathname so that #line files are absolute.
2611    argv.append('-c')
2612    argv.append(os.path.abspath(mname))
2613
2614    argv.append('-I')
2615    argv.append('sip')
2616
2617    sp_sip_dir = source_path('sip')
2618    if sp_sip_dir != 'sip':
2619        # SIP assumes POSIX style separators.
2620        sp_sip_dir = sp_sip_dir.replace('\\', '/')
2621        argv.append('-I')
2622        argv.append(sp_sip_dir)
2623
2624    # The .sip files for the Qt modules will be in the out-of-tree directory.
2625    if mname == 'Qt':
2626        sip_dir = 'sip'
2627    else:
2628        sip_dir = sp_sip_dir
2629
2630    # Add the name of the .sip file.
2631    argv.append('%s/%s/%smod.sip' % (sip_dir, mname, mname))
2632
2633    run_command(' '.join([quote(a) for a in argv]), verbose)
2634
2635    # Check the result.
2636    if mname == 'Qt':
2637        file_check = 'sip%scmodule.c' % mname
2638    else:
2639        file_check = 'sipAPI%s.h' % mname
2640
2641    if not os.access(os.path.join(mname, file_check), os.F_OK):
2642        error("Unable to create the C++ code.")
2643
2644    # Embed the sip flags.
2645    if mname == 'QtCore':
2646        inform("Embedding sip flags...")
2647
2648        in_f = open(source_path('qpy', 'QtCore', 'qpycore_post_init.cpp.in'))
2649        out_f = open_for_writing(
2650                os.path.join('QtCore', 'qpycore_post_init.cpp'))
2651
2652        for line in in_f:
2653            line = line.replace('@@PYQT_SIP_FLAGS@@', ' '.join(sip_flags))
2654            out_f.write(line)
2655
2656        in_f.close()
2657        out_f.close()
2658
2659    # Generate the makefile.
2660    include_paths = []
2661    libs = ''
2662
2663    if target_config.vend_enabled:
2664        if mname == 'QtCore':
2665            include_paths.append(target_config.vend_inc_dir)
2666            libs = '-L%s -lvendorid' % target_config.vend_lib_dir
2667
2668    generate_module_makefile(target_config, verbose, mname,
2669            include_paths=include_paths, libs=libs, qpy_sources=qpy_sources,
2670            qpy_headers=qpy_headers)
2671
2672
2673def generate_module_makefile(target_config, verbose, mname, include_paths=None, libs='', install_path='', src_dir='', qpy_sources=None, qpy_headers=None):
2674    """ Generate the makefile for a module.  target_config is the target
2675    configuration.  verbose is set if the output is to be displayed.  mname is
2676    the name of the module.  include_paths is an optional list of values of
2677    INCLUDEPATH.  libs is an optional additional value of LIBS.  install_path
2678    is the optional name of the directory that the module will be installed in.
2679    src_dir is the optional source directory (by default the sources are
2680    assumed to be in the module directory).  qpy_sources is the optional list
2681    of QPy support code source files.  qpy_headers is the optional list of QPy
2682    support code header files.
2683    """
2684
2685    if verbose:
2686        sys.stdout.write('\n')
2687
2688    inform("Generating the .pro file for the %s module..." % mname)
2689
2690    if src_dir == '':
2691        src_dir = mname
2692
2693    target_name = mname
2694
2695    metadata = MODULE_METADATA[mname]
2696
2697    if metadata.qmake_TARGET != '':
2698        target_name = metadata.qmake_TARGET
2699
2700    pro_lines = ['TEMPLATE = lib']
2701
2702    # Note some version of Qt5 (probably incorrectly) implements
2703    # 'plugin_bundle' instead of 'plugin' so we specify both.
2704    pro_lines.append('CONFIG += warn_on exceptions_off %s' % ('staticlib hide_symbols' if target_config.static else 'plugin plugin_bundle'))
2705
2706    pro_add_qt_dependencies(target_config, metadata, pro_lines)
2707
2708    if target_config.qml_debug:
2709        pro_lines.append('CONFIG += qml_debug')
2710
2711    # Work around QTBUG-39300.
2712    pro_lines.append('CONFIG -= android_install')
2713
2714    pro_lines.append('TARGET = %s' % target_name)
2715
2716    if not target_config.static:
2717        debug_suffix = target_config.get_win32_debug_suffix()
2718
2719        # For Qt v5.5 make sure these frameworks are already loaded by the time
2720        # the libqcocoa.dylib plugin gets loaded.  This problem seems to be
2721        # fixed in Qt v5.6.
2722        extra_lflags = ''
2723
2724        if mname == 'QtGui':
2725            # Note that this workaround is flawed because it looks at the PyQt
2726            # configuration rather than the Qt configuration.  It will fail if
2727            # the user is building a PyQt without the QtDBus module against a
2728            # Qt with the QtDBus library.  However it will be fine for the
2729            # common case where the PyQt configuration reflects the Qt
2730            # configuration.
2731            fwks = []
2732            for m in ('QtPrintSupport', 'QtDBus', 'QtWidgets'):
2733                if m in target_config.pyqt_modules:
2734                    fwks.append('-framework ' + m)
2735
2736            if len(fwks) != 0:
2737                extra_lflags = 'QMAKE_LFLAGS += "%s"\n        ' % ' '.join(fwks)
2738
2739        # Without the 'no_check_exist' magic the target.files must exist when
2740        # qmake is run otherwise the install and uninstall targets are not
2741        # generated.
2742        shared = '''
2743win32 {
2744    PY_MODULE = %s%s.pyd
2745    PY_MODULE_SRC = $(DESTDIR_TARGET)
2746} else {
2747    PY_MODULE = %s.so
2748
2749    macx {
2750        PY_MODULE_SRC = $(TARGET).plugin/Contents/MacOS/$(TARGET)
2751
2752        QMAKE_LFLAGS += "-undefined dynamic_lookup"
2753
2754        equals(QT_MINOR_VERSION, 5) {
2755            %sQMAKE_RPATHDIR += $$[QT_INSTALL_LIBS]
2756        }
2757    } else {
2758        PY_MODULE_SRC = $(TARGET)
2759    }
2760}
2761
2762QMAKE_POST_LINK = $(COPY_FILE) $$PY_MODULE_SRC $$PY_MODULE
2763
2764target.CONFIG = no_check_exist
2765target.files = $$PY_MODULE
2766''' % (target_name, debug_suffix, target_name, extra_lflags)
2767
2768        pro_lines.extend(shared.split('\n'))
2769
2770    if install_path == '':
2771        install_path = target_config.pyqt_module_dir + '/PyQt5'
2772
2773    install_path = install_path.replace('\\', '/')
2774
2775    pro_lines.append('target.path = %s' % install_path)
2776    pro_lines.append('INSTALLS += target')
2777
2778    # This optimisation could apply to other platforms.
2779    if 'linux' in target_config.qmake_spec and not target_config.static:
2780        if target_config.py_version >= 0x030000:
2781            entry_point = 'PyInit_%s' % target_name
2782        else:
2783            entry_point = 'init%s' % target_name
2784
2785        exp = open_for_writing(os.path.join(mname, target_name + '.exp'))
2786        exp.write('{ global: %s; local: *; };' % entry_point)
2787        exp.close()
2788
2789        pro_lines.append('QMAKE_LFLAGS += -Wl,--version-script=%s.exp' % target_name)
2790
2791    if target_config.prot_is_public:
2792        pro_lines.append('DEFINES += SIP_PROTECTED_IS_PUBLIC protected=public')
2793
2794    # This is needed for Windows.
2795    pro_lines.append('INCLUDEPATH += .')
2796
2797    target_config.add_sip_h_directives(pro_lines)
2798
2799    if metadata.qpy_lib:
2800        # This is the easiest way to make sure it is set for handwritten code.
2801        if not target_config.py_debug:
2802            pro_lines.append('DEFINES += Py_LIMITED_API=0x03040000')
2803
2804        pro_lines.append('INCLUDEPATH += %s' %
2805                qmake_quote(os.path.relpath(source_path('qpy', mname), mname)))
2806
2807    if include_paths:
2808        pro_lines.append(
2809                'INCLUDEPATH += ' + ' '.join(
2810                        [qmake_quote(p) for p in include_paths]))
2811
2812    if libs != '':
2813        pro_lines.append('LIBS += %s' % libs)
2814
2815    if src_dir != mname:
2816        pro_lines.append('INCLUDEPATH += %s' % qmake_quote(src_dir))
2817        pro_lines.append('VPATH = %s' % qmake_quote(src_dir))
2818
2819    pro_lines.extend(pro_sources(src_dir, qpy_headers, qpy_sources))
2820
2821    pro_name = os.path.join(mname, mname + '.pro')
2822
2823    pro = open_for_writing(pro_name)
2824    pro.write('\n'.join(pro_lines))
2825    pro.write('\n')
2826    pro.close()
2827
2828
2829def fix_license(src_lfile, dst_lfile):
2830    """ Fix the license file, if there is one, so that it conforms to the SIP
2831    v5 syntax.  src_lfile is the name of the license file.  dst_lfile is the
2832    name of the fixed license file.
2833    """
2834
2835    f = open(src_lfile)
2836    f5 = open_for_writing(dst_lfile)
2837
2838    for line in f:
2839        if line.startswith('%License'):
2840            anno_start = line.find('/')
2841            anno_end = line.rfind('/')
2842
2843            if anno_start < 0 or anno_end < 0 or anno_start == anno_end:
2844                error("%s has missing annotations." % name)
2845
2846            annos = line[anno_start + 1:anno_end].split(', ')
2847            annos5 = [anno[0].lower() + anno[1:] for anno in annos]
2848
2849            f5.write('%License(')
2850            f5.write(', '.join(annos5))
2851            f5.write(')\n')
2852        else:
2853            f5.write(line)
2854
2855    f5.close()
2856    f.close()
2857
2858
2859def check_license(target_config, license_confirmed):
2860    """ Handle the validation of the PyQt5 license.  target_config is the
2861    target configuration.  license_confirmed is set if the user has already
2862    accepted the license.
2863    """
2864
2865    try:
2866        import license
2867        ltype = license.LicenseType
2868        lname = license.LicenseName
2869
2870        try:
2871            lfile = license.LicenseFile
2872        except AttributeError:
2873            lfile = None
2874    except ImportError:
2875        ltype = None
2876
2877    if ltype is None:
2878        ltype = 'GPL'
2879        lname = "GNU General Public License"
2880        lfile = 'pyqt-gpl.sip'
2881
2882    inform(
2883            "This is the %s version of PyQt %s (licensed under the %s) for "
2884            "Python %s on %s." %
2885                    (ltype, PYQT_VERSION_STR, lname, sys.version.split()[0],
2886                            sys.platform))
2887
2888    # Confirm the license if not already done.
2889    if not license_confirmed:
2890        loptions = """
2891Type 'L' to view the license.
2892"""
2893
2894        sys.stdout.write(loptions)
2895        sys.stdout.write("""Type 'yes' to accept the terms of the license.
2896Type 'no' to decline the terms of the license.
2897
2898""")
2899
2900        while 1:
2901            sys.stdout.write("Do you accept the terms of the license? ")
2902            sys.stdout.flush()
2903
2904            try:
2905                resp = sys.stdin.readline()
2906            except KeyboardInterrupt:
2907                raise SystemExit
2908            except:
2909                resp = ""
2910
2911            resp = resp.strip().lower()
2912
2913            if resp == "yes":
2914                break
2915
2916            if resp == "no":
2917                sys.exit(0)
2918
2919            if resp == 'l':
2920                os.system('more LICENSE')
2921
2922    # Check that the license file exists and fix its syntax.
2923    sip_dir = 'sip'
2924    mk_dir(sip_dir)
2925
2926    src_lfile = os.path.join(target_config.license_dir, lfile)
2927
2928    if os.access(src_lfile, os.F_OK):
2929        inform("Found the license file %s." % lfile)
2930        fix_license(src_lfile, os.path.join(sip_dir, lfile + '5'))
2931    else:
2932        error(
2933                "Please copy the license file %s to %s." % (lfile,
2934                        target_config.license_dir))
2935
2936
2937def check_qt(target_config):
2938    """ Check the Qt installation.  target_config is the target configuration.
2939    """
2940
2941    # Starting with v4.7, Qt (when built with MinGW) assumes that stack frames
2942    # are 16 byte aligned because it uses SSE.  However the Python Windows
2943    # installers are built with 4 byte aligned stack frames.  We therefore need
2944    # to tweak the g++ flags to deal with it.
2945    if target_config.qmake_spec == 'win32-g++':
2946        target_config.qmake_variables.append('QMAKE_CFLAGS += -mstackrealign')
2947        target_config.qmake_variables.append('QMAKE_CXXFLAGS += -mstackrealign')
2948
2949
2950def check_python(target_config):
2951    """ Check the Python installation.  target_config is the target
2952    configuration.
2953    """
2954
2955    # Check the Python version number.  This allows us to assume relative
2956    # imports and ElemenTree are available.
2957    if target_config.py_version < 0x020600:
2958        error("PyQt5 requires Python v2.6 or later.")
2959
2960
2961def check_sip(target_config, verbose):
2962    """ Check that the version of sip is good enough and return its version.
2963    target_config is the target configuration.
2964    """
2965
2966    if target_config.sip is None:
2967        error(
2968                "Make sure you have a working sip on your PATH or use the "
2969                "--sip argument to explicitly specify a working sip.")
2970
2971    pipe = os.popen(' '.join([quote(target_config.sip), '-V']))
2972
2973    for l in pipe:
2974        version_str = l.strip()
2975        break
2976    else:
2977        error("'%s -V' did not generate any output." % target_config.sip)
2978
2979    pipe.close()
2980
2981    if '.dev' in version_str or 'snapshot' in version_str:
2982        # We only need to distinguish between sip v4 and sip v5.
2983        if target_config.using_sip5():
2984            version = 0x050000
2985        else:
2986            version = 0x040000
2987    else:
2988        version = version_from_string(version_str)
2989        if version is None:
2990            error(
2991                    "'%s -V' generated unexpected output: '%s'." % (
2992                            target_config.sip, version_str))
2993
2994        min_version = version_from_string(SIP_MIN_VERSION)
2995        if version < min_version:
2996            error(
2997                    "This version of PyQt5 requires sip %s or later." %
2998                            SIP_MIN_VERSION)
2999
3000    if version >= 0x050000:
3001        # Install the sip.h file for the private sip module.
3002        if target_config.sip_inc_dir is None:
3003            target_config.sip_inc_dir = os.path.join(
3004                    os.path.abspath(os.getcwd()), 'include')
3005
3006            inform("Installing sip.h in %s..." % target_config.sip_inc_dir)
3007
3008            os.makedirs(target_config.sip_inc_dir, exist_ok=True)
3009
3010            argv = ['sip-module', '--sip-h']
3011
3012            if target_config.abi_version:
3013                argv.append('--abi-version')
3014                argv.append(target_config.abi_version)
3015
3016            argv.append('--target-dir')
3017            argv.append(quote(target_config.sip_inc_dir)),
3018            argv.append('PyQt5.sip')
3019
3020            run_command(' '.join(argv), verbose)
3021
3022            if not os.access(os.path.join(target_config.sip_inc_dir, 'sip.h'), os.F_OK):
3023                error(
3024                        "sip-module failed to install sip.h in %s." %
3025                                target_config.sip_inc_dir)
3026    else:
3027        if target_config.sip_inc_dir is None:
3028            target_config.sip_inc_dir = target_config.py_venv_inc_dir
3029
3030    return version_str
3031
3032
3033def version_from_string(version_str):
3034    """ Convert a version string of the form m.n or m.n.o to an encoded version
3035    number (or None if it was an invalid format).  version_str is the version
3036    string.
3037    """
3038
3039    parts = version_str.split('.')
3040    if not isinstance(parts, list):
3041        return None
3042
3043    if len(parts) == 2:
3044        parts.append('0')
3045
3046    if len(parts) != 3:
3047        return None
3048
3049    version = 0
3050
3051    for part in parts:
3052        try:
3053            v = int(part)
3054        except ValueError:
3055            return None
3056
3057        version = (version << 8) + v
3058
3059    return version
3060
3061
3062def open_for_writing(fname):
3063    """ Return a file opened for writing while handling the most common problem
3064    of not having write permission on the directory.  fname is the name of the
3065    file to open.
3066    """
3067    try:
3068        return open(fname, 'w')
3069    except IOError:
3070        error(
3071                "There was an error creating %s.  Make sure you have write "
3072                "permission on the parent directory." % fname)
3073
3074
3075def matching_files(pattern):
3076    """ Return a reproducable list of files that match a pattern. """
3077
3078    return sorted(glob.glob(pattern))
3079
3080
3081def main(argv):
3082    """ Create the configuration module module.  argv is the list of command
3083    line arguments.
3084    """
3085
3086    # Create the default target configuration.
3087    target_config = TargetConfiguration()
3088
3089    # Parse the command line.
3090    parser = create_optparser(target_config)
3091    opts, target_config.qmake_variables = parser.parse_args()
3092
3093    target_config.apply_pre_options(opts)
3094
3095    # Query qmake for the basic configuration information.
3096    target_config.get_qt_configuration()
3097
3098    # Update the target configuration.
3099    if opts.config_file is not None:
3100        target_config.from_configuration_file(opts.config_file)
3101    else:
3102        target_config.from_introspection(opts.verbose, opts.debug)
3103
3104    target_config.post_configuration()
3105
3106    target_config.apply_post_options(opts)
3107
3108    # Check the licenses are compatible.
3109    check_license(target_config, opts.license_confirmed)
3110
3111    # Check Python is what we need.
3112    check_python(target_config)
3113
3114    # Check SIP is what we need.
3115    sip_version = check_sip(target_config, opts.verbose)
3116
3117    # Check Qt is what we need.
3118    check_qt(target_config)
3119
3120    # Check for the VendorID package.
3121    check_vendorid(target_config)
3122
3123    # Check which modules to build if we haven't been told.
3124    if len(target_config.pyqt_modules) == 0:
3125        check_modules(target_config, opts.disabled_modules, opts.verbose)
3126    else:
3127        # Check that the user supplied module names are valid.
3128        for mname in target_config.pyqt_modules:
3129            if mname not in MODULE_METADATA:
3130                error("'%s' is not a valid module name." % mname)
3131
3132    check_dbus(target_config, opts.verbose)
3133
3134    # Tell the user what's been found.
3135    inform_user(target_config, sip_version)
3136
3137    # Generate the makefiles.
3138    generate_makefiles(target_config, opts.verbose,
3139            opts.split if opts.concat else 0, opts.tracing,
3140            opts.fatal_warnings, opts.distinfo)
3141
3142
3143###############################################################################
3144# The script starts here.
3145###############################################################################
3146
3147if __name__ == '__main__':
3148    try:
3149        main(sys.argv)
3150    except SystemExit:
3151        raise
3152    except:
3153        sys.stderr.write(
3154"""An internal error occured.  Please report all the output from the program,
3155including the following traceback, to support@riverbankcomputing.com.
3156""")
3157        raise
3158