Exemplo n.º 1
0
class NuitkaPluginQtBindingsPluginBase(NuitkaPluginBase):
    # For overload in the derived bindings plugin.
    binding_name = None

    def __init__(self, qt_plugins):
        self.webengine_done = False
        self.qt_plugins_dirs = None

        self.qt_plugins = OrderedSet(x.strip().lower() for x in qt_plugins.split(","))

        # Allow to specify none.
        if self.qt_plugins == set(["none"]):
            self.qt_plugins = set()

    @classmethod
    def addPluginCommandLineOptions(cls, group):
        group.add_option(
            "--include-qt-plugins",
            action="store",
            dest="qt_plugins",
            default="sensible",
            help="""\
Which Qt plugins to include. These can be big with dependencies, so
by default only the sensible ones are included, but you can also put
"all" or list them individually. If you specify something that does
not exist, a list of all available will be given.""",
        )

    @abstractmethod
    def _getQmlTargetDir(self, target_plugin_dir):
        """ Where does the bindings package expect the QML files. """

    def getQtPluginsSelected(self):
        # Resolve "sensible on first use"
        if "sensible" in self.qt_plugins:
            # Most used ones with low dependencies.
            self.qt_plugins.update(
                tuple(
                    family
                    for family in (
                        "imageformats",
                        "iconengines",
                        "mediaservice",
                        "printsupport",
                        "platforms",
                    )
                    if self.hasPluginFamily(family)
                )
            )

            self.qt_plugins.remove("sensible")

            # Make sure the above didn't detect nothing, which would be
            # indicating the check to be bad.
            assert self.qt_plugins

        return self.qt_plugins

    def getQtPluginDirs(self):
        if self.qt_plugins_dirs is not None:
            return self.qt_plugins_dirs

        command = """\
from __future__ import print_function
from __future__ import absolute_import

import %(binding_name)s.QtCore
for v in %(binding_name)s.QtCore.QCoreApplication.libraryPaths():
    print(v)
import os
# Standard CPython has installations like this.
guess_path = os.path.join(os.path.dirname(%(binding_name)s.__file__), "plugins")
if os.path.exists(guess_path):
    print("GUESS:", guess_path)
# Anaconda has this, but it seems not automatic.
guess_path = os.path.join(os.path.dirname(%(binding_name)s.__file__), "..", "..", "..", "Library", "plugins")
if os.path.exists(guess_path):
    print("GUESS:", guess_path)
""" % {
            "binding_name": self.binding_name
        }

        output = Execution.check_output([sys.executable, "-c", command])

        # May not be good for everybody, but we cannot have bytes in paths, or
        # else working with them breaks down.
        if str is not bytes:
            output = output.decode("utf-8")

        result = []

        for line in output.replace("\r", "").split("\n"):
            if not line:
                continue

            # Take the guessed path only if necessary.
            if line.startswith("GUESS: "):
                if result:
                    continue

                line = line[len("GUESS: ") :]

            result.append(os.path.normpath(line))

        # Avoid duplicates.
        result = tuple(sorted(set(result)))

        self.qt_plugins_dirs = result

        return result

    def _getQtBinDirs(self):
        for plugin_dir in self.getQtPluginDirs():
            qt_bin_dir = os.path.normpath(os.path.join(plugin_dir, "..", "bin"))

            if os.path.isdir(qt_bin_dir):
                yield qt_bin_dir

    def hasPluginFamily(self, family):
        for plugin_dir in self.getQtPluginDirs():
            if os.path.isdir(os.path.join(plugin_dir, family)):
                return True

        # TODO: Special case "xml".
        return False

    def copyQmlFiles(self, full_name, target_plugin_dir):
        for plugin_dir in self.getQtPluginDirs():
            qml_plugin_dir = os.path.normpath(os.path.join(plugin_dir, "..", "qml"))

            if os.path.exists(qml_plugin_dir):
                break
        else:
            self.sysexit("Error, no such Qt plugin family: qml")

        qml_target_dir = os.path.normpath(self._getQmlTargetDir(target_plugin_dir))

        self.info("Copying Qt plug-ins 'qml' to '%s'." % (qml_target_dir))

        copyTree(qml_plugin_dir, qml_target_dir)

        # We try to filter here, not for DLLs.
        return [
            (
                filename,
                os.path.join(qml_target_dir, os.path.relpath(filename, qml_plugin_dir)),
                full_name,
            )
            for filename in getFileList(qml_plugin_dir)
            if not filename.endswith(
                (
                    ".qml",
                    ".qmlc",
                    ".qmltypes",
                    ".js",
                    ".jsc",
                    ".png",
                    ".ttf",
                    ".metainfo",
                )
            )
            if not os.path.isdir(filename)
            if not os.path.basename(filename) == "qmldir"
        ]

    def findDLLs(self, full_name, target_plugin_dir):
        # TODO: Change this to modern DLL entry points.
        return [
            (
                filename,
                os.path.join(target_plugin_dir, os.path.relpath(filename, plugin_dir)),
                full_name,
            )
            for plugin_dir in self.getQtPluginDirs()
            for filename in getFileList(plugin_dir)
            if not filename.endswith(".qml")
            if not filename.endswith(".mesh")
            if os.path.exists(
                os.path.join(target_plugin_dir, os.path.relpath(filename, plugin_dir))
            )
        ]

    def createPostModuleLoadCode(self, module):
        """Create code to load after a module was successfully imported.

        For Qt we need to set the library path to the distribution folder
        we are running from. The code is immediately run after the code
        and therefore makes sure it's updated properly.
        """

        # Only in standalone mode, this will be needed.
        if not Options.isStandaloneMode():
            return

        full_name = module.getFullName()

        if full_name == "%s.QtCore" % self.binding_name:
            code = """\
from __future__ import absolute_import

from %(package_name)s import QCoreApplication
import os

QCoreApplication.setLibraryPaths(
    [
        os.path.join(
           os.path.dirname(__file__),
           "qt-plugins"
        )
    ]
)
""" % {
                "package_name": full_name
            }

            return (
                code,
                """\
Setting Qt library path to distribution folder. We need to avoid loading target
system Qt plug-ins, which may be from another Qt version.""",
            )

    @staticmethod
    def createPreModuleLoadCode(module):
        """Method called when a module is being imported.

        Notes:
            If full name equals to the binding we insert code to include the dist
            folder in the 'PATH' environment variable (on Windows only).

        Args:
            module: the module object
        Returns:
            Code to insert and descriptive text (tuple), or (None, None).
        """
        if (
            not isWin32Windows() or not Options.isStandaloneMode()
        ):  # we are only relevant on standalone mode for Windows
            return None

        if module.getFullName() != "PyQt5":
            return None

        code = """import os
path = os.environ.get("PATH", "")
if not path.startswith(__nuitka_binary_dir):
    os.environ["PATH"] = __nuitka_binary_dir + ";" + path
"""
        return (
            code,
            "Adding binary folder to runtime 'PATH' environment variable for proper loading.",
        )

    def considerExtraDlls(self, dist_dir, module):
        # pylint: disable=too-many-branches,too-many-locals,too-many-statements
        full_name = module.getFullName()

        if full_name == self.binding_name:
            if not self.getQtPluginDirs():
                self.sysexit(
                    "Error, failed to detect %r plugin directories." % self.binding_name
                )

            target_plugin_dir = os.path.join(dist_dir, full_name.asPath(), "qt-plugins")

            self.info(
                "Copying Qt plug-ins '%s' to '%s'."
                % (
                    ",".join(
                        sorted(x for x in self.getQtPluginsSelected() if x != "xml")
                    ),
                    target_plugin_dir,
                )
            )

            # TODO: Change this to filtering copyTree while it's doing it.
            for plugin_dir in self.getQtPluginDirs():
                copyTree(plugin_dir, target_plugin_dir)

            if "all" not in self.getQtPluginsSelected():
                for plugin_candidate in getSubDirectories(target_plugin_dir):
                    if (
                        os.path.basename(plugin_candidate)
                        not in self.getQtPluginsSelected()
                    ):
                        removeDirectory(plugin_candidate, ignore_errors=False)

                for plugin_candidate in self.getQtPluginsSelected():
                    if plugin_candidate == "qml":
                        continue

                    if not os.path.isdir(
                        os.path.join(target_plugin_dir, plugin_candidate)
                    ):
                        self.sysexit(
                            "Error, no such Qt plugin family: %s" % plugin_candidate
                        )

            result = self.findDLLs(
                full_name=full_name,
                target_plugin_dir=target_plugin_dir,
            )

            if isWin32Windows():
                # Those 2 vars will be used later, just saving some resources
                # by caching the files list
                qt_bin_files = sum(
                    (getFileList(qt_bin_dir) for qt_bin_dir in self._getQtBinDirs()),
                    [],
                )

                self.info("Copying OpenSSL DLLs to %r." % dist_dir)

                for filename in qt_bin_files:
                    basename = os.path.basename(filename).lower()
                    if basename in ("libeay32.dll", "ssleay32.dll"):
                        shutil.copy(filename, os.path.join(dist_dir, basename))

            if (
                "qml" in self.getQtPluginsSelected()
                or "all" in self.getQtPluginsSelected()
            ):
                result += self.copyQmlFiles(
                    full_name=full_name,
                    target_plugin_dir=target_plugin_dir,
                )

                # Also copy required OpenGL DLLs on Windows
                if isWin32Windows():
                    opengl_dlls = ("libegl.dll", "libglesv2.dll", "opengl32sw.dll")

                    self.info("Copying OpenGL DLLs to %r." % dist_dir)

                    for filename in qt_bin_files:
                        basename = os.path.basename(filename).lower()

                        if basename in opengl_dlls or basename.startswith(
                            "d3dcompiler_"
                        ):
                            shutil.copy(filename, os.path.join(dist_dir, basename))

            return result
        elif full_name == self.binding_name + ".QtNetwork":
            if not isWin32Windows():
                dll_path = locateDLL("crypto")

                if dll_path is None:
                    dist_dll_path = os.path.join(dist_dir, os.path.basename(dll_path))
                    shutil.copy(dll_path, dist_dll_path)

                dll_path = locateDLL("ssl")
                if dll_path is not None:
                    dist_dll_path = os.path.join(dist_dir, os.path.basename(dll_path))

                    shutil.copy(dll_path, dist_dll_path)
        elif (
            full_name
            in (
                self.binding_name + ".QtWebEngine",
                self.binding_name + ".QtWebEngineCore",
                self.binding_name + ".QtWebEngineWidgets",
            )
            and not self.webengine_done
        ):
            self.webengine_done = True  # prevent multiple copies
            self.info("Copying QtWebEngine components")

            plugin_parent = os.path.dirname(self.getQtPluginDirs()[0])

            if isWin32Windows():
                bin_dir = os.path.join(plugin_parent, "bin")
            else:  # TODO verify this for non-Windows!
                bin_dir = os.path.join(plugin_parent, "libexec")
            target_bin_dir = os.path.join(dist_dir)
            for f in os.listdir(bin_dir):
                if f.startswith("QtWebEngineProcess"):
                    shutil.copy(os.path.join(bin_dir, f), target_bin_dir)

            resources_dir = os.path.join(plugin_parent, "resources")
            target_resources_dir = os.path.join(dist_dir)
            for f in os.listdir(resources_dir):
                shutil.copy(os.path.join(resources_dir, f), target_resources_dir)

            translations_dir = os.path.join(plugin_parent, "translations")
            pos = len(translations_dir) + 1
            target_translations_dir = os.path.join(
                dist_dir,
                full_name.getTopLevelPackageName().asPath(),
                "Qt",
                "translations",
            )
            for f in getFileList(translations_dir):
                tar_f = os.path.join(target_translations_dir, f[pos:])
                makePath(os.path.dirname(tar_f))
                shutil.copyfile(f, tar_f)

        return ()

    def removeDllDependencies(self, dll_filename, dll_filenames):
        for value in self.getQtPluginDirs():
            # TODO: That is not a proper check if a file is below that.
            if dll_filename.startswith(value):
                for sub_dll_filename in dll_filenames:
                    for badword in (
                        "libKF5",
                        "libkfontinst",
                        "libkorganizer",
                        "libplasma",
                        "libakregator",
                        "libdolphin",
                        "libnoteshared",
                        "libknotes",
                        "libsystemsettings",
                        "libkerfuffle",
                        "libkaddressbook",
                        "libkworkspace",
                        "libkmail",
                        "libmilou",
                        "libtaskmanager",
                        "libkonsole",
                        "libgwenview",
                        "libweather_ion",
                    ):
                        if os.path.basename(sub_dll_filename).startswith(badword):
                            yield sub_dll_filename
Exemplo n.º 2
0
class NuitkaPluginQtBindingsPluginBase(NuitkaPluginBase):
    # For overload in the derived bindings plugin.
    binding_name = None

    def __init__(self, qt_plugins):
        self.webengine_done = False
        self.qt_plugins_dirs = None

        self.qt_plugins = OrderedSet(x.strip().lower() for x in qt_plugins.split(","))

        # Allow to specify none.
        if self.qt_plugins == set(["none"]):
            self.qt_plugins = set()

    @classmethod
    def addPluginCommandLineOptions(cls, group):
        group.add_option(
            "--include-qt-plugins",
            action="store",
            dest="qt_plugins",
            default="sensible",
            help="""\
Which Qt plugins to include. These can be big with dependencies, so
by default only the sensible ones are included, but you can also put
"all" or list them individually. If you specify something that does
not exist, a list of all available will be given.""",
        )

    @abstractmethod
    def _getQmlTargetDir(self, target_plugin_dir):
        """ Where does the bindings package expect the QML files. """

    def getQtPluginsSelected(self):
        # Resolve "sensible on first use"
        if "sensible" in self.qt_plugins:
            # Most used ones with low dependencies.
            self.qt_plugins.update(
                tuple(
                    family
                    for family in (
                        "imageformats",
                        "iconengines",
                        "mediaservice",
                        "printsupport",
                        "platforms",
                        "styles",
                    )
                    if self.hasPluginFamily(family)
                )
            )

            self.qt_plugins.remove("sensible")

            # Make sure the above didn't detect nothing, which would be
            # indicating the check to be bad.
            assert self.qt_plugins

        return self.qt_plugins

    def _getQtInformation(self):
        # This is generic, and therefore needs to apply this to a lot of strings.
        def applyBindingName(template):
            return template % {"binding_name": self.binding_name}

        setup_codes = applyBindingName(
            r"""\
import os
import %(binding_name)s.QtCore
"""
        )

        return self.queryRuntimeInformationMultiple(
            info_name=applyBindingName("%(binding_name)s_info"),
            setup_codes=setup_codes,
            values=(
                (
                    "library_paths",
                    applyBindingName(
                        "%(binding_name)s.QtCore.QCoreApplication.libraryPaths()"
                    ),
                ),
                (
                    "guess_path1",
                    applyBindingName(
                        "os.path.join(os.path.dirname(%(binding_name)s.__file__), 'plugins')"
                    ),
                ),
                (
                    "guess_path2",
                    applyBindingName(
                        "os.path.join(os.path.dirname(%(binding_name)s.__file__), '..', '..', '..', 'Library', 'plugins')"
                    ),
                ),
                (
                    "version",
                    applyBindingName(
                        "%(binding_name)s.__version_info__"
                        if "PySide" in self.binding_name
                        else "%(binding_name)s.QtCore.PYQT_VERSION_STR"
                    ),
                ),
                (
                    "nuitka_patch_level",
                    applyBindingName(
                        "getattr(%(binding_name)s, '_nuitka_patch_level', 0)"
                    ),
                ),
            ),
        )

    def _getBindingVersion(self):
        """ Get the version of the binding in tuple digit form, e.g. (6,0,3) """
        return self._getQtInformation().version

    def _getNuitkaPatchLevel(self):
        """ Does it include the Nuitka patch, i.e. is a self-built one with it applied. """
        return self._getQtInformation().nuitka_patch_level

    def getQtPluginDirs(self):
        if self.qt_plugins_dirs is not None:
            return self.qt_plugins_dirs

        qt_info = self._getQtInformation()

        self.qt_plugins_dirs = qt_info.library_paths

        if not self.qt_plugins_dirs and os.path.exists(qt_info.guess_path1):
            self.qt_plugins_dirs.append(qt_info.guess_path1)

        if not self.qt_plugins_dirs and os.path.exists(qt_info.guess_path2):
            self.qt_plugins_dirs.append(qt_info.guess_path2)

        # Avoid duplicates.
        self.qt_plugins_dirs = tuple(sorted(set(self.qt_plugins_dirs)))

        if not self.qt_plugins_dirs:
            self.warning("Couldn't detect Qt plugin directories.")

        return self.qt_plugins_dirs

    def _getQtBinDirs(self):
        for plugin_dir in self.getQtPluginDirs():
            qt_bin_dir = os.path.normpath(os.path.join(plugin_dir, "..", "bin"))

            if os.path.isdir(qt_bin_dir):
                yield qt_bin_dir

    def hasPluginFamily(self, family):
        for plugin_dir in self.getQtPluginDirs():
            if os.path.isdir(os.path.join(plugin_dir, family)):
                return True

        # TODO: Special case "xml".
        return False

    def copyQmlFiles(self, full_name, target_plugin_dir):
        for plugin_dir in self.getQtPluginDirs():
            qml_plugin_dir = os.path.normpath(os.path.join(plugin_dir, "..", "qml"))

            if os.path.exists(qml_plugin_dir):
                break
        else:
            self.sysexit("Error, no such Qt plugin family: qml")

        qml_target_dir = os.path.normpath(self._getQmlTargetDir(target_plugin_dir))

        self.info("Copying Qt plug-ins 'qml' to '%s'." % (qml_target_dir))

        copyTree(qml_plugin_dir, qml_target_dir)

        # We try to filter here, not for DLLs.
        return [
            (
                filename,
                os.path.join(qml_target_dir, os.path.relpath(filename, qml_plugin_dir)),
                full_name,
            )
            for filename in getFileList(qml_plugin_dir)
            if not filename.endswith(
                (
                    ".qml",
                    ".qmlc",
                    ".qmltypes",
                    ".js",
                    ".jsc",
                    ".png",
                    ".ttf",
                    ".metainfo",
                )
            )
            if not os.path.isdir(filename)
            if not os.path.basename(filename) == "qmldir"
        ]

    def findDLLs(self, full_name, target_plugin_dir):
        # TODO: Change this to modern DLL entry points.
        return [
            (
                filename,
                os.path.join(target_plugin_dir, os.path.relpath(filename, plugin_dir)),
                full_name,
            )
            for plugin_dir in self.getQtPluginDirs()
            for filename in getFileList(plugin_dir)
            if not filename.endswith(".qml")
            if not filename.endswith(".mesh")
            if os.path.exists(
                os.path.join(target_plugin_dir, os.path.relpath(filename, plugin_dir))
            )
        ]

    def _getChildNamed(self, *child_names):
        for child_name in child_names:
            return ModuleName(self.binding_name).getChildNamed(child_name)

    def getImplicitImports(self, module):
        # Way too many indeed, pylint: disable=too-many-branches

        full_name = module.getFullName()
        top_level_package_name, child_name = full_name.splitPackageName()

        if top_level_package_name != self.binding_name:
            return

        # These are alternatives depending on PyQt5 version
        if child_name == "QtCore" and "PyQt" in self.binding_name:
            if python_version < 0x300:
                yield "atexit"

            yield "sip"
            yield self._getChildNamed("sip")

        if child_name in (
            "QtGui",
            "QtAssistant",
            "QtDBus",
            "QtDeclarative",
            "QtSql",
            "QtDesigner",
            "QtHelp",
            "QtNetwork",
            "QtScript",
            "QtQml",
            "QtGui",
            "QtScriptTools",
            "QtSvg",
            "QtTest",
            "QtWebKit",
            "QtOpenGL",
            "QtXml",
            "QtXmlPatterns",
            "QtPrintSupport",
            "QtNfc",
            "QtWebKitWidgets",
            "QtBluetooth",
            "QtMultimediaWidgets",
            "QtQuick",
            "QtWebChannel",
            "QtWebSockets",
            "QtX11Extras",
            "_QOpenGLFunctions_2_0",
            "_QOpenGLFunctions_2_1",
            "_QOpenGLFunctions_4_1_Core",
        ):
            yield self._getChildNamed("QtCore")

        if child_name in (
            "QtDeclarative",
            "QtWebKit",
            "QtXmlPatterns",
            "QtQml",
            "QtPrintSupport",
            "QtWebKitWidgets",
            "QtMultimedia",
            "QtMultimediaWidgets",
            "QtQuick",
            "QtQuickWidgets",
            "QtWebSockets",
            "QtWebEngineWidgets",
        ):
            yield self._getChildNamed("QtNetwork")

        if child_name == "QtWebEngineWidgets":
            yield self._getChildNamed("QtWebEngineCore")
            yield self._getChildNamed("QtWebChannel")
            yield self._getChildNamed("QtPrintSupport")
        elif child_name == "QtScriptTools":
            yield self._getChildNamed("QtScript")
        elif child_name in (
            "QtWidgets",
            "QtDeclarative",
            "QtDesigner",
            "QtHelp",
            "QtScriptTools",
            "QtSvg",
            "QtTest",
            "QtWebKit",
            "QtPrintSupport",
            "QtWebKitWidgets",
            "QtMultimedia",
            "QtMultimediaWidgets",
            "QtOpenGL",
            "QtQuick",
            "QtQuickWidgets",
            "QtSql",
            "_QOpenGLFunctions_2_0",
            "_QOpenGLFunctions_2_1",
            "_QOpenGLFunctions_4_1_Core",
        ):
            yield self._getChildNamed("QtGui")

        if child_name in (
            "QtDesigner",
            "QtHelp",
            "QtTest",
            "QtPrintSupport",
            "QtSvg",
            "QtOpenGL",
            "QtWebKitWidgets",
            "QtMultimediaWidgets",
            "QtQuickWidgets",
            "QtSql",
        ):
            yield self._getChildNamed("QtWidgets")

        if child_name in ("QtPrintSupport",):
            yield self._getChildNamed("QtSvg")

        if child_name in ("QtWebKitWidgets",):
            yield self._getChildNamed("QtWebKit")
            yield self._getChildNamed("QtPrintSupport")

        if child_name in ("QtMultimediaWidgets",):
            yield self._getChildNamed("QtMultimedia")

        if child_name in ("QtQuick", "QtQuickWidgets"):
            yield self._getChildNamed("QtQml")

        if child_name in ("QtQuickWidgets", "QtQml"):
            yield self._getChildNamed("QtQuick")

        if child_name == "Qt":
            yield self._getChildNamed("QtCore")
            yield self._getChildNamed("QtDBus")
            yield self._getChildNamed("QtGui")
            yield self._getChildNamed("QtNetwork")
            yield self._getChildNamed("QtNetworkAuth")
            yield self._getChildNamed("QtSensors")
            yield self._getChildNamed("QtSerialPort")
            yield self._getChildNamed("QtMultimedia")
            yield self._getChildNamed("QtQml")
            yield self._getChildNamed("QtWidgets")

        # TODO: Questionable if this still exists in newer PySide.
        if child_name == "QtUiTools":
            yield self._getChildNamed("QtGui")
            yield self._getChildNamed("QtXml")

        # TODO: Questionable if this still exists in newer PySide.
        if full_name == "phonon":
            yield self._getChildNamed("QtGui")

    def createPostModuleLoadCode(self, module):
        """Create code to load after a module was successfully imported.

        For Qt we need to set the library path to the distribution folder
        we are running from. The code is immediately run after the code
        and therefore makes sure it's updated properly.
        """

        # Only in standalone mode, this will be needed.
        if not Options.isStandaloneMode():
            return

        full_name = module.getFullName()

        if full_name == "%s.QtCore" % self.binding_name:
            code = """\
from __future__ import absolute_import

from %(package_name)s import QCoreApplication
import os

QCoreApplication.setLibraryPaths(
    [
        os.path.join(
           os.path.dirname(__file__),
           "qt-plugins"
        )
    ]
)
""" % {
                "package_name": full_name
            }

            return (
                code,
                """\
Setting Qt library path to distribution folder. We need to avoid loading target
system Qt plug-ins, which may be from another Qt version.""",
            )

    def createPreModuleLoadCode(self, module):
        """Method called when a module is being imported.

        Notes:
            If full name equals to the binding we insert code to include the dist
            folder in the 'PATH' environment variable (on Windows only).

        Args:
            module: the module object
        Returns:
            Code to insert and descriptive text (tuple), or (None, None).
        """

        # This isonly relevant on standalone mode for Windows
        if not isWin32Windows() or not Options.isStandaloneMode():
            return None

        full_name = module.getFullName()

        if full_name == self.binding_name:
            code = """import os
path = os.environ.get("PATH", "")
if not path.startswith(__nuitka_binary_dir):
    os.environ["PATH"] = __nuitka_binary_dir + ";" + path
"""
            return (
                code,
                "Adding binary folder to runtime 'PATH' environment variable for proper loading.",
            )

    def considerExtraDlls(self, dist_dir, module):
        # pylint: disable=too-many-branches,too-many-locals,too-many-statements
        full_name = module.getFullName()

        if full_name == self.binding_name:
            if not self.getQtPluginDirs():
                self.sysexit(
                    "Error, failed to detect %r plugin directories." % self.binding_name
                )

            target_plugin_dir = os.path.join(dist_dir, full_name.asPath(), "qt-plugins")

            self.info(
                "Copying Qt plug-ins '%s' to '%s'."
                % (
                    ",".join(
                        sorted(x for x in self.getQtPluginsSelected() if x != "xml")
                    ),
                    target_plugin_dir,
                )
            )

            # TODO: Change this to filtering copyTree while it's doing it.
            for plugin_dir in self.getQtPluginDirs():
                copyTree(plugin_dir, target_plugin_dir)

            if "all" not in self.getQtPluginsSelected():
                for plugin_candidate in getSubDirectories(target_plugin_dir):
                    if (
                        os.path.basename(plugin_candidate)
                        not in self.getQtPluginsSelected()
                    ):
                        removeDirectory(plugin_candidate, ignore_errors=False)

                for plugin_candidate in self.getQtPluginsSelected():
                    if plugin_candidate == "qml":
                        continue

                    if not os.path.isdir(
                        os.path.join(target_plugin_dir, plugin_candidate)
                    ):
                        self.sysexit(
                            "Error, no such Qt plugin family: %s" % plugin_candidate
                        )

            result = self.findDLLs(
                full_name=full_name,
                target_plugin_dir=target_plugin_dir,
            )

            if isWin32Windows():
                # Those 2 vars will be used later, just saving some resources
                # by caching the files list
                qt_bin_files = sum(
                    (getFileList(qt_bin_dir) for qt_bin_dir in self._getQtBinDirs()),
                    [],
                )

                self.info("Copying OpenSSL DLLs to %r." % dist_dir)

                for filename in qt_bin_files:
                    basename = os.path.basename(filename).lower()
                    if basename in ("libeay32.dll", "ssleay32.dll"):
                        shutil.copy(filename, os.path.join(dist_dir, basename))

            if (
                "qml" in self.getQtPluginsSelected()
                or "all" in self.getQtPluginsSelected()
            ):
                result += self.copyQmlFiles(
                    full_name=full_name,
                    target_plugin_dir=target_plugin_dir,
                )

                # Also copy required OpenGL DLLs on Windows
                if isWin32Windows():
                    opengl_dlls = ("libegl.dll", "libglesv2.dll", "opengl32sw.dll")

                    self.info("Copying OpenGL DLLs to %r." % dist_dir)

                    for filename in qt_bin_files:
                        basename = os.path.basename(filename).lower()

                        if basename in opengl_dlls or basename.startswith(
                            "d3dcompiler_"
                        ):
                            shutil.copy(filename, os.path.join(dist_dir, basename))

            return result
        elif full_name == self.binding_name + ".QtNetwork":
            if not isWin32Windows():
                dll_path = locateDLL("crypto")

                if dll_path is not None:
                    dist_dll_path = os.path.join(dist_dir, os.path.basename(dll_path))
                    shutil.copy(dll_path, dist_dll_path)

                dll_path = locateDLL("ssl")
                if dll_path is not None:
                    dist_dll_path = os.path.join(dist_dir, os.path.basename(dll_path))

                    shutil.copy(dll_path, dist_dll_path)
        elif (
            full_name
            in (
                self.binding_name + ".QtWebEngine",
                self.binding_name + ".QtWebEngineCore",
                self.binding_name + ".QtWebEngineWidgets",
            )
            and not self.webengine_done
        ):
            self.webengine_done = True  # prevent multiple copies
            self.info("Copying QtWebEngine components")

            plugin_parent = os.path.dirname(self.getQtPluginDirs()[0])

            if isWin32Windows():
                bin_dir = os.path.join(plugin_parent, "bin")
            else:  # TODO verify this for non-Windows!
                bin_dir = os.path.join(plugin_parent, "libexec")
            target_bin_dir = os.path.join(dist_dir)
            for f in os.listdir(bin_dir):
                if f.startswith("QtWebEngineProcess"):
                    shutil.copy(os.path.join(bin_dir, f), target_bin_dir)

            resources_dir = os.path.join(plugin_parent, "resources")
            target_resources_dir = os.path.join(dist_dir)
            for f in os.listdir(resources_dir):
                shutil.copy(os.path.join(resources_dir, f), target_resources_dir)

            translations_dir = os.path.join(plugin_parent, "translations")
            pos = len(translations_dir) + 1
            target_translations_dir = os.path.join(
                dist_dir,
                full_name.getTopLevelPackageName().asPath(),
                "Qt",
                "translations",
            )
            for f in getFileList(translations_dir):
                tar_f = os.path.join(target_translations_dir, f[pos:])
                makePath(os.path.dirname(tar_f))
                shutil.copyfile(f, tar_f)

        return ()

    def removeDllDependencies(self, dll_filename, dll_filenames):
        for value in self.getQtPluginDirs():
            # TODO: That is not a proper check if a file is below that.
            if dll_filename.startswith(value):
                for sub_dll_filename in dll_filenames:
                    for badword in (
                        "libKF5",
                        "libkfontinst",
                        "libkorganizer",
                        "libplasma",
                        "libakregator",
                        "libdolphin",
                        "libnoteshared",
                        "libknotes",
                        "libsystemsettings",
                        "libkerfuffle",
                        "libkaddressbook",
                        "libkworkspace",
                        "libkmail",
                        "libmilou",
                        "libtaskmanager",
                        "libkonsole",
                        "libgwenview",
                        "libweather_ion",
                    ):
                        if os.path.basename(sub_dll_filename).startswith(badword):
                            yield sub_dll_filename
Exemplo n.º 3
0
class NuitkaPluginQtBindingsPluginBase(NuitkaPluginBase):
    # For overload in the derived bindings plugin.
    binding_name = None

    def __init__(self, qt_plugins, no_qt_translations):
        self.qt_plugins = OrderedSet(x.strip().lower()
                                     for x in qt_plugins.split(","))
        self.no_qt_translations = no_qt_translations

        self.webengine_done_binaries = False
        self.webengine_done_data = False
        self.qt_plugins_dirs = None

        self.binding_package_name = ModuleName(self.binding_name)

        # Allow to specify none.
        if self.qt_plugins == set(["none"]):
            self.qt_plugins = set()

        # Prevent the list of binding names from being incomplete, it's used for conflicts.
        assert self.binding_name in _qt_binding_names, self.binding_name

        # Also lets have consistency in naming.
        assert self.plugin_name in getQtPluginNames()

        active_qt_plugin_name = getActiveQtPlugin()

        if active_qt_plugin_name is not None:
            self.sysexit(
                "Error, confliciting plugin '%s', you can only have one enabled."
                % active_qt_plugin_name)

        self.warned_about = set()

    @classmethod
    def addPluginCommandLineOptions(cls, group):
        group.add_option(
            "--include-qt-plugins",
            action="store",
            dest="qt_plugins",
            default="sensible",
            help="""\
Which Qt plugins to include. These can be big with dependencies, so
by default only the sensible ones are included, but you can also put
"all" or list them individually. If you specify something that does
not exist, a list of all available will be given.""",
        )

        group.add_option(
            "--noinclude-qt-translations",
            action="store",
            dest="no_qt_translations",
            default=False,
            help="""\
Include Qt translations with QtWebEngine if used. These can be a lot
of files that you may not want to be included.""",
        )

    def _getQmlTargetDir(self):
        """Where does the Qt bindings package expect the QML files."""
        return os.path.join(self.binding_name, "qml")

    def _getResourcesTargetDir(self):
        """Where does the Qt bindings package expect the resources files."""
        if isMacOS():
            return "Content/Resources"
        elif isWin32Windows():
            if self.binding_name in ("PySide2", "PyQt5"):
                return "resources"
            else:
                # While PyQt6/PySide6 complains about these, they are not working
                # return os.path.join(self.binding_name, "resources")
                return "."
        else:
            if self.binding_name in ("PySide2", "PySide6", "PyQt6"):
                return "."
            elif self.binding_name == "PyQt5":
                return "resources"
            else:
                assert False

    def _getTranslationsTargetDir(self):
        """Where does the Qt bindings package expect the translation files."""
        if isMacOS():
            return "Content/Resources"
        elif isWin32Windows():
            if self.binding_name in ("PySide2", "PyQt5"):
                return "translations"
            elif self.binding_name == "PyQt6":
                # TODO: PyQt6 is complaining about not being in "translations", but ignores it there.
                return "."
            else:
                return os.path.join(self.binding_name, "translations")
        else:
            if self.binding_name in ("PySide2", "PySide6", "PyQt6"):
                return "."
            elif self.binding_name == "PyQt5":
                return "translations"
            else:
                assert False

    @staticmethod
    def _getWebEngineTargetDir():
        """Where does the Qt bindings package expect the web process executable."""
        return "Helpers" if isMacOS() else "."

    def getQtPluginsSelected(self):
        # Resolve "sensible on first use"
        if "sensible" in self.qt_plugins:
            # Most used ones with low dependencies.
            self.qt_plugins.update(
                tuple(family for family in (
                    "imageformats",
                    "iconengines",
                    "mediaservice",
                    "printsupport",
                    "platforms",
                    "platformthemes",
                    "styles",
                    # Wayland on Linux needs these
                    "wayland-shell-integration",
                    "wayland-decoration-client",
                    "wayland-graphics-integration-client",
                    "egldeviceintegrations",
                    # OpenGL rendering, maybe should be something separate.
                    "xcbglintegrations",
                ) if self.hasPluginFamily(family)))

            self.qt_plugins.remove("sensible")

            # Make sure the above didn't detect nothing, which would be
            # indicating the check to be bad.
            assert self.qt_plugins

        return self.qt_plugins

    def hasQtPluginSelected(self, plugin_name):
        selected = self.getQtPluginsSelected()

        return "all" in selected or plugin_name in selected

    def _getQtInformation(self):
        # This is generic, and therefore needs to apply this to a lot of strings.
        def applyBindingName(template):
            return template % {"binding_name": self.binding_name}

        def getLocationQueryCode(path_name):
            if self.binding_name == "PyQt6":
                template = """\
%(binding_name)s.QtCore.QLibraryInfo.path(%(binding_name)s.QtCore.QLibraryInfo.LibraryPath.%(path_name)s)"""

            else:
                template = """\
%(binding_name)s.QtCore.QLibraryInfo.location(%(binding_name)s.QtCore.QLibraryInfo.%(path_name)s)"""

            return template % {
                "binding_name": self.binding_name,
                "path_name": path_name,
            }

        setup_codes = applyBindingName(r"""
import os
import %(binding_name)s.QtCore
""")

        info = self.queryRuntimeInformationMultiple(
            info_name=applyBindingName("%(binding_name)s_info"),
            setup_codes=setup_codes,
            values=(
                (
                    "library_paths",
                    applyBindingName(
                        "%(binding_name)s.QtCore.QCoreApplication.libraryPaths()"
                    ),
                ),
                (
                    "guess_path1",
                    applyBindingName(
                        "os.path.join(os.path.dirname(%(binding_name)s.__file__), 'plugins')"
                    ),
                ),
                (
                    "guess_path2",
                    applyBindingName(
                        "os.path.join(os.path.dirname(%(binding_name)s.__file__), '..', '..', '..', 'Library', 'plugins')"
                    ),
                ),
                (
                    "version",
                    applyBindingName(
                        "%(binding_name)s.__version_info__" if "PySide" in
                        self.binding_name else
                        "%(binding_name)s.QtCore.PYQT_VERSION_STR"),
                ),
                (
                    "nuitka_patch_level",
                    applyBindingName(
                        "getattr(%(binding_name)s, '_nuitka_patch_level', 0)"),
                ),
                ("translations_path",
                 getLocationQueryCode("TranslationsPath")),
                (
                    "library_executables_path",
                    getLocationQueryCode("LibraryExecutablesPath"),
                ),
                ("data_path", getLocationQueryCode("DataPath")),
            ),
        )

        if info is None:
            self.sysexit("Error, it seems '%s' is not installed." %
                         self.binding_name)

        return info

    def _getBindingVersion(self):
        """Get the version of the binding in tuple digit form, e.g. (6,0,3)"""
        return self._getQtInformation().version

    def _getNuitkaPatchLevel(self):
        """Does it include the Nuitka patch, i.e. is a self-built one with it applied."""
        return self._getQtInformation().nuitka_patch_level

    def _getTranslationsPath(self):
        """Get the path to the Qt translations."""
        return self._getQtInformation().translations_path

    def _getResourcesPath(self):
        """Get the path to the Qt webengine resources."""
        return os.path.join(self._getQtInformation().data_path, "resources")

    def _getLibraryExecutablePath(self):
        """Get the patch to Qt binaries."""
        return self._getQtInformation().library_executables_path

    def getQtPluginDirs(self):
        if self.qt_plugins_dirs is not None:
            return self.qt_plugins_dirs

        qt_info = self._getQtInformation()

        self.qt_plugins_dirs = qt_info.library_paths

        if not self.qt_plugins_dirs and os.path.exists(qt_info.guess_path1):
            self.qt_plugins_dirs.append(qt_info.guess_path1)

        if not self.qt_plugins_dirs and os.path.exists(qt_info.guess_path2):
            self.qt_plugins_dirs.append(qt_info.guess_path2)

        # Avoid duplicates.
        self.qt_plugins_dirs = [
            os.path.normpath(dirname) for dirname in self.qt_plugins_dirs
        ]
        self.qt_plugins_dirs = tuple(sorted(set(self.qt_plugins_dirs)))

        if not self.qt_plugins_dirs:
            self.warning("Couldn't detect Qt plugin directories.")

        return self.qt_plugins_dirs

    def _getQtBinDirs(self):
        for plugin_dir in self.getQtPluginDirs():
            if "PyQt" in self.binding_name:
                qt_bin_dir = os.path.normpath(
                    os.path.join(plugin_dir, "..", "bin"))

                if os.path.isdir(qt_bin_dir):
                    yield qt_bin_dir
            else:
                qt_bin_dir = os.path.normpath(os.path.join(plugin_dir, ".."))

                yield qt_bin_dir

    def hasPluginFamily(self, family):
        return any(
            os.path.isdir(os.path.join(plugin_dir, family))
            for plugin_dir in self.getQtPluginDirs())

    def _getQmlDirectory(self):
        for plugin_dir in self.getQtPluginDirs():
            qml_plugin_dir = os.path.normpath(
                os.path.join(plugin_dir, "..", "qml"))

            if os.path.exists(qml_plugin_dir):
                return qml_plugin_dir

        self.sysexit("Error, no such Qt plugin family: qml")

    def _getQmlFileList(self, dlls):
        qml_plugin_dir = self._getQmlDirectory()

        # List all file types of the QML plugin folder that are datafiles and not DLLs.
        datafile_suffixes = (
            ".qml",
            ".qmlc",
            ".qmltypes",
            ".js",
            ".jsc",
            ".png",
            ".ttf",
            ".metainfo",
            ".mesh",
            ".frag",
            "qmldir",
        )

        if dlls:
            ignore_suffixes = datafile_suffixes
            only_suffixes = ()
        else:
            ignore_suffixes = ()
            only_suffixes = datafile_suffixes

        return getFileList(
            qml_plugin_dir,
            ignore_suffixes=ignore_suffixes,
            only_suffixes=only_suffixes,
        )

    def _findQtPluginDLLs(self):
        for qt_plugins_dir in self.getQtPluginDirs():
            for filename in getFileList(qt_plugins_dir):
                filename_relative = os.path.relpath(filename,
                                                    start=qt_plugins_dir)

                qt_plugin_name = filename_relative.split(os.path.sep, 1)[0]

                if not self.hasQtPluginSelected(qt_plugin_name):
                    continue

                yield self.makeDllEntryPoint(
                    source_path=filename,
                    dest_path=os.path.join(
                        self.binding_name,
                        "qt-plugins",
                        filename_relative,
                    ),
                    package_name=self.binding_package_name,
                )

    def _getChildNamed(self, *child_names):
        for child_name in child_names:
            return ModuleName(self.binding_name).getChildNamed(child_name)

    def getImplicitImports(self, module):
        # Way too many indeed, pylint: disable=too-many-branches,too-many-statements

        full_name = module.getFullName()
        top_level_package_name, child_name = full_name.splitPackageName()

        if top_level_package_name != self.binding_name:
            return

        # These are alternatives depending on PyQt5 version
        if child_name == "QtCore" and "PyQt" in self.binding_name:
            if python_version < 0x300:
                yield "atexit"

            yield "sip"
            yield self._getChildNamed("sip")

        if child_name in (
                "QtGui",
                "QtAssistant",
                "QtDBus",
                "QtDeclarative",
                "QtSql",
                "QtDesigner",
                "QtHelp",
                "QtNetwork",
                "QtScript",
                "QtQml",
                "QtGui",
                "QtScriptTools",
                "QtSvg",
                "QtTest",
                "QtWebKit",
                "QtOpenGL",
                "QtXml",
                "QtXmlPatterns",
                "QtPrintSupport",
                "QtNfc",
                "QtWebKitWidgets",
                "QtBluetooth",
                "QtMultimediaWidgets",
                "QtQuick",
                "QtWebChannel",
                "QtWebSockets",
                "QtX11Extras",
                "_QOpenGLFunctions_2_0",
                "_QOpenGLFunctions_2_1",
                "_QOpenGLFunctions_4_1_Core",
        ):
            yield self._getChildNamed("QtCore")

        if child_name in (
                "QtDeclarative",
                "QtWebKit",
                "QtXmlPatterns",
                "QtQml",
                "QtPrintSupport",
                "QtWebKitWidgets",
                "QtMultimedia",
                "QtMultimediaWidgets",
                "QtQuick",
                "QtQuickWidgets",
                "QtWebSockets",
                "QtWebEngineWidgets",
        ):
            yield self._getChildNamed("QtNetwork")

        if child_name == "QtWebEngineWidgets":
            yield self._getChildNamed("QtWebEngineCore")
            yield self._getChildNamed("QtWebChannel")
            yield self._getChildNamed("QtPrintSupport")
        elif child_name == "QtScriptTools":
            yield self._getChildNamed("QtScript")
        elif child_name in (
                "QtWidgets",
                "QtDeclarative",
                "QtDesigner",
                "QtHelp",
                "QtScriptTools",
                "QtSvg",
                "QtTest",
                "QtWebKit",
                "QtPrintSupport",
                "QtWebKitWidgets",
                "QtMultimedia",
                "QtMultimediaWidgets",
                "QtOpenGL",
                "QtQuick",
                "QtQuickWidgets",
                "QtSql",
                "_QOpenGLFunctions_2_0",
                "_QOpenGLFunctions_2_1",
                "_QOpenGLFunctions_4_1_Core",
        ):
            yield self._getChildNamed("QtGui")

        if child_name in (
                "QtDesigner",
                "QtHelp",
                "QtTest",
                "QtPrintSupport",
                "QtSvg",
                "QtOpenGL",
                "QtWebKitWidgets",
                "QtMultimediaWidgets",
                "QtQuickWidgets",
                "QtSql",
        ):
            yield self._getChildNamed("QtWidgets")

        if child_name in ("QtPrintSupport", ):
            yield self._getChildNamed("QtSvg")

        if child_name in ("QtWebKitWidgets", ):
            yield self._getChildNamed("QtWebKit")
            yield self._getChildNamed("QtPrintSupport")

        if child_name in ("QtMultimediaWidgets", ):
            yield self._getChildNamed("QtMultimedia")

        if child_name in ("QtQuick", "QtQuickWidgets"):
            yield self._getChildNamed("QtQml")
            yield self._getChildNamed("QtOpenGL")

        if child_name in ("QtQuickWidgets", "QtQml", "QtQuickControls2"):
            yield self._getChildNamed("QtQuick")

        if child_name == "Qt":
            yield self._getChildNamed("QtCore")
            yield self._getChildNamed("QtDBus")
            yield self._getChildNamed("QtGui")
            yield self._getChildNamed("QtNetwork")
            yield self._getChildNamed("QtNetworkAuth")
            yield self._getChildNamed("QtSensors")
            yield self._getChildNamed("QtSerialPort")
            yield self._getChildNamed("QtMultimedia")
            yield self._getChildNamed("QtQml")
            yield self._getChildNamed("QtWidgets")

        # TODO: Questionable if this still exists in newer PySide.
        if child_name == "QtUiTools":
            yield self._getChildNamed("QtGui")
            yield self._getChildNamed("QtXml")

        # TODO: Questionable if this still exists in newer PySide.
        if full_name == "phonon":
            yield self._getChildNamed("QtGui")

    def createPostModuleLoadCode(self, module):
        """Create code to load after a module was successfully imported.

        For Qt we need to set the library path to the distribution folder
        we are running from. The code is immediately run after the code
        and therefore makes sure it's updated properly.
        """

        # Only in standalone mode, this will be needed.
        if not isStandaloneMode():
            return

        full_name = module.getFullName()

        if full_name == "%s.QtCore" % self.binding_name:
            code = """\
from __future__ import absolute_import

from %(package_name)s import QCoreApplication
import os

QCoreApplication.setLibraryPaths(
    [
        os.path.join(
           os.path.dirname(__file__),
           "qt-plugins"
        )
    ]
)

os.environ["QML2_IMPORT_PATH"] = os.path.join(
    os.path.dirname(__file__),
    "qml"
)

""" % {
                "package_name": full_name
            }

            yield (
                code,
                """\
Setting Qt library path to distribution folder. We need to avoid loading target
system Qt plugins, which may be from another Qt version.""",
            )

    def isQtWebEngineModule(self, full_name):
        return full_name in (
            self.binding_name + ".QtWebEngine",
            self.binding_name + ".QtWebEngineCore",
            self.binding_name + ".QtWebEngineWidgets",
        )

    def createPreModuleLoadCode(self, module):
        """Method called when a module is being imported.

        Notes:
            If full name equals to the binding we insert code to include the dist
            folder in the 'PATH' environment variable (on Windows only).

        Args:
            module: the module object
        Returns:
            Code to insert and descriptive text (tuple), or (None, None).
        """

        # This is only relevant on standalone mode for Windows
        if not isStandaloneMode():
            return

        full_name = module.getFullName()

        if full_name == self.binding_name and isWin32Windows():
            code = """import os
path = os.environ.get("PATH", "")
if not path.startswith(__nuitka_binary_dir):
    os.environ["PATH"] = __nuitka_binary_dir + ";" + path
"""
            yield (
                code,
                "Adding binary folder to runtime 'PATH' environment variable for proper Qt loading.",
            )

    def considerDataFiles(self, module):
        full_name = module.getFullName()

        if full_name == self.binding_name and (
                "qml" in self.getQtPluginsSelected()
                or "all" in self.getQtPluginsSelected()):
            qml_plugin_dir = self._getQmlDirectory()
            qml_target_dir = self._getQmlTargetDir()

            self.info("Including Qt plugins 'qml' below '%s'." %
                      qml_target_dir)

            for filename in self._getQmlFileList(dlls=False):
                filename_relative = os.path.relpath(filename, qml_plugin_dir)

                yield self.makeIncludedDataFile(
                    source_path=filename,
                    dest_path=os.path.join(
                        qml_target_dir,
                        filename_relative,
                    ),
                    reason="Qt QML datafile",
                    tags="qml",
                )
        elif self.isQtWebEngineModule(
                full_name) and not self.webengine_done_data:
            self.webengine_done_data = True

            # TODO: This is probably wrong/not needed on macOS
            if not isMacOS():
                yield self.makeIncludedGeneratedDataFile(
                    data="""\
[Paths]
Prefix = .
""",
                    dest_path="qt6.conf"
                    if "6" in self.binding_name else "qt.conf",
                    reason="QtWebEngine needs Qt configuration file",
                )

            resources_dir = self._getResourcesPath()

            for filename, filename_relative in listDir(resources_dir):
                yield self.makeIncludedDataFile(
                    source_path=filename,
                    dest_path=os.path.join(self._getResourcesTargetDir(),
                                           filename_relative),
                    reason="Qt resources",
                )

            if not self.no_qt_translations:
                translations_path = self._getTranslationsPath()

                for filename in getFileList(translations_path):
                    filename_relative = os.path.relpath(
                        filename, translations_path)
                    dest_path = self._getTranslationsTargetDir()

                    yield self.makeIncludedDataFile(
                        source_path=filename,
                        dest_path=os.path.join(dest_path, filename_relative),
                        reason="Qt translation",
                        tags="translation",
                    )

    def getExtraDlls(self, module):
        # pylint: disable=too-many-branches
        full_name = module.getFullName()

        if full_name == self.binding_name:
            if not self.getQtPluginDirs():
                self.sysexit(
                    "Error, failed to detect '%s' plugin directories." %
                    self.binding_name)

            target_plugin_dir = os.path.join(full_name.asPath(), "qt-plugins")

            self.info("Including Qt plugins '%s' below '%s'." % (
                ",".join(
                    sorted(
                        x for x in self.getQtPluginsSelected() if x != "xml")),
                target_plugin_dir,
            ))

            # TODO: Yielding a generator should become OK too.
            for r in self._findQtPluginDLLs():
                yield r

            if isWin32Windows():
                # Those 2 vars will be used later, just saving some resources
                # by caching the files list
                qt_bin_files = sum(
                    (getFileList(qt_bin_dir)
                     for qt_bin_dir in self._getQtBinDirs()),
                    [],
                )

                count = 0
                for filename in qt_bin_files:
                    basename = os.path.basename(filename).lower()
                    if basename in ("libeay32.dll", "ssleay32.dll"):
                        yield self.makeDllEntryPoint(
                            source_path=filename,
                            dest_path=basename,
                            package_name=full_name,
                        )

                        count += 1

                self.reportFileCount(full_name, count, section="OpenSSL")

            if ("qml" in self.getQtPluginsSelected()
                    or "all" in self.getQtPluginsSelected()):
                qml_plugin_dir = self._getQmlDirectory()
                qml_target_dir = self._getQmlTargetDir()

                for filename in self._getQmlFileList(dlls=True):
                    filename_relative = os.path.relpath(
                        filename, qml_plugin_dir)

                    yield self.makeDllEntryPoint(source_path=filename,
                                                 dest_path=os.path.join(
                                                     qml_target_dir,
                                                     filename_relative,
                                                 ),
                                                 package_name=full_name
                                                 # reason="Qt QML plugin DLL",
                                                 )

                # Also copy required OpenGL DLLs on Windows
                if isWin32Windows():
                    opengl_dlls = ("libegl.dll", "libglesv2.dll",
                                   "opengl32sw.dll")

                    count = 0
                    for filename in qt_bin_files:
                        basename = os.path.basename(filename).lower()

                        if basename in opengl_dlls or basename.startswith(
                                "d3dcompiler_"):
                            yield self.makeDllEntryPoint(
                                source_path=filename,
                                dest_path=basename,
                                package_name=full_name,
                            )

                    self.reportFileCount(full_name, count, section="OpenGL")

        elif full_name == self.binding_name + ".QtNetwork":
            if not isWin32Windows():
                dll_path = self.locateDLL("crypto")
                if dll_path is not None:
                    yield self.makeDllEntryPoint(
                        source_path=dll_path,
                        dest_path=os.path.basename(dll_path),
                        package_name=full_name,
                    )

                dll_path = self.locateDLL("ssl")
                if dll_path is not None:
                    yield self.makeDllEntryPoint(
                        source_path=dll_path,
                        dest_path=os.path.basename(dll_path),
                        package_name=full_name,
                    )
        elif self.isQtWebEngineModule(
                full_name) and not self.webengine_done_binaries:
            self.webengine_done_binaries = True  # prevent multiple copies
            self.info("Including QtWebEngine executable.")

            qt_web_engine_dir = self._getLibraryExecutablePath()

            for filename, filename_relative in listDir(qt_web_engine_dir):
                if filename_relative.startswith("QtWebEngineProcess"):
                    yield makeExeEntryPoint(
                        source_path=filename,
                        dest_path=os.path.join(self._getWebEngineTargetDir(),
                                               filename_relative),
                        package_name=full_name,
                    )

                    break
            else:
                self.sysexit(
                    "Error, cannot locate QtWebEngineProcess executable at '%s'."
                    % qt_web_engine_dir)

    def removeDllDependencies(self, dll_filename, dll_filenames):
        for value in self.getQtPluginDirs():
            # TODO: That is not a proper check if a file is below that.
            if dll_filename.startswith(value):
                for sub_dll_filename in dll_filenames:
                    for badword in (
                            "libKF5",
                            "libkfontinst",
                            "libkorganizer",
                            "libplasma",
                            "libakregator",
                            "libdolphin",
                            "libnoteshared",
                            "libknotes",
                            "libsystemsettings",
                            "libkerfuffle",
                            "libkaddressbook",
                            "libkworkspace",
                            "libkmail",
                            "libmilou",
                            "libtaskmanager",
                            "libkonsole",
                            "libgwenview",
                            "libweather_ion",
                    ):
                        if os.path.basename(sub_dll_filename).startswith(
                                badword):
                            yield sub_dll_filename

    def onModuleEncounter(self, module_filename, module_name, module_kind):
        top_package_name = module_name.getTopLevelPackageName()

        if isStandaloneMode():
            if (top_package_name in _qt_binding_names
                    and top_package_name != self.binding_name):
                if top_package_name not in self.warned_about:
                    self.info(
                        """\
Unwanted import of '%(unwanted)s' that conflicts with '%(binding_name)s' encountered, preventing
its use. As a result an "ImportError" might be given at run time. Uninstall it for full compatible
behavior with the uncompiled code to debug it.""" % {
                            "unwanted": top_package_name,
                            "binding_name": self.binding_name,
                        })

                    self.warned_about.add(top_package_name)

                return (
                    False,
                    "Not included due to potentially conflicting Qt versions with selected Qt binding '%s'."
                    % self.binding_name,
                )

    def onModuleCompleteSet(self, module_set):
        for module in module_set:
            module_name = module.getFullName()

            if module_name in _qt_binding_names and module_name != self.binding_name:
                self.warning("""\
Unwanted import of '%(unwanted)s' that conflicts with '%(binding_name)s' encountered. Use \
'--nofollow-import-to=%(unwanted)s' or uninstall it.""" % {
                    "unwanted": module_name,
                    "binding_name": self.binding_name
                })

            if module_name in _other_gui_binding_names:
                self.warning("""\
Unwanted import of '%(unwanted)s' that conflicts with '%(binding_name)s' encountered. Use \
'--nofollow-import-to=%(unwanted)s' or uninstall it.""" % {
                    "unwanted": module_name,
                    "binding_name": self.binding_name
                })

    def onModuleSourceCode(self, module_name, source_code):
        """Third party packages that make binding selections."""
        if module_name.hasNamespace("pyqtgraph"):
            # TODO: Add a mechanism to force all variable references of a name to something
            # during tree building, that would cover all uses in a nicer way.
            source_code = source_code.replace("{QT_LIB.lower()}",
                                              self.binding_name.lower())
            source_code = source_code.replace("QT_LIB.lower()",
                                              repr(self.binding_name.lower()))

        return source_code