1# Copyright (c) 2017, Riverbank Computing Limited
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7# 1. Redistributions of source code must retain the above copyright notice,
8#    this list of conditions and the following disclaimer.
9#
10# 2. Redistributions in binary form must reproduce the above copyright notice,
11#    this list of conditions and the following disclaimer in the documentation
12#    and/or other materials provided with the distribution.
13#
14# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24# POSSIBILITY OF SUCH DAMAGE.
25
26# This is v2.3 of this boilerplate.
27
28
29from distutils import sysconfig
30import glob
31import os
32import optparse
33import sys
34
35
36###############################################################################
37# You shouldn't need to modify anything above this line.
38###############################################################################
39
40
41class QtAVConfiguration(object):
42    """ This class encapsulates all the module specific information needed by
43    the rest of this script to implement a configure.py script for modules that
44    build on top of PyQt.  Functions implemented by the rest of this script
45    that begin with an underscore are considered internal and shouldn't be
46    called from here.
47    """
48
49    # The name of the module as it would be used in an import statement.
50    name = 'QtAV'
51
52    # Set if support for C++ exceptions can be disabled.
53    no_exceptions = True
54
55    # The name (without the .pyi extension) of the name of the PEP 484 stub
56    # file to be generated.  If it is None or an empty string then a stub file
57    # is not generated.
58    pep484_stub_file = 'QtAV'
59
60    @staticmethod
61    def get_sip_file(target_configuration):
62        """ Return the name of the module's .sip file.  target_configuration is
63        the target configuration.
64        """
65
66        return 'sip/QtAV/QtAVmod.sip'
67
68    @staticmethod
69    def get_sip_installs(target_configuration):
70        """ Return a tuple of the installation directory of the module's .sip
71        files and a sequence of the names of each of the .sip files relative to
72        the directory containing this configuration script.  None is returned
73        if the module's .sip files are not to be installed.
74        target_configuration is the target configuration.
75        """
76
77        if target_configuration.qtav_sip_dir == '':
78            return None
79
80        path = os.path.join(target_configuration.qtav_sip_dir, 'QtAV')
81        files = glob.glob('sip/QtAV/*.sip')
82
83        return path, files
84
85    @staticmethod
86    def get_qmake_configuration(target_configuration):
87        """ Return a dict of qmake configuration values for CONFIG, DEFINES,
88        INCLUDEPATH, LIBS and QT.  If value names (i.e. dict keys) have either
89        'Qt4' or 'Qt5' prefixes then they are specific to the corresponding
90        version of Qt.  target_configuration is the target configuration.
91        """
92        libs = r"-L%s/lib " % (target_configuration.qtav_base_dir)
93        if sys.platform == "win32":
94            libs += "-lQtAV1 -lQtAVWidgets1"
95        else:
96            libs += "-lQtAV -lQtAVWidgets"
97
98        return {'QT': 'widgets opengl',
99                'INCLUDEPATH': os.path.join(target_configuration.qtav_base_dir, "include"),
100                'LIBS': libs}
101
102    @staticmethod
103    def get_mac_wrapped_library_file(target_configuration):
104        """ Return the full pathname of the file that implements the library
105        being wrapped by the module as it would be called on OS/X so that the
106        module will reference it explicitly without DYLD_LIBRARY_PATH being
107        set.  If it is None or an empty string then the default is used.
108        target_configuration is the target configuration.
109        """
110
111        return None
112
113
114class QtAVWidgetsConfiguration(object):
115    """ This class encapsulates all the module specific information needed by
116    the rest of this script to implement a configure.py script for modules that
117    build on top of PyQt.  Functions implemented by the rest of this script
118    that begin with an underscore are considered internal and shouldn't be
119    called from here.
120    """
121
122    # The name of the module as it would be used in an import statement.
123    name = 'QtAVWidgets'
124
125    # Set if support for C++ exceptions can be disabled.
126    no_exceptions = True
127
128    # The name (without the .pyi extension) of the name of the PEP 484 stub
129    # file to be generated.  If it is None or an empty string then a stub file
130    # is not generated.
131    pep484_stub_file = 'QtAVWidgets'
132
133    @staticmethod
134    def get_sip_file(target_configuration):
135        """ Return the name of the module's .sip file.  target_configuration is
136        the target configuration.
137        """
138
139        return 'sip/QtAVWidgets/QtAVWidgetsmod.sip'
140
141    @staticmethod
142    def get_sip_installs(target_configuration):
143        """ Return a tuple of the installation directory of the module's .sip
144        files and a sequence of the names of each of the .sip files relative to
145        the directory containing this configuration script.  None is returned
146        if the module's .sip files are not to be installed.
147        target_configuration is the target configuration.
148        """
149
150        if target_configuration.qtav_sip_dir == '':
151            return None
152
153        path = os.path.join(target_configuration.qtav_sip_dir, 'QtAVWidgets')
154        files = glob.glob('sip/QtAVWidgets/*.sip')
155
156        return path, files
157
158    @staticmethod
159    def get_qmake_configuration(target_configuration):
160        """ Return a dict of qmake configuration values for CONFIG, DEFINES,
161        INCLUDEPATH, LIBS and QT.  If value names (i.e. dict keys) have either
162        'Qt4' or 'Qt5' prefixes then they are specific to the corresponding
163        version of Qt.  target_configuration is the target configuration.
164        """
165        libs = r"-L%s/lib " % (target_configuration.qtav_base_dir)
166        if sys.platform == "win32":
167            libs += "-lQtAV1 -lQtAVWidgets1"
168        else:
169            libs += "-lQtAV -lQtAVWidgets"
170
171        return {'QT': 'widgets opengl',
172                'INCLUDEPATH': os.path.join(target_configuration.qtav_base_dir, "include"),
173                'LIBS': libs}
174
175    @staticmethod
176    def get_mac_wrapped_library_file(target_configuration):
177        """ Return the full pathname of the file that implements the library
178        being wrapped by the module as it would be called on OS/X so that the
179        module will reference it explicitly without DYLD_LIBRARY_PATH being
180        set.  If it is None or an empty string then the default is used.
181        target_configuration is the target configuration.
182        """
183
184        return None
185
186
187class PackageConfiguration(object):
188    """ This class encapsulates all the package specific information needed by
189    the rest of this script to implement a configure.py script for modules that
190    build on top of PyQt.  Functions implemented by the rest of this script
191    that begin with an underscore are considered internal and shouldn't be
192    called from here.
193    """
194
195    # The descriptive name of the module.  This is used in help text and error
196    # messages.
197    descriptive_name = "PyQtAV"
198
199    # The version of the module as a string.  Set it to None if you don't
200    # provide version information.
201    version = '1.12.0'
202
203    # The sequence of module configurations that make up the package.
204    modules = (QtAVConfiguration(), QtAVWidgetsConfiguration())
205
206    # Set if a configuration script is provided that handles versions of PyQt4
207    # prior to v4.10 (i.e. versions where the pyqtconfig.py module is
208    # available).  If provided the script must be called configure-old.py and
209    # be in the same directory as this script.
210    legacy_configuration_script = False
211
212    # The minimum version of SIP that is required.  This should be a
213    # dot-separated string of two or three integers (e.g. '1.0', '4.10.3').  If
214    # it is None or an empty string then the version is not checked.
215    minimum_sip_version = '4.19.1'
216
217    # Set if the module supports redefining 'protected' as 'public'.
218    protected_is_public_is_supported = True
219
220    # Set if the module supports PyQt4.
221    pyqt4_is_supported = False
222
223    # Set if the module supports PyQt5.
224    pyqt5_is_supported = True
225
226    # Set if the PyQt5 support is the default.  It is ignored unless both
227    # 'pyqt4_is_supported' and 'pyqt5_is_supported' are set.
228    pyqt5_is_default = True
229
230    # The name (without the .api extension) of the name of the QScintilla API
231    # file to be generated.  If it is None or an empty string then an API file
232    # is not generated.
233    qscintilla_api_file = 'PyQtAV'
234
235    # The email address that will be included when an error in the script is
236    # detected.  Leave it blank if you don't want to include an address.
237    support_email_address = 'support@riverbankcomputing.com'
238
239    # Set if the user can provide a configuration file.  It is normally only
240    # used if cross-compilation is supported.
241    user_configuration_file_is_supported = True
242
243    # Set if the user is allowed to pass PyQt sip flags on the command line.
244    # It is normally only used if cross-compilation is supported.  It is
245    # ignored unless at least one of 'pyqt4_is_supported' or
246    # 'pyqt5_is_supported' is set.
247    user_pyqt_sip_flags_is_supported = True
248
249    @staticmethod
250    def init_target_configuration(target_configuration):
251        """ Perform any module specific initialisation of the target
252        target configuration.  Typically this is the initialisation of module
253        specific attributes.  To avoid name clashes attributes should be given
254        a module specific prefix.  target_configuration is the target
255        configuration.
256        """
257
258        target_configuration.qtav_version = None
259        target_configuration.qtav_base_dir = None
260        target_configuration.qtav_sip_dir = None
261
262    @staticmethod
263    def init_optparser(optparser, target_configuration):
264        """ Perform any module specific initialisation of the command line
265        option parser.  To avoid name clashes destination attributes should be
266        given a module specific prefix.  optparser is the option parser.
267        target_configuration is the target configuration.
268        """
269
270        optparser.add_option('--qtav-version', dest='qtav_version',
271                type='string', metavar="VERSION",
272                help="the QtAV version number (eg. 1.12.0)")
273
274        optparser.add_option('--qtav-base-dir', '-v',
275                dest='qtav_base_dir', type='string', default=None,
276                action='callback', callback=optparser_store_abspath_dir,
277                metavar="DIR",
278                help="the base directory that contains QtAV installation ")
279
280        optparser.add_option("--no-sip-files", action="store_true",
281                default=False, dest="qtav_no_sip_files",
282                help="disable the installation of the .sip files "
283                        "[default: enabled]")
284
285    @staticmethod
286    def apply_options(target_configuration, options):
287        """ Apply the module specific command line options to the target
288        configuration.  target_configuration is the target configuration.
289        options are the parsed options.
290        """
291
292        # Historically Digia have been incapable of remembering to update the
293        # version number in a new release of add-on libraries so we allow the
294        # user to specify it while defaulting to the Qt version.
295        qtav_version_str = options.qtav_version
296        if qtav_version_str is None:
297            qtav_version_str = target_configuration.qt_version_str
298
299        target_configuration.qtav_version = version_from_string(
300                qtav_version_str)
301
302        if target_configuration.qtav_version is None:
303            error("%s is not a valid QtAV version." % qtav_version_str)
304
305        if target_configuration.qtav_version >= 0x020000 and target_configuration.pyqt_package != 'PyQt5':
306            error("QtAV v%s requires PyQt5." % options.qtav_version)
307
308        if options.qtav_base_dir is not None:
309            target_configuration.qtav_base_dir = options.qtav_base_dir
310        else:
311            error("--qtav-base-dir must be spcified")
312
313        if options.qtav_no_sip_files:
314            target_configuration.qtav_sip_dir = ''
315        else:
316            target_configuration.qtav_sip_dir = os.path.join(target_configuration.qtav_base_dir, 'share', 'sip')
317
318    @staticmethod
319    def check_package(target_configuration):
320        """ Perform any package specific checks now that the target
321        configuration is complete.  target_configuration is the target
322        configuration.
323        """
324
325        # Nothing to do.
326
327    @staticmethod
328    def inform_user(target_configuration):
329        """ Inform the user about module specific configuration information.
330        target_configuration is the target configuration.
331        """
332
333        major = (target_configuration.qtav_version >> 16) & 0xff
334        minor = (target_configuration.qtav_version >> 8) & 0xff
335        patch = target_configuration.qtav_version & 0xff
336        version = '%d.%d.%d' % (major, minor, patch)
337
338        inform("QtAV %s is being used." % version)
339
340        if target_configuration.qtav_sip_dir != '':
341            inform(
342                    "The QtAV .sip files will be installed in %s." %
343                            target_configuration.qtav_sip_dir)
344
345    @staticmethod
346    def pre_code_generation(target_config):
347        """ Perform any module specific initialisation prior to generating the
348        code.  target_config is the target configuration.
349        """
350
351        # Nothing to do.
352
353    @staticmethod
354    def get_sip_flags(target_configuration):
355        """ Return the list of module-specific flags to pass to SIP.
356        target_configuration is the target configuration.
357        """
358
359        major = (target_configuration.qtav_version >> 16) & 0xff
360        minor = (target_configuration.qtav_version >> 8) & 0xff
361        patch = target_configuration.qtav_version & 0xff
362        version_tag = 'QtAV_%d_%d_%d' % (major, minor, patch)
363
364        return ['-t', version_tag]
365
366
367###############################################################################
368# You shouldn't need to modify anything below this line.
369###############################################################################
370
371
372def error(msg):
373    """ Display an error message and terminate.  msg is the text of the error
374    message.
375    """
376
377    sys.stderr.write(_format("Error: " + msg) + "\n")
378    sys.exit(1)
379
380
381def inform(msg):
382    """ Display an information message.  msg is the text of the error message.
383    """
384
385    sys.stdout.write(_format(msg) + "\n")
386
387
388def quote(path):
389    """ Return a path with quotes added if it contains spaces.  path is the
390    path.
391    """
392
393    if ' ' in path:
394        path = '"%s"' % path
395
396    return path
397
398
399def optparser_store_abspath(option, opt_str, value, parser):
400    """ An optparser callback that saves an option as an absolute pathname. """
401
402    setattr(parser.values, option.dest, os.path.abspath(value))
403
404
405def optparser_store_abspath_dir(option, opt_str, value, parser):
406    """ An optparser callback that saves an option as the absolute pathname
407    of an existing directory.
408    """
409
410    if not os.path.isdir(value):
411        raise optparse.OptionValueError("'%s' is not a directory" % value)
412
413    setattr(parser.values, option.dest, os.path.abspath(value))
414
415
416def optparser_store_abspath_exe(option, opt_str, value, parser):
417    """ An optparser callback that saves an option as the absolute pathname
418    of an existing executable.
419    """
420
421    if not os.access(value, os.X_OK):
422        raise optparse.OptionValueError("'%s' is not an executable" % value)
423
424    setattr(parser.values, option.dest, os.path.abspath(value))
425
426
427def read_define(filename, define):
428    """ Read the value of a #define from a file.  filename is the name of the
429    file.  define is the name of the #define.  None is returned if there was no
430    such #define.
431    """
432
433    f = open(filename)
434
435    for l in f:
436        wl = l.split()
437        if len(wl) >= 3 and wl[0] == "#define" and wl[1] == define:
438            # Take account of embedded spaces.
439            value = ' '.join(wl[2:])[1:-1]
440            break
441    else:
442        value = None
443
444    f.close()
445
446    return value
447
448
449def version_from_string(version_str):
450    """ Convert a version string of the form m, m.n or m.n.o to an encoded
451    version number (or None if it was an invalid format).  version_str is the
452    version string.
453    """
454
455    parts = version_str.split('.')
456    if not isinstance(parts, list):
457        return None
458
459    if len(parts) == 1:
460        parts.append('0')
461
462    if len(parts) == 2:
463        parts.append('0')
464
465    if len(parts) != 3:
466        return None
467
468    version = 0
469
470    for part in parts:
471        try:
472            v = int(part)
473        except ValueError:
474            return None
475
476        version = (version << 8) + v
477
478    return version
479
480
481def _format(msg, left_margin=0, right_margin=78):
482    """ Format a message by inserting line breaks at appropriate places.  msg
483    is the text of the message.  left_margin is the position of the left
484    margin.  right_margin is the position of the right margin.  Returns the
485    formatted message.
486    """
487
488    curs = left_margin
489    fmsg = " " * left_margin
490
491    for w in msg.split():
492        l = len(w)
493        if curs != left_margin and curs + l > right_margin:
494            fmsg = fmsg + "\n" + (" " * left_margin)
495            curs = left_margin
496
497        if curs > left_margin:
498            fmsg = fmsg + " "
499            curs = curs + 1
500
501        fmsg = fmsg + w
502        curs = curs + l
503
504    return fmsg
505
506
507class _ConfigurationFileParser:
508    """ A parser for configuration files. """
509
510    def __init__(self, config_file):
511        """ Read and parse a configuration file. """
512
513        self._config = {}
514        self._extrapolating = []
515
516        cfg = open(config_file)
517        line_nr = 0
518        last_name = None
519
520        section = ''
521        section_config = {}
522        self._config[section] = section_config
523
524        for l in cfg:
525            line_nr += 1
526
527            # Strip comments.
528            l = l.split('#')[0]
529
530            # See if this might be part of a multi-line.
531            multiline = (last_name is not None and len(l) != 0 and l[0] == ' ')
532
533            l = l.strip()
534
535            if l == '':
536                last_name = None
537                continue
538
539            # See if this is a new section.
540            if l[0] == '[' and l[-1] == ']':
541                section = l[1:-1].strip()
542                if section == '':
543                    error(
544                            "%s:%d: Empty section name." % (
545                                    config_file, line_nr))
546
547                if section in self._config:
548                    error(
549                            "%s:%d: Section '%s' defined more than once." % (
550                                    config_file, line_nr, section))
551
552                section_config = {}
553                self._config[section] = section_config
554
555                last_name = None
556                continue
557
558            parts = l.split('=', 1)
559            if len(parts) == 2:
560                name = parts[0].strip()
561                value = parts[1].strip()
562            elif multiline:
563                name = last_name
564                value = section_config[last_name]
565                value += ' ' + l
566            else:
567                name = value = ''
568
569            if name == '' or value == '':
570                error("%s:%d: Invalid line." % (config_file, line_nr))
571
572            section_config[name] = value
573            last_name = name
574
575        cfg.close()
576
577    def sections(self):
578        """ Return the list of sections, excluding the default one. """
579
580        return [s for s in self._config.keys() if s != '']
581
582    def preset(self, name, value):
583        """ Add a preset value to the configuration. """
584
585        self._config[''][name] = value
586
587    def get(self, section, name, default=None):
588        """ Get a configuration value while extrapolating. """
589
590        # Get the name from the section, or the default section.
591        value = self._config[section].get(name)
592        if value is None:
593            value = self._config[''].get(name)
594            if value is None:
595                if default is None:
596                    error(
597                            "Configuration file references non-existent name "
598                            "'%s'." % name)
599
600                return default
601
602        # Handle any extrapolations.
603        parts = value.split('%(', 1)
604        while len(parts) == 2:
605            prefix, tail = parts
606
607            parts = tail.split(')', 1)
608            if len(parts) != 2:
609                error(
610                        "Configuration file contains unterminated "
611                        "extrapolated name '%s'." % tail)
612
613            xtra_name, suffix = parts
614
615            if xtra_name in self._extrapolating:
616                error(
617                        "Configuration file contains a recursive reference to "
618                        "'%s'." % xtra_name)
619
620            self._extrapolating.append(xtra_name)
621            xtra_value = self.get(section, xtra_name)
622            self._extrapolating.pop()
623
624            value = prefix + xtra_value + suffix
625
626            parts = value.split('%(', 1)
627
628        return value
629
630    def getboolean(self, section, name, default):
631        """ Get a boolean configuration value while extrapolating. """
632
633        value = self.get(section, name, default)
634
635        # In case the default was returned.
636        if isinstance(value, bool):
637            return value
638
639        if value in ('True', 'true', '1'):
640            return True
641
642        if value in ('False', 'false', '0'):
643            return False
644
645        error(
646                "Configuration file contains invalid boolean value for "
647                "'%s'." % name)
648
649    def getlist(self, section, name, default):
650        """ Get a list configuration value while extrapolating. """
651
652        value = self.get(section, name, default)
653
654        # In case the default was returned.
655        if isinstance(value, list):
656            return value
657
658        return value.split()
659
660
661class _HostPythonConfiguration:
662    """ A container for the host Python configuration. """
663
664    def __init__(self):
665        """ Initialise the configuration. """
666
667        self.platform = sys.platform
668        self.version = sys.hexversion >> 8
669
670        self.inc_dir = sysconfig.get_python_inc()
671        self.venv_inc_dir = sysconfig.get_python_inc(prefix=sys.prefix)
672        self.module_dir = sysconfig.get_python_lib(plat_specific=1)
673        self.debug = hasattr(sys, 'gettotalrefcount')
674
675        if sys.platform == 'win32':
676            try:
677                # Python v3.3 and later.
678                base_prefix = sys.base_prefix
679
680            except AttributeError:
681                try:
682                    # virtualenv for Python v2.
683                    base_prefix = sys.real_prefix
684
685                except AttributeError:
686                    # We can't detect the base prefix in Python v3 prior to
687                    # v3.3.
688                    base_prefix = sys.prefix
689
690            self.data_dir = sys.prefix
691            self.lib_dir = base_prefix + '\\libs'
692        else:
693            self.data_dir = sys.prefix + '/share'
694            self.lib_dir = sys.prefix + '/lib'
695
696
697class _TargetQtConfiguration:
698    """ A container for the target Qt configuration. """
699
700    def __init__(self, qmake):
701        """ Initialise the configuration.  qmake is the full pathname of the
702        qmake executable that will provide the configuration.
703        """
704
705        pipe = os.popen(quote(qmake) + ' -query')
706
707        for l in pipe:
708            l = l.strip()
709
710            tokens = l.split(':', 1)
711            if isinstance(tokens, list):
712                if len(tokens) != 2:
713                    error("Unexpected output from qmake: '%s'\n" % l)
714
715                name, value = tokens
716            else:
717                name = tokens
718                value = None
719
720            name = name.replace('/', '_')
721
722            setattr(self, name, value)
723
724        pipe.close()
725
726
727class _TargetConfiguration:
728    """ A container for the target configuration. """
729
730    def __init__(self, pkg_config):
731        """ Initialise the configuration with default values.  pkg_config is
732        the package configuration.
733        """
734
735        # Values based on the host Python configuration.
736        py_config = _HostPythonConfiguration()
737        self.py_debug = py_config.debug
738        self.py_platform = py_config.platform
739        self.py_version = py_config.version
740        self.py_module_dir = py_config.module_dir
741        self.py_inc_dir = py_config.inc_dir
742        self.py_venv_inc_dir = py_config.venv_inc_dir
743        self.py_pylib_dir = py_config.lib_dir
744        self.py_sip_dir = os.path.join(py_config.data_dir, 'sip')
745        self.sip_inc_dir = py_config.venv_inc_dir
746
747        # Remaining values.
748        self.debug = False
749        self.pyqt_sip_flags = None
750        self.pyqt_version_str = ''
751        self.qmake = self._find_exe('qmake')
752        self.qmake_spec = ''
753        self.qt_version = 0
754        self.qt_version_str = ''
755        self.sip = self._find_exe('sip5', 'sip')
756        self.sip_version = None
757        self.sip_version_str = None
758        self.sysroot = ''
759        self.stubs_dir = ''
760
761        self.prot_is_public = (self.py_platform.startswith('linux') or self.py_platform == 'darwin')
762
763        if pkg_config.pyqt5_is_supported and pkg_config.pyqt4_is_supported:
764            pyqt = 'PyQt5' if pkg_config.pyqt5_is_default else 'PyQt4'
765        elif pkg_config.pyqt5_is_supported and not pkg_config.pyqt4_is_supported:
766            pyqt = 'PyQt5'
767        elif not pkg_config.pyqt5_is_supported and pkg_config.pyqt4_is_supported:
768            pyqt = 'PyQt4'
769        else:
770            pyqt = None
771
772        if pyqt is not None:
773            self.module_dir = os.path.join(py_config.module_dir, pyqt)
774            self.pyqt_sip_dir = os.path.join(self.py_sip_dir, pyqt)
775        else:
776            self.module_dir = py_config.module_dir
777            self.pyqt_sip_dir = None
778
779        self.pyqt_package = pyqt
780
781        pkg_config.init_target_configuration(self)
782
783    def update_from_configuration_file(self, config_file):
784        """ Update the configuration with values from a file.  config_file
785        is the name of the configuration file.
786        """
787
788        inform("Reading configuration from %s..." % config_file)
789
790        parser = _ConfigurationFileParser(config_file)
791
792        # Populate some presets from the command line.
793        parser.preset('py_major', str(self.py_version >> 16))
794        parser.preset('py_minor', str((self.py_version >> 8) & 0xff))
795        parser.preset('sysroot', self.sysroot)
796
797        if self.pyqt_package is None:
798            section = ''
799        else:
800            # At the moment we only need to distinguish between PyQt4 and
801            # PyQt5.  If that changes we may need a --target-pyqt-version
802            # command line option.
803            pyqt_version = 0x050000 if self.pyqt_package == 'PyQt5' else 0x040000
804
805            # Find the section corresponding to the version of PyQt.
806            section = None
807            latest_section = -1
808
809            for name in parser.sections():
810                parts = name.split()
811                if len(parts) != 2 or parts[0] != 'PyQt':
812                    continue
813
814                section_pyqt_version = version_from_string(parts[1])
815                if section_pyqt_version is None:
816                    continue
817
818                # Major versions must match.
819                if section_pyqt_version >> 16 != pyqt_version >> 16:
820                    continue
821
822                # It must be no later that the version of PyQt.
823                if section_pyqt_version > pyqt_version:
824                    continue
825
826                # Save it if it is the latest so far.
827                if section_pyqt_version > latest_section:
828                    section = name
829                    latest_section = section_pyqt_version
830
831            if section is None:
832                error(
833                        "%s does not define a section that covers PyQt "
834                        "v%s." % (config_file, self.pyqt_version_str))
835
836        self.py_platform = parser.get(section, 'py_platform', self.py_platform)
837        self.py_inc_dir = parser.get(section, 'py_inc_dir', self.py_inc_dir)
838        self.py_venv_inc_dir = self.py_inc_dir
839        self.py_pylib_dir = parser.get(section, 'py_pylib_dir',
840                self.py_pylib_dir)
841
842        self.sip_inc_dir = self.py_venv_inc_dir
843
844        self.module_dir = parser.get(section, 'module_dir', self.module_dir)
845
846        if self.pyqt_package is not None:
847            self.py_sip_dir = parser.get(section, 'py_sip_dir',
848                    self.py_sip_dir)
849
850            # Construct the SIP flags.
851            flags = []
852
853            flags.append('-t')
854            flags.append(self._get_platform_tag())
855
856            if self.pyqt_package == 'PyQt5':
857                if self.qt_version < 0x050000:
858                    error("PyQt5 requires Qt v5.0 or later.")
859
860                if self.qt_version > 0x060000:
861                    self.qt_version = 0x060000
862            else:
863                if self.qt_version > 0x050000:
864                    self.qt_version = 0x050000
865
866            major = (self.qt_version >> 16) & 0xff
867            minor = (self.qt_version >> 8) & 0xff
868            patch = self.qt_version & 0xff
869
870            flags.append('-t')
871            flags.append('Qt_%d_%d_%d' % (major, minor, patch))
872
873            for feat in parser.getlist(section, 'pyqt_disabled_features', []):
874                flags.append('-x')
875                flags.append(feat)
876
877            self.pyqt_sip_flags = ' '.join(flags)
878
879    def _get_platform_tag(self):
880        """ Return the tag for the target platform. """
881
882        # This replicates the logic in PyQt's configure scripts.
883        if self.py_platform == 'win32':
884            plattag = 'WS_WIN'
885        elif self.py_platform == 'darwin':
886            plattag = 'WS_MACX'
887        else:
888            plattag = 'WS_X11'
889
890        return plattag
891
892    def introspect_pyqt(self, pkg_config):
893        """ Introspect PyQt to determine the sip flags required.  pkg_config
894        is the package configuration.
895        """
896
897        if self.pyqt_package == 'PyQt5':
898            try:
899                from PyQt5 import QtCore
900            except ImportError:
901                error(
902                        "Unable to import PyQt5.QtCore. Make sure PyQt5 is "
903                        "installed.")
904        else:
905            try:
906                from PyQt4 import QtCore
907            except ImportError:
908                error(
909                        "Unable to import PyQt4.QtCore. Make sure PyQt4 is "
910                        "installed.")
911
912        self.pyqt_version_str = QtCore.PYQT_VERSION_STR
913        self.qt_version_str = QtCore.qVersion()
914
915        # See if we have a PyQt that embeds its configuration.
916        try:
917            pyqt_config = QtCore.PYQT_CONFIGURATION
918        except AttributeError:
919            pyqt_config = None
920
921        if pyqt_config is None:
922            if pkg_config.legacy_configuration_script:
923                # Fallback to the old configuration script.
924                config_script = sys.argv[0].replace('configure', 'configure-old')
925                args = [sys.executable, config_script] + sys.argv[1:]
926
927                try:
928                    os.execv(sys.executable, args)
929                except OSError:
930                    pass
931
932                error("Unable to execute '%s'" % config_script)
933
934            error("PyQt v4.10 or later is required.")
935
936        self.pyqt_sip_flags = pyqt_config['sip_flags']
937
938    def apply_sysroot(self):
939        """ Apply sysroot where necessary. """
940
941        if self.sysroot != '':
942            self.py_inc_dir = self._apply_sysroot(self.py_inc_dir)
943            self.py_venv_inc_dir = self._apply_sysroot(self.py_venv_inc_dir)
944            self.py_pylib_dir = self._apply_sysroot(self.py_pylib_dir)
945            self.py_sip_dir = self._apply_sysroot(self.py_sip_dir)
946            self.module_dir = self._apply_sysroot(self.module_dir)
947            self.sip_inc_dir = self._apply_sysroot(self.sip_inc_dir)
948
949    def _apply_sysroot(self, dir_name):
950        """ Replace any leading sys.prefix of a directory name with sysroot.
951        """
952
953        if dir_name.startswith(sys.prefix):
954            dir_name = self.sysroot + dir_name[len(sys.prefix):]
955
956        return dir_name
957
958    def get_qt_configuration(self, opts):
959        """ Get the Qt configuration that can be extracted from qmake.  opts
960        are the command line options.
961        """
962
963        # Query qmake.
964        qt_config = _TargetQtConfiguration(self.qmake)
965
966        self.qt_version_str = getattr(qt_config, 'QT_VERSION', '')
967        self.qt_version = version_from_string(self.qt_version_str)
968        if self.qt_version is None:
969            error("Unable to determine the version of Qt.")
970
971        # On Windows for Qt versions prior to v5.9.0 we need to be explicit
972        # about the qmake spec.
973        if self.qt_version < 0x050900 and self.py_platform == 'win32':
974            if self.py_version >= 0x030500:
975                self.qmake_spec = 'win32-msvc2015'
976            elif self.py_version >= 0x030300:
977                self.qmake_spec = 'win32-msvc2010'
978            elif self.py_version >= 0x020600:
979                self.qmake_spec = 'win32-msvc2008'
980            elif self.py_version >= 0x020400:
981                self.qmake_spec = 'win32-msvc.net'
982            else:
983                self.qmake_spec = 'win32-msvc'
984        else:
985            # Otherwise use the default.
986            self.qmake_spec = ''
987
988        # The binary MacOS/X Qt installer used to default to XCode.  If so then
989        # use macx-clang (Qt v5) or macx-g++ (Qt v4).
990        if sys.platform == 'darwin':
991            try:
992                # Qt v5.
993                if qt_config.QMAKE_SPEC == 'macx-xcode':
994                    # This will exist (and we can't check anyway).
995                    self.qmake_spec = 'macx-clang'
996                else:
997                    # No need to explicitly name the default.
998                    self.qmake_spec = ''
999            except AttributeError:
1000                # Qt v4.
1001                self.qmake_spec = 'macx-g++'
1002
1003        self.api_dir = os.path.join(qt_config.QT_INSTALL_DATA, 'qsci')
1004        self.qt_inc_dir = qt_config.QT_INSTALL_HEADERS
1005        self.qt_lib_dir = qt_config.QT_INSTALL_LIBS
1006
1007        if self.sysroot == '':
1008            self.sysroot = getattr(qt_config, 'QT_SYSROOT', '')
1009
1010    def apply_pre_options(self, opts):
1011        """ Apply options from the command line that influence subsequent
1012        configuration.  opts are the command line options.
1013        """
1014
1015        # On Windows the interpreter must be a debug build if a debug version
1016        # is to be built and vice versa.
1017        if sys.platform == 'win32':
1018            if opts.debug:
1019                if not self.py_debug:
1020                    error(
1021                            "A debug version of Python must be used when "
1022                            "--debug is specified.")
1023            elif self.py_debug:
1024                error(
1025                        "--debug must be specified when a debug version of "
1026                        "Python is used.")
1027
1028        self.debug = opts.debug
1029
1030        # Get the system root.
1031        if opts.sysroot is not None:
1032            self.sysroot = opts.sysroot
1033
1034        # Determine how to run qmake.
1035        if opts.qmake is not None:
1036            self.qmake = opts.qmake
1037
1038            # On Windows add the directory that probably contains the Qt DLLs
1039            # to PATH.
1040            if sys.platform == 'win32':
1041                path = os.environ['PATH']
1042                path = os.path.dirname(self.qmake) + ';' + path
1043                os.environ['PATH'] = path
1044
1045        if self.qmake is None:
1046            error(
1047                    "Use the --qmake argument to explicitly specify a working "
1048                    "Qt qmake.")
1049
1050        if opts.qmakespec is not None:
1051            self.qmake_spec = opts.qmakespec
1052
1053        if self.pyqt_package is not None:
1054            try:
1055                self.pyqt_package = opts.pyqt_package
1056            except AttributeError:
1057                # Multiple PyQt versions are not supported.
1058                pass
1059
1060            self.module_dir = os.path.join(self.py_module_dir,
1061                    self.pyqt_package)
1062
1063    def apply_post_options(self, opts, pkg_config):
1064        """ Apply options from the command line that override the previous
1065        configuration.  opts are the command line options.  pkg_config is the
1066        package configuration.
1067        """
1068
1069        if self.pyqt_package is not None:
1070            if pkg_config.user_pyqt_sip_flags_is_supported:
1071                if opts.pyqt_sip_flags is not None:
1072                    self.pyqt_sip_flags = opts.pyqt_sip_flags
1073
1074            if opts.pyqt_sip_dir is not None:
1075                self.pyqt_sip_dir = opts.pyqt_sip_dir
1076            else:
1077                self.pyqt_sip_dir = os.path.join(self.py_sip_dir,
1078                        self.pyqt_package)
1079
1080        if _has_stubs(pkg_config):
1081            if opts.stubsdir is not None:
1082                self.stubs_dir = opts.stubsdir
1083
1084            if opts.no_stubs:
1085                self.stubs_dir = ''
1086            elif self.stubs_dir == '':
1087                self.stubs_dir = self.module_dir
1088
1089        if pkg_config.qscintilla_api_file:
1090            if opts.apidir is not None:
1091                self.api_dir = opts.apidir
1092
1093            if opts.no_qsci_api:
1094                self.api_dir = ''
1095
1096        if opts.destdir is not None:
1097            self.module_dir = opts.destdir
1098
1099        if pkg_config.protected_is_public_is_supported:
1100            if opts.prot_is_public is not None:
1101                self.prot_is_public = opts.prot_is_public
1102        else:
1103            self.prot_is_public = False
1104
1105        if opts.sip_inc_dir is not None:
1106            self.sip_inc_dir = opts.sip_inc_dir
1107
1108        if opts.sip is not None:
1109            self.sip = opts.sip
1110
1111        pkg_config.apply_options(self, opts)
1112
1113    @staticmethod
1114    def _find_exe(*exes):
1115        """ Find an executable, ie. the first on the path. """
1116
1117        path_dirs = os.environ.get('PATH', '').split(os.pathsep)
1118
1119        for exe in exes:
1120            # Strip any surrounding quotes.
1121            if exe.startswith('"') and exe.endswith('"'):
1122                exe = exe[1:-1]
1123
1124            if sys.platform == 'win32':
1125                exe = exe + '.exe'
1126
1127            for d in path_dirs:
1128                exe_path = os.path.join(d, exe)
1129
1130                if os.access(exe_path, os.X_OK):
1131                    return exe_path
1132
1133        return None
1134
1135
1136def _create_optparser(target_config, pkg_config):
1137    """ Create the parser for the command line.  target_config is the target
1138    configuration containing default values.  pkg_config is the package
1139    configuration.
1140    """
1141
1142    pkg_name = pkg_config.descriptive_name
1143
1144    p = optparse.OptionParser(usage="python %prog [options]",
1145            version=pkg_config.version)
1146
1147    p.add_option('--spec', dest='qmakespec', default=None, action='store',
1148            metavar="SPEC",
1149            help="pass -spec SPEC to qmake")
1150
1151    if _has_stubs(pkg_config):
1152        p.add_option('--stubsdir', dest='stubsdir', type='string',
1153                default=None, action='callback',
1154                callback=optparser_store_abspath, metavar="DIR",
1155                help="the PEP 484 stubs will be installed in DIR [default: "
1156                        "with the module]")
1157        p.add_option('--no-stubs', dest='no_stubs', default=False,
1158                action='store_true',
1159                help="disable the installation of the PEP 484 stubs "
1160                        "[default: enabled]")
1161
1162    if pkg_config.qscintilla_api_file:
1163        p.add_option('--apidir', '-a', dest='apidir', type='string',
1164                default=None, action='callback',
1165                callback=optparser_store_abspath, metavar="DIR",
1166                help="the QScintilla API file will be installed in DIR "
1167                        "[default: QT_INSTALL_DATA/qsci]")
1168        p.add_option('--no-qsci-api', dest='no_qsci_api', default=False,
1169                action='store_true',
1170                help="disable the installation of the QScintilla API file "
1171                        "[default: enabled]")
1172
1173    if pkg_config.user_configuration_file_is_supported:
1174        p.add_option('--configuration', dest='config_file', type='string',
1175                default=None, action='callback',
1176                callback=optparser_store_abspath, metavar="FILE",
1177                help="FILE defines the target configuration")
1178
1179    p.add_option('--destdir', '-d', dest='destdir', type='string',
1180            default=None, action='callback', callback=optparser_store_abspath,
1181            metavar="DIR",
1182            help="install %s in DIR [default: %s]" %
1183                    (pkg_name, target_config.module_dir))
1184
1185    if pkg_config.protected_is_public_is_supported:
1186        p.add_option('--protected-is-public', dest='prot_is_public',
1187                default=None, action='store_true',
1188                help="enable building with 'protected' redefined as 'public' "
1189                        "[default: %s]" % target_config.prot_is_public)
1190        p.add_option('--protected-not-public', dest='prot_is_public',
1191                action='store_false',
1192                help="disable building with 'protected' redefined as 'public'")
1193
1194    if target_config.pyqt_package is not None:
1195        pyqt = target_config.pyqt_package
1196
1197        if pkg_config.pyqt5_is_supported and pkg_config.pyqt4_is_supported:
1198            p.add_option('--pyqt', dest='pyqt_package', type='choice',
1199                    choices=['PyQt4', 'PyQt5'], default=pyqt,
1200                    action='store', metavar="PyQtn",
1201                    help="configure for PyQt4 or PyQt5 [default: %s]" % pyqt)
1202
1203        if pkg_config.user_pyqt_sip_flags_is_supported:
1204            p.add_option('--pyqt-sip-flags', dest='pyqt_sip_flags',
1205                default=None, action='store', metavar="FLAGS",
1206                help="the sip flags used to build PyQt [default: query PyQt]")
1207
1208    p.add_option('--qmake', '-q', dest='qmake', type='string', default=None,
1209            action='callback', callback=optparser_store_abspath_exe,
1210            metavar="FILE",
1211            help="the pathname of qmake is FILE [default: %s]" % (
1212                    target_config.qmake or "search PATH"))
1213
1214    p.add_option('--sip', dest='sip', type='string', default=None,
1215            action='callback', callback=optparser_store_abspath_exe,
1216            metavar="FILE",
1217            help="the pathname of sip is FILE [default: "
1218                    "%s]" % (target_config.sip or "None"))
1219    p.add_option('--sip-incdir', dest='sip_inc_dir', type='string',
1220            default=None, action='callback',
1221            callback=optparser_store_abspath_dir, metavar="DIR",
1222            help="the directory containing the sip.h header file file is DIR "
1223                    "[default: %s]" % target_config.sip_inc_dir)
1224
1225    if target_config.pyqt_package is not None:
1226        p.add_option('--pyqt-sipdir', dest='pyqt_sip_dir', type='string',
1227                default=None, action='callback',
1228                callback=optparser_store_abspath_dir, metavar="DIR",
1229                help="the directory containing the PyQt .sip files is DIR "
1230                        "[default: %s]" % target_config.pyqt_sip_dir)
1231
1232    p.add_option('--concatenate', '-c', dest='concat', default=False,
1233            action='store_true',
1234            help="concatenate the C++ source files")
1235    p.add_option('--concatenate-split', '-j', dest='split', type='int',
1236            default=1, metavar="N",
1237            help="split the concatenated C++ source files into N pieces "
1238                    "[default: 1]")
1239    p.add_option('--static', '-k', dest='static', default=False,
1240            action='store_true',
1241            help="build a static %s" % pkg_name)
1242    p.add_option("--sysroot", dest='sysroot', type='string', action='callback',
1243            callback=optparser_store_abspath_dir, metavar="DIR",
1244            help="DIR is the target system root directory")
1245    p.add_option('--no-docstrings', dest='no_docstrings', default=False,
1246            action='store_true',
1247            help="disable the generation of docstrings")
1248    p.add_option('--trace', '-r', dest='tracing', default=False,
1249            action='store_true',
1250            help="build %s with tracing enabled" % pkg_name)
1251    p.add_option('--debug', '-u', default=False, action='store_true',
1252            help="build %s with debugging symbols" % pkg_name)
1253    p.add_option('--verbose', '-w', dest='verbose', default=False,
1254            action='store_true',
1255            help="enable verbose output during configuration")
1256
1257    pkg_config.init_optparser(p, target_config)
1258
1259    return p
1260
1261
1262def _has_stubs(pkg_config):
1263    """ See if a stub file for any of the modules will be generated.
1264    pkg_config is the package configuration.
1265    """
1266
1267    for module_config in pkg_config.modules:
1268        if module_config.pep484_stub_file:
1269            return True
1270
1271    return False
1272
1273
1274def _inform_user(target_config, pkg_config):
1275    """ Tell the user the values that are going to be used.  target_config is
1276    the target configuration.  pkg_config is the package configuration.
1277    """
1278
1279    pkg_name = pkg_config.descriptive_name
1280
1281    inform("Configuring %s %s..." % (pkg_name, pkg_config.version))
1282
1283    pkg_config.inform_user(target_config)
1284
1285    inform("%s will be installed in %s." %
1286            (pkg_name, target_config.module_dir))
1287
1288    if target_config.debug:
1289        inform("A debug version of %s will be built." % pkg_name)
1290
1291    if target_config.py_debug:
1292        inform("A debug build of Python is being used.")
1293
1294    if target_config.pyqt_version_str != '':
1295        inform("PyQt %s is being used." % target_config.pyqt_version_str)
1296    else:
1297        inform("%s is being used." % target_config.pyqt_package)
1298
1299    if target_config.qt_version_str != '':
1300        inform("Qt %s is being used." % target_config.qt_version_str)
1301
1302    if target_config.sysroot != '':
1303        inform("The system root directory is %s." % target_config.sysroot)
1304
1305    inform("sip %s is being used." % target_config.sip_version_str)
1306    inform("The sip executable is %s." % target_config.sip)
1307
1308    if target_config.prot_is_public:
1309        inform("%s is being built with 'protected' redefined as 'public'." %
1310                pkg_name)
1311
1312    if target_config.stubs_dir != '':
1313        inform("The PEP 484 stubs will be installed in %s." %
1314                target_config.stubs_dir)
1315
1316    if pkg_config.qscintilla_api_file and target_config.api_dir != '':
1317        inform("The QScintilla API file will be installed in %s." %
1318                os.path.join(target_config.api_dir, 'api', 'python'))
1319
1320
1321def _generate_code(target_config, opts, pkg_config, module_config):
1322    """ Generate the code for the module.  target_config is the target
1323    configuration.  opts are the command line options.  pkg_config is the
1324    package configuration.  module_config is the module configuration.
1325    """
1326
1327    inform(
1328            "Generating the C++ source for the %s module..." %
1329                    module_config.name)
1330
1331    # Generate the code in a module-specific sub-directory.
1332    try:
1333        os.mkdir(module_config.name)
1334    except:
1335        pass
1336
1337    # Build the SIP command line.
1338    argv = [quote(target_config.sip)]
1339
1340    # Tell SIP if this is a debug build of Python (SIP v4.19.1 and later).
1341    if target_config.sip_version >= 0x041301 and target_config.py_debug:
1342        argv.append('-D')
1343
1344    # Add the module-specific flags.
1345    argv.extend(pkg_config.get_sip_flags(target_config))
1346
1347    if target_config.pyqt_package is not None:
1348        # Get the flags used for the main PyQt module.
1349        argv.extend(target_config.pyqt_sip_flags.split())
1350
1351        # Add the backstop version.
1352        argv.append('-B')
1353        argv.append('Qt_6_0_0' if target_config.pyqt_package == 'PyQt5'
1354                else 'Qt_5_0_0')
1355
1356        # Add PyQt's .sip files to the search path.
1357        argv.append('-I')
1358        argv.append(quote(target_config.pyqt_sip_dir))
1359
1360    if target_config.stubs_dir != '':
1361        # Generate the stub file.
1362        argv.append('-y')
1363        argv.append(quote(module_config.pep484_stub_file + '.pyi'))
1364
1365    if pkg_config.qscintilla_api_file and target_config.api_dir != '':
1366        # Generate the API file.
1367        argv.append('-a')
1368        argv.append(quote(module_config.name + '.api'))
1369
1370    if target_config.prot_is_public:
1371        argv.append('-P');
1372
1373    if not opts.no_docstrings:
1374        argv.append('-o');
1375
1376    if opts.concat:
1377        argv.append('-j')
1378        argv.append(str(opts.split))
1379
1380    if opts.tracing:
1381        argv.append('-r')
1382
1383    argv.append('-c')
1384    argv.append(os.path.abspath(module_config.name))
1385
1386    # This assumes that, for multi-module packages, all modules's .sip files
1387    # will be rooted in a common root directory.
1388    sip_file = module_config.get_sip_file(target_config)
1389
1390    head, tail = os.path.split(sip_file)
1391    while head:
1392        head, tail = os.path.split(head)
1393
1394    if tail != sip_file:
1395        argv.append('-I')
1396        argv.append(quote(tail))
1397
1398    argv.append(sip_file)
1399
1400    check_file = os.path.join(module_config.name,
1401            'sipAPI%s.h' % module_config.name)
1402    _remove_file(check_file)
1403
1404    _run_command(' '.join(argv), opts.verbose)
1405
1406    if not os.access(check_file, os.F_OK):
1407        error("Unable to create the C++ code.")
1408
1409    # Generate the .pro file.
1410    _generate_pro(target_config, opts, module_config)
1411
1412
1413def _get_qt_qmake_config(qmake_config, qt_version):
1414    """ Return a dict of qmake configuration values for a specific Qt version.
1415    """
1416
1417    qt_qmake_config = {}
1418
1419    for name, value in qmake_config.items():
1420        name_parts = name.split(':')
1421        if len(name_parts) == 2 and name_parts[0] == qt_version:
1422            qt_qmake_config[name_parts[1]] = value
1423
1424    return qt_qmake_config
1425
1426
1427def _write_qt_qmake_config(qt_qmake_config, pro):
1428    """ Write the qmake configuration values to a .pro file. """
1429
1430    for name in ('QT', 'CONFIG', 'DEFINES', 'INCLUDEPATH', 'LIBS'):
1431        value = qt_qmake_config.get(name)
1432        if value:
1433            pro.write('    %s += %s\n' % (name, value))
1434
1435
1436def _generate_pro(target_config, opts, module_config):
1437    """ Generate the .pro file for the module.  target_config is the target
1438    configuration.  opts are the command line options.  module_config is the
1439    module configuration.
1440    """
1441
1442    inform("Generating the .pro file for the %s module..." % module_config.name)
1443
1444    # Without the 'no_check_exist' magic the target.files must exist when qmake
1445    # is run otherwise the install and uninstall targets are not generated.
1446
1447    qmake_config = module_config.get_qmake_configuration(target_config)
1448
1449    pro = open(os.path.join(module_config.name, module_config.name + '.pro'),
1450            'w')
1451
1452    pro.write('TEMPLATE = lib\n')
1453
1454    qt = qmake_config.get('QT')
1455    if qt:
1456        pro.write('QT += %s\n' % qt)
1457
1458    pro.write('CONFIG += %s\n' % ('debug' if target_config.debug else 'release'))
1459    pro.write('CONFIG += %s\n' % ('staticlib' if opts.static else 'plugin plugin_bundle'))
1460
1461    config = qmake_config.get('CONFIG')
1462    if config:
1463        pro.write('CONFIG += %s\n' % config)
1464
1465    # Work around QTBUG-39300.
1466    pro.write('CONFIG -= android_install\n')
1467
1468    qt5_qmake_config = _get_qt_qmake_config(qmake_config, 'Qt5')
1469    qt4_qmake_config = _get_qt_qmake_config(qmake_config, 'Qt4')
1470
1471    if qt5_qmake_config or qt4_qmake_config:
1472        pro.write('''
1473greaterThan(QT_MAJOR_VERSION, 4) {
1474''')
1475
1476        if qt5_qmake_config:
1477            _write_qt_qmake_config(qt5_qmake_config, pro)
1478
1479        if qt4_qmake_config:
1480            pro.write('} else {\n')
1481            _write_qt_qmake_config(qt4_qmake_config, pro)
1482
1483        pro.write('}\n')
1484
1485    mname = module_config.name
1486
1487    pro.write('TARGET = %s\n' % mname)
1488
1489    if not opts.static:
1490        pro.write('''
1491win32 {
1492    PY_MODULE = %s.pyd
1493    PY_MODULE_SRC = $(DESTDIR_TARGET)
1494
1495    LIBS += -L%s
1496} else {
1497    PY_MODULE = %s.so
1498
1499    macx {
1500        PY_MODULE_SRC = $(TARGET).plugin/Contents/MacOS/$(TARGET)
1501
1502        QMAKE_LFLAGS += "-undefined dynamic_lookup"
1503
1504        equals(QT_MAJOR_VERSION, 5) {
1505            equals(QT_MINOR_VERSION, 5) {
1506                QMAKE_RPATHDIR += $$[QT_INSTALL_LIBS]
1507            }
1508        }
1509    } else {
1510        PY_MODULE_SRC = $(TARGET)
1511    }
1512}
1513
1514QMAKE_POST_LINK = $(COPY_FILE) $$PY_MODULE_SRC $$PY_MODULE
1515
1516target.CONFIG = no_check_exist
1517target.files = $$PY_MODULE
1518''' % (mname, quote(target_config.py_pylib_dir), mname))
1519
1520    pro.write('''
1521target.path = %s
1522INSTALLS += target
1523''' % quote(target_config.module_dir))
1524
1525    sip_installs = module_config.get_sip_installs(target_config)
1526    if sip_installs is not None:
1527        path, files = sip_installs
1528
1529        pro.write('''
1530sip.path = %s
1531sip.files =''' % quote(path))
1532
1533        for f in files:
1534            pro.write(' \\\n    ../%s' % f)
1535
1536        pro.write('''
1537INSTALLS += sip
1538''')
1539
1540    pro.write('\n')
1541
1542    # These optimisations could apply to other platforms.
1543    if module_config.no_exceptions:
1544        if target_config.py_platform.startswith('linux') or target_config.py_platform == 'darwin':
1545            pro.write('QMAKE_CXXFLAGS += -fno-exceptions\n')
1546
1547    if target_config.py_platform.startswith('linux') and not opts.static:
1548        if target_config.py_version >= 0x030000:
1549            entry_point = 'PyInit_%s' % mname
1550        else:
1551            entry_point = 'init%s' % mname
1552
1553        exp = open(os.path.join(mname, mname + '.exp'), 'wt')
1554        exp.write('{ global: %s; local: *; };' % entry_point)
1555        exp.close()
1556
1557        pro.write('QMAKE_LFLAGS += -Wl,--version-script=%s.exp\n' % mname)
1558
1559    if target_config.prot_is_public:
1560        pro.write('DEFINES += SIP_PROTECTED_IS_PUBLIC protected=public\n')
1561
1562    defines = qmake_config.get('DEFINES')
1563    if defines:
1564        pro.write('DEFINES += %s\n' % defines)
1565
1566    includepath = qmake_config.get('INCLUDEPATH')
1567    if includepath:
1568        pro.write('INCLUDEPATH += %s\n' % includepath)
1569
1570    # Make sure the SIP include directory is searched before the Python include
1571    # directory if they are different.
1572    pro.write('INCLUDEPATH += %s\n' % quote(target_config.sip_inc_dir))
1573    if target_config.py_inc_dir != target_config.sip_inc_dir:
1574        pro.write('INCLUDEPATH += %s\n' % quote(target_config.py_inc_dir))
1575
1576    libs = qmake_config.get('LIBS')
1577    if libs:
1578        pro.write('LIBS += %s\n' % libs)
1579
1580    if not opts.static:
1581        dylib = module_config.get_mac_wrapped_library_file(target_config)
1582
1583        if dylib:
1584            pro.write('''
1585macx {
1586    QMAKE_POST_LINK = $$QMAKE_POST_LINK$$escape_expand(\\\\n\\\\t)$$quote(install_name_tool -change %s %s $$PY_MODULE)
1587}
1588''' % (os.path.basename(dylib), dylib))
1589
1590    pro.write('\n')
1591    pro.write('HEADERS = sipAPI%s.h\n' % mname)
1592
1593    pro.write('SOURCES =')
1594    for s in os.listdir(module_config.name):
1595        if s.endswith('.cpp'):
1596            pro.write(' \\\n    %s' % s)
1597    pro.write('\n')
1598
1599    pro.close()
1600
1601
1602def _run_qmake(target_config, verbose, pro_name):
1603    """ Run qmake against a .pro file.  target_config is the target
1604    configuration.  verbose is set if the output is to be displayed.  pro_name
1605    is the name of the .pro file.
1606    """
1607
1608    inform("Generating the Makefiles...")
1609
1610    # qmake doesn't behave consistently if it is not run from the directory
1611    # containing the .pro file - so make sure it is.
1612    pro_dir, pro_file = os.path.split(pro_name)
1613    if pro_dir != '':
1614        cwd = os.getcwd()
1615        os.chdir(pro_dir)
1616    else:
1617        cwd = None
1618
1619    mf = 'Makefile'
1620
1621    _remove_file(mf)
1622
1623    args = [quote(target_config.qmake)]
1624
1625    # Make sure all Makefiles are generated now in case qmake has been
1626    # configured with environment variables.
1627    args.append('-recursive')
1628
1629    if target_config.qmake_spec != '':
1630        args.append('-spec')
1631        args.append(target_config.qmake_spec)
1632
1633    args.append(pro_file)
1634
1635    _run_command(' '.join(args), verbose)
1636
1637    if not os.access(mf, os.F_OK):
1638        error(
1639                "%s failed to create a Makefile from %s." %
1640                        (target_config.qmake, pro_name))
1641
1642    # Restore the current directory.
1643    if cwd is not None:
1644        os.chdir(cwd)
1645
1646
1647def _run_command(cmd, verbose):
1648    """ Run a command and display the output if requested.  cmd is the command
1649    to run.  verbose is set if the output is to be displayed.
1650    """
1651
1652    if verbose:
1653        sys.stdout.write(cmd + "\n")
1654
1655    fout = _get_command_output(cmd)
1656
1657    # Read stdout and stderr until there is no more output.
1658    lout = fout.readline()
1659    while lout:
1660        if verbose:
1661            if sys.hexversion >= 0x03000000:
1662                sys.stdout.write(str(lout, encoding=sys.stdout.encoding))
1663            else:
1664                sys.stdout.write(lout)
1665
1666        lout = fout.readline()
1667
1668    fout.close()
1669
1670    try:
1671        os.wait()
1672    except:
1673        pass
1674
1675
1676def _get_command_output(cmd):
1677    """ Return a pipe from which a command's output can be read.  cmd is the
1678    command.
1679    """
1680
1681    try:
1682        import subprocess
1683    except ImportError:
1684        _, sout = os.popen4(cmd)
1685
1686        return sout
1687
1688    p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
1689            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1690
1691    return p.stdout
1692
1693
1694def _remove_file(fname):
1695    """ Remove a file which may or may not exist.  fname is the name of the
1696    file.
1697    """
1698
1699    try:
1700        os.remove(fname)
1701    except OSError:
1702        pass
1703
1704
1705def _check_sip(target_config, pkg_config):
1706    """ Check that the version of sip is good enough.  target_config is the
1707    target configuration.  pkg_config is the package configuration.
1708    """
1709
1710    if target_config.sip is None:
1711        error(
1712                "Make sure you have a working sip on your PATH or use the "
1713                "--sip argument to explicitly specify a working sip.")
1714
1715    pipe = os.popen(' '.join([quote(target_config.sip), '-V']))
1716
1717    for l in pipe:
1718        version_str = l.strip()
1719        break
1720    else:
1721        error("'%s -V' did not generate any output." % target_config.sip)
1722
1723    pipe.close()
1724
1725    if '.dev' in version_str or 'snapshot' in version_str:
1726        version = 0
1727    else:
1728        version = version_from_string(version_str)
1729        if version is None:
1730            error(
1731                    "'%s -V' generated unexpected output: '%s'." % (
1732                            target_config.sip, version_str))
1733
1734        min_sip_version = pkg_config.minimum_sip_version
1735        if min_sip_version:
1736            min_version = version_from_string(min_sip_version)
1737            if version < min_version:
1738                error(
1739                        "This version of %s requires sip %s or later." %
1740                                (pkg_config.descriptive_name, min_sip_version))
1741
1742    target_config.sip_version = version
1743    target_config.sip_version_str = version_str
1744
1745
1746def _main(argv, pkg_config):
1747    """ Create the configured package.  argv is the list of command line
1748    arguments.  pkg_config is the package configuration.
1749    """
1750
1751    # Create the default target configuration.
1752    target_config = _TargetConfiguration(pkg_config)
1753
1754    # Parse the command line.
1755    p = _create_optparser(target_config, pkg_config)
1756    opts, args = p.parse_args()
1757
1758    if args:
1759        p.print_help()
1760        sys.exit(2)
1761
1762    target_config.apply_pre_options(opts)
1763
1764    # Query qmake for the basic configuration information.
1765    target_config.get_qt_configuration(opts)
1766
1767    # Update the target configuration.
1768    if pkg_config.user_configuration_file_is_supported:
1769        config_file = opts.config_file
1770    else:
1771        config_file = None
1772
1773    if config_file is not None:
1774        target_config.update_from_configuration_file(config_file)
1775    else:
1776        target_config.apply_sysroot()
1777
1778    target_config.apply_post_options(opts, pkg_config)
1779
1780    if target_config.pyqt_package is not None:
1781        if target_config.pyqt_sip_flags is None:
1782            target_config.introspect_pyqt(pkg_config)
1783
1784    # Check SIP is new enough.
1785    _check_sip(target_config, pkg_config)
1786
1787    # Perform any package specific checks now that all other information has
1788    # been captured.
1789    pkg_config.check_package(target_config)
1790
1791    # Tell the user what's been found.
1792    _inform_user(target_config, pkg_config)
1793
1794    # Allow for module specific hacks.
1795    pkg_config.pre_code_generation(target_config)
1796
1797    # Generate the code.
1798    for module_config in pkg_config.modules:
1799        _generate_code(target_config, opts, pkg_config, module_config)
1800
1801    # Concatenate any .api files.
1802    if pkg_config.qscintilla_api_file and target_config.api_dir != '':
1803        inform("Generating the QScintilla API file...")
1804        f = open(pkg_config.qscintilla_api_file + '.api', 'w')
1805
1806        for module_config in pkg_config.modules:
1807            api = open(module_config.name + '.api')
1808
1809            for l in api:
1810                if target_config.pyqt_package is not None:
1811                    l = target_config.pyqt_package + '.' + l
1812
1813                f.write(l)
1814
1815            api.close()
1816            os.remove(module_config.name + '.api')
1817
1818        f.close()
1819
1820    # Generate the top-level .pro file.
1821    inform("Generating the top-level .pro file...")
1822
1823    pro_name = pkg_config.descriptive_name + '.pro'
1824    pro = open(pro_name, 'w')
1825
1826    pro.write('''TEMPLATE = subdirs
1827CONFIG += ordered nostrip
1828SUBDIRS = %s
1829''' % ' '.join([module.name for module in pkg_config.modules]))
1830
1831    if target_config.stubs_dir != '':
1832        stubs = [module.pep484_stub_file + '.pyi' for module in pkg_config.modules if module.pep484_stub_file]
1833
1834        if stubs:
1835            pro.write('''
1836pep484_stubs.path = %s
1837pep484_stubs.files = %s
1838INSTALLS += pep484_stubs
1839''' % (target_config.stubs_dir, ' '.join(stubs)))
1840
1841    if pkg_config.qscintilla_api_file and target_config.api_dir != '':
1842        pro.write('''
1843api.path = %s/api/python
1844api.files = %s.api
1845INSTALLS += api
1846''' % (target_config.api_dir, pkg_config.qscintilla_api_file))
1847
1848    pro.close()
1849
1850    # Generate the Makefile.
1851    _run_qmake(target_config, opts.verbose, pro_name)
1852
1853
1854###############################################################################
1855# The script starts here.
1856###############################################################################
1857
1858if __name__ == '__main__':
1859    # Assume the product is a package containing multiple modules.  If it isn't
1860    # then create a dummy package containing the single module.
1861    try:
1862        pkg_config_type = PackageConfiguration
1863    except NameError:
1864        pkg_config_type = type('PackageConfiguration', (object, ), {})
1865
1866    if not hasattr(pkg_config_type, 'modules'):
1867        mod_config_type = ModuleConfiguration
1868
1869        # Extract the package-specific attributes and methods.
1870        pkg_config_type.descriptive_name = mod_config_type.descriptive_name
1871        pkg_config_type.legacy_configuration_script = mod_config_type.legacy_configuration_script
1872        pkg_config_type.minimum_sip_version = mod_config_type.minimum_sip_version
1873        pkg_config_type.protected_is_public_is_supported = mod_config_type.protected_is_public_is_supported
1874        pkg_config_type.pyqt4_is_supported = mod_config_type.pyqt4_is_supported
1875        pkg_config_type.pyqt5_is_supported = mod_config_type.pyqt5_is_supported
1876        pkg_config_type.pyqt5_is_default = mod_config_type.pyqt5_is_default
1877        pkg_config_type.qscintilla_api_file = mod_config_type.qscintilla_api_file
1878        pkg_config_type.support_email_address = mod_config_type.support_email_address
1879        pkg_config_type.user_configuration_file_is_supported = mod_config_type.user_configuration_file_is_supported
1880        pkg_config_type.user_pyqt_sip_flags_is_supported = mod_config_type.user_pyqt_sip_flags_is_supported
1881        pkg_config_type.version = mod_config_type.version
1882
1883        pkg_config_type.init_target_configuration = staticmethod(
1884                mod_config_type.init_target_configuration)
1885        pkg_config_type.init_optparser = staticmethod(
1886                mod_config_type.init_optparser)
1887        pkg_config_type.apply_options = staticmethod(
1888                mod_config_type.apply_options)
1889        pkg_config_type.inform_user = staticmethod(
1890                mod_config_type.inform_user)
1891        pkg_config_type.pre_code_generation = staticmethod(
1892                mod_config_type.pre_code_generation)
1893        pkg_config_type.get_sip_flags = staticmethod(
1894                mod_config_type.get_sip_flags)
1895
1896        # Note the name change.
1897        pkg_config_type.check_package = staticmethod(
1898                mod_config_type.check_module)
1899
1900        pkg_config_type.modules = [mod_config_type()]
1901
1902    pkg_config = pkg_config_type()
1903
1904    try:
1905        _main(sys.argv, pkg_config)
1906    except SystemExit:
1907        raise
1908    except:
1909        if pkg_config.support_email_address:
1910            sys.stderr.write(
1911"""An internal error occured.  Please report all the output from the program,
1912including the following traceback, to %s.
1913""" % pkg_config.support_email_address)
1914
1915        raise
1916