# This script generates the Makefiles for building PyQt5.
#
# Copyright (c) 2016 Riverbank Computing Limited
#
# This file is part of PyQt5.
#
# This file may be used under the terms of the GNU General Public License
# version 3.0 as published by the Free Software Foundation and appearing in
# the file LICENSE included in the packaging of this file. Please review the
# following information to ensure the GNU General Public License version 3.0
# requirements will be met: http://www.gnu.org/copyleft/gpl.html.
#
# If you do not wish to use this file under the terms of the GPL version 3.0
# then you may purchase a commercial license. For more information contact
# info@riverbankcomputing.com.
#
# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
from distutils import sysconfig
import glob
import optparse
import os
import shutil
import stat
import sys
# Initialise the constants.
PYQT_VERSION_STR = "5.7"
SIP_MIN_VERSION = '4.18'
# The different values QLibraryInfo::licensee() can return for the LGPL version
# of Qt.
OPEN_SOURCE_LICENSEES = ('Open Source', 'Builder Qt')
class ModuleMetadata:
""" This class encapsulates the meta-data about a PyQt5 module. """
def __init__(self, qmake_QT=None, qmake_TARGET='', qpy_lib=False, cpp11=False, public=True):
""" Initialise the meta-data. """
# The values to update qmake's QT variable.
self.qmake_QT = [] if qmake_QT is None else qmake_QT
# The value to set qmake's TARGET variable to. It defaults to the name
# of the module.
self.qmake_TARGET = qmake_TARGET
# Set if there is a qpy support library.
self.qpy_lib = qpy_lib
# Set if C++11 support is required.
self.cpp11 = cpp11
# Set if the module is public.
self.public = public
# The module meta-data.
MODULE_METADATA = {
'dbus': ModuleMetadata(qmake_QT=['-gui'],
qmake_TARGET='pyqt5'),
'QAxContainer': ModuleMetadata(qmake_QT=['axcontainer']),
'Qt': ModuleMetadata(qmake_QT=['-core', '-gui']),
'QtBluetooth': ModuleMetadata(qmake_QT=['bluetooth']),
'QtCore': ModuleMetadata(qmake_QT=['-gui'], qpy_lib=True),
'QtDBus': ModuleMetadata(qmake_QT=['dbus', '-gui'],
qpy_lib=True),
'QtDesigner': ModuleMetadata(qmake_QT=['designer'],
qpy_lib=True),
'Enginio': ModuleMetadata(qmake_QT=['enginio']),
'QtGui': ModuleMetadata(qpy_lib=True),
'QtHelp': ModuleMetadata(qmake_QT=['help']),
'QtLocation': ModuleMetadata(qmake_QT=['location']),
'QtMacExtras': ModuleMetadata(qmake_QT=['macextras']),
'QtMultimedia': ModuleMetadata(qmake_QT=['multimedia']),
'QtMultimediaWidgets': ModuleMetadata(
qmake_QT=['multimediawidgets',
'multimedia']),
'QtNetwork': ModuleMetadata(qmake_QT=['network', '-gui']),
'QtNfc': ModuleMetadata(qmake_QT=['nfc', '-gui']),
'QtOpenGL': ModuleMetadata(qmake_QT=['opengl']),
'QtPositioning': ModuleMetadata(qmake_QT=['positioning']),
'QtPrintSupport': ModuleMetadata(qmake_QT=['printsupport']),
'QtQml': ModuleMetadata(qmake_QT=['qml'], qpy_lib=True),
'QtQuick': ModuleMetadata(qmake_QT=['quick'], qpy_lib=True),
'QtQuickWidgets': ModuleMetadata(qmake_QT=['quickwidgets']),
'QtSensors': ModuleMetadata(qmake_QT=['sensors']),
'QtSerialPort': ModuleMetadata(qmake_QT=['serialport']),
'QtSql': ModuleMetadata(qmake_QT=['sql', 'widgets']),
'QtSvg': ModuleMetadata(qmake_QT=['svg']),
'QtTest': ModuleMetadata(qmake_QT=['testlib', 'widgets']),
'QtWebChannel': ModuleMetadata(
qmake_QT=['webchannel', 'network',
'-gui']),
'QtWebEngineCore': ModuleMetadata(qmake_QT=['webenginecore', '-gui']),
'QtWebEngineWidgets': ModuleMetadata(
qmake_QT=['webenginewidgets', 'webchannel',
'network', 'widgets'],
cpp11=True),
'QtWebKit': ModuleMetadata(qmake_QT=['webkit', 'network']),
'QtWebKitWidgets': ModuleMetadata(
qmake_QT=['webkitwidgets',
'printsupport']),
'QtWebSockets': ModuleMetadata(qmake_QT=['websockets', '-gui']),
'QtWidgets': ModuleMetadata(qmake_QT=['widgets'], qpy_lib=True),
'QtWinExtras': ModuleMetadata(qmake_QT=['winextras', 'widgets']),
'QtX11Extras': ModuleMetadata(qmake_QT=['x11extras']),
'QtXml': ModuleMetadata(qmake_QT=['xml', '-gui']),
'QtXmlPatterns': ModuleMetadata(
qmake_QT=['xmlpatterns', '-gui',
'network']),
# The OpenGL wrappers.
'_QOpenGLFunctions_1_0': ModuleMetadata(public=False),
'_QOpenGLFunctions_1_1': ModuleMetadata(public=False),
'_QOpenGLFunctions_1_2': ModuleMetadata(public=False),
'_QOpenGLFunctions_1_3': ModuleMetadata(public=False),
'_QOpenGLFunctions_1_4': ModuleMetadata(public=False),
'_QOpenGLFunctions_1_5': ModuleMetadata(public=False),
'_QOpenGLFunctions_2_0': ModuleMetadata(public=False),
'_QOpenGLFunctions_2_1': ModuleMetadata(public=False),
'_QOpenGLFunctions_3_0': ModuleMetadata(public=False),
'_QOpenGLFunctions_3_1': ModuleMetadata(public=False),
'_QOpenGLFunctions_3_2_Compatibility': ModuleMetadata(public=False),
'_QOpenGLFunctions_3_2_Core': ModuleMetadata(public=False),
'_QOpenGLFunctions_3_3_Compatibility': ModuleMetadata(public=False),
'_QOpenGLFunctions_3_3_Core': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_0_Compatibility': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_0_Core': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_1_Compatibility': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_1_Core': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_2_Compatibility': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_2_Core': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_3_Compatibility': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_3_Core': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_4_Compatibility': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_4_Core': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_5_Compatibility': ModuleMetadata(public=False),
'_QOpenGLFunctions_4_5_Core': ModuleMetadata(public=False),
'_QOpenGLFunctions_ES2': ModuleMetadata(public=False),
# Internal modules.
'pylupdate': ModuleMetadata(qmake_QT=['xml', '-gui'],
qpy_lib=True, public=False),
'pyrcc': ModuleMetadata(qmake_QT=['xml', '-gui'],
qpy_lib=True, public=False),
}
# The component modules that make up the composite Qt module. SIP is broken in
# its handling of composite module in that a component module must be %Included
# before it is first %Imported. In other words, a module must appear before
# any modules that depend on it.
COMPOSITE_COMPONENTS = (
'QtCore',
'QtDBus', 'QtGui', 'QtNetwork', 'QtSensors', 'QtSerialPort',
'QtMultimedia', 'QtQml', 'QtWebKit', 'QtWidgets', 'QtXml', 'QtXmlPatterns',
'QtAxContainer', 'QtDesigner', 'QtHelp', 'QtMultimediaWidgets', 'QtOpenGL',
'QtPrintSupport', 'QtQuick', 'QtSql', 'QtSvg', 'QtTest',
'QtWebKitWidgets', 'QtBluetooth', 'QtMacExtras', 'QtPositioning',
'QtWinExtras', 'QtX11Extras', 'QtQuickWidgets', 'QtWebSockets',
'Enginio', 'QtWebChannel', 'QtWebEngineCore', 'QtWebEngineWidgets',
'QtLocation', 'QtNfc'
)
def error(msg):
""" Display an error message and terminate. msg is the text of the error
message.
"""
sys.stderr.write(format("Error: " + msg) + "\n")
sys.exit(1)
def inform(msg):
""" Display an information message. msg is the text of the error message.
"""
sys.stdout.write(format(msg) + "\n")
def format(msg, left_margin=0, right_margin=78):
""" Format a message by inserting line breaks at appropriate places. msg
is the text of the message. left_margin is the position of the left
margin. right_margin is the position of the right margin. Returns the
formatted message.
"""
curs = left_margin
fmsg = " " * left_margin
for w in msg.split():
l = len(w)
if curs != left_margin and curs + l > right_margin:
fmsg = fmsg + "\n" + (" " * left_margin)
curs = left_margin
if curs > left_margin:
fmsg = fmsg + " "
curs = curs + 1
fmsg = fmsg + w
curs = curs + l
return fmsg
def version_to_sip_tag(version):
""" Convert a version number to a SIP tag. version is the version number.
"""
# Anything after Qt v5 is assumed to be Qt v6.0.
if version > 0x060000:
version = 0x060000
major = (version >> 16) & 0xff
minor = (version >> 8) & 0xff
patch = version & 0xff
return 'Qt_%d_%d_%d' % (major, minor, patch)
def version_to_string(version, parts=3):
""" Convert an n-part version number encoded as a hexadecimal value to a
string. version is the version number. Returns the string.
"""
part_list = [str((version >> 16) & 0xff)]
if parts > 1:
part_list.append(str((version >> 8) & 0xff))
if parts > 2:
part_list.append(str(version & 0xff))
return '.'.join(part_list)
class ConfigurationFileParser:
""" A parser for configuration files. """
def __init__(self, config_file):
""" Read and parse a configuration file. """
self._config = {}
self._extrapolating = []
cfg = open(config_file)
line_nr = 0
last_name = None
section = ''
section_config = {}
self._config[section] = section_config
for l in cfg:
line_nr += 1
# Strip comments.
l = l.split('#')[0]
# See if this might be part of a multi-line.
multiline = (last_name is not None and len(l) != 0 and l[0] == ' ')
l = l.strip()
if l == '':
last_name = None
continue
# See if this is a new section.
if l[0] == '[' and l[-1] == ']':
section = l[1:-1].strip()
if section == '':
error(
"%s:%d: Empty section name." % (
config_file, line_nr))
if section in self._config:
error(
"%s:%d: Section '%s' defined more than once." % (
config_file, line_nr, section))
section_config = {}
self._config[section] = section_config
last_name = None
continue
parts = l.split('=', 1)
if len(parts) == 2:
name = parts[0].strip()
value = parts[1].strip()
elif multiline:
name = last_name
value = section_config[last_name]
value += ' ' + l
else:
name = value = ''
if name == '' or value == '':
error("%s:%d: Invalid line." % (config_file, line_nr))
section_config[name] = value
last_name = name
cfg.close()
def sections(self):
""" Return the list of sections, excluding the default one. """
return [s for s in self._config.keys() if s != '']
def preset(self, name, value):
""" Add a preset value to the configuration. """
self._config[''][name] = value
def get(self, section, name, default=None):
""" Get a configuration value while extrapolating. """
# Get the name from the section, or the default section.
value = self._config[section].get(name)
if value is None:
value = self._config[''].get(name)
if value is None:
if default is None:
error(
"Configuration file references non-existent name "
"'%s'." % name)
return default
# Handle any extrapolations.
parts = value.split('%(', 1)
while len(parts) == 2:
prefix, tail = parts
parts = tail.split(')', 1)
if len(parts) != 2:
error(
"Configuration file contains unterminated "
"extrapolated name '%s'." % tail)
xtra_name, suffix = parts
if xtra_name in self._extrapolating:
error(
"Configuration file contains a recursive reference to "
"'%s'." % xtra_name)
self._extrapolating.append(xtra_name)
xtra_value = self.get(section, xtra_name)
self._extrapolating.pop()
value = prefix + xtra_value + suffix
parts = value.split('%(', 1)
return value
def getboolean(self, section, name, default):
""" Get a boolean configuration value while extrapolating. """
value = self.get(section, name, default)
# In case the default was returned.
if isinstance(value, bool):
return value
if value in ('True', 'true', '1'):
return True
if value in ('False', 'false', '0'):
return False
error(
"Configuration file contains invalid boolean value for "
"'%s'." % name)
def getlist(self, section, name, default):
""" Get a list configuration value while extrapolating. """
value = self.get(section, name, default)
# In case the default was returned.
if isinstance(value, list):
return value
return value.split()
class HostPythonConfiguration:
""" A container for the host Python configuration. """
def __init__(self):
""" Initialise the configuration. """
self.platform = sys.platform
self.version = sys.hexversion >> 8
self.inc_dir = sysconfig.get_python_inc()
self.venv_inc_dir = sysconfig.get_python_inc(prefix=sys.prefix)
self.module_dir = sysconfig.get_python_lib(plat_specific=1)
if sys.platform == 'win32':
self.bin_dir = sys.exec_prefix
self.data_dir = sys.prefix
self.lib_dir = sys.prefix + '\\libs'
else:
self.bin_dir = sys.exec_prefix + '/bin'
self.data_dir = sys.prefix + '/share'
self.lib_dir = sys.prefix + '/lib'
# The name of the interpreter used by the pyuic5 wrapper.
if sys.platform == 'darwin':
# The installation of MacOS's python is a mess that changes from
# version to version and where sys.executable is useless.
py_major = self.version >> 16
py_minor = (self.version >> 8) & 0xff
# In Python v3.4 and later there is no pythonw.
if (py_major == 3 and py_minor >= 4) or py_major >= 4:
exe = "python"
else:
exe = "pythonw"
self.pyuic_interpreter = '%s%d.%d' % (exe, py_major, py_minor)
else:
self.pyuic_interpreter = sys.executable
class TargetQtConfiguration:
""" A container for the target Qt configuration. """
def __init__(self, qmake):
""" Initialise the configuration. qmake is the full pathname of the
qmake executable that will provide the configuration.
"""
inform("Querying qmake about your Qt installation...")
pipe = os.popen(' '.join([qmake, '-query']))
for l in pipe:
l = l.strip()
tokens = l.split(':', 1)
if isinstance(tokens, list):
if len(tokens) != 2:
error("Unexpected output from qmake: '%s'\n" % l)
name, value = tokens
else:
name = tokens
value = None
name = name.replace('/', '_')
setattr(self, name, value)
pipe.close()
class TargetConfiguration:
""" A container for configuration information about the target. """
def __init__(self):
""" Initialise the configuration with default values. """
# Values based on the host Python configuration.
py_config = HostPythonConfiguration()
self.py_inc_dir = py_config.inc_dir
self.py_venv_inc_dir = py_config.venv_inc_dir
self.py_lib_dir = py_config.lib_dir
self.py_platform = py_config.platform
self.py_version = py_config.version
self.pyqt_bin_dir = py_config.bin_dir
self.pyqt_module_dir = py_config.module_dir
self.pyqt_stubs_dir = os.path.join(py_config.module_dir, 'PyQt5')
self.pyqt_sip_dir = os.path.join(py_config.data_dir, 'sip', 'PyQt5')
self.pyuic_interpreter = py_config.pyuic_interpreter
# The qmake spec we want to use.
if self.py_platform == 'win32':
if self.py_version >= 0x030500:
self.qmake_spec = 'win32-msvc2015'
elif self.py_version >= 0x030300:
self.qmake_spec = 'win32-msvc2010'
elif self.py_version >= 0x020600:
self.qmake_spec = 'win32-msvc2008'
elif self.py_version >= 0x020400:
self.qmake_spec = 'win32-msvc.net'
else:
self.qmake_spec = 'win32-msvc'
else:
# Use the Qt default. (We may update it for OS/X later.)
self.qmake_spec = ''
self.default_qmake_spec = ''
# Remaining values.
self.dbus_inc_dirs = []
self.dbus_lib_dirs = []
self.dbus_libs = []
self.debug = False
self.designer_plugin_dir = ''
self.license_dir = source_path('sip')
self.no_designer_plugin = False
self.no_docstrings = False
self.no_pydbus = False
self.no_qml_plugin = False
self.no_tools = False
self.prot_is_public = (self.py_platform.startswith('linux') or self.py_platform == 'darwin')
self.qmake = self._find_exe('qmake')
self.qmake_variables = []
self.py_pylib_dir = ''
self.py_pylib_lib = ''
self.py_pyshlib = ''
self.pydbus_inc_dir = ''
self.pydbus_module_dir = ''
self.pyqt_disabled_features = []
self.pyqt_modules = []
self.qml_plugin_dir = ''
self.qsci_api = False
self.qsci_api_dir = ''
self.qt_licensee = ''
self.qtconf_prefix = ''
self.qt_shared = False
self.qt_version = 0
self.sip = self._find_exe('sip5', 'sip')
self.sip_inc_dir = ''
self.static = False
self.sysroot = ''
self.vend_enabled = False
self.vend_inc_dir = ''
self.vend_lib_dir = ''
def from_configuration_file(self, config_file):
""" Initialise the configuration with values from a file. config_file
is the name of the configuration file.
"""
inform("Reading configuration from %s..." % config_file)
parser = ConfigurationFileParser(config_file)
# Populate some presets from the command line.
version = version_to_string(self.py_version).split('.')
parser.preset('py_major', version[0])
parser.preset('py_minor', version[1])
parser.preset('sysroot', self.sysroot)
# Find the section corresponding to the version of Qt.
qt_major = self.qt_version >> 16
section = None
latest_section = -1
for name in parser.sections():
parts = name.split()
if len(parts) != 2 or parts[0] != 'Qt':
continue
section_qt_version = version_from_string(parts[1])
if section_qt_version is None:
continue
# Major versions must match.
if section_qt_version >> 16 != self.qt_version >> 16:
continue
# It must be no later that the version of Qt.
if section_qt_version > self.qt_version:
continue
# Save it if it is the latest so far.
if se