예제 #1
0
파일: Plugins.py 프로젝트: psydox/Nuitka
    def getDataFileTags(included_datafile):
        tags = OrderedSet([included_datafile.kind])

        tags.update(Options.getDataFileTags(tags))

        for plugin in getActivePlugins():
            plugin.updateDataFileTags(included_datafile)

        return tags
예제 #2
0
파일: Options.py 프로젝트: psydox/Nuitka
def getDataFileTags(dest_path):
    result = OrderedSet()

    for value in options.data_file_tags:
        pattern, tags = value.rsplit(":", 1)

        if fnmatch.fnmatch(dest_path, pattern):
            result.update(tags.split(","))

    return result
예제 #3
0
def getPluginsEnabled():
    """*tuple*, user enabled (standard) plugins (not including user plugins)

    Note:
        Do not use this outside of main binary, as other plugins, e.g.
        hinted compilation will activate plugins themselves and this
        will not be visible here.
    """
    result = OrderedSet()

    if options:
        for plugin_enabled in options.plugins_enabled:
            result.update(plugin_enabled.split(","))

    return tuple(result)
예제 #4
0
def getPackageSpecificDLLDirectories(package_name):
    scan_dirs = OrderedSet()

    if package_name is not None:
        from nuitka.importing.Importing import findModule

        package_dir = findModule(None, package_name, None, 0, False)[1]

        if os.path.isdir(package_dir):
            scan_dirs.add(package_dir)
            scan_dirs.update(
                getSubDirectories(package_dir, ignore_dirs=("__pycache__", )))

        scan_dirs.update(Plugins.getModuleSpecificDllPaths(package_name))

    return scan_dirs
예제 #5
0
파일: Options.py 프로젝트: psydox/Nuitka
def getPluginsEnabled():
    """*tuple*, user enabled (standard) plugins (not including user plugins)

    Note:
        Do not use this outside of main binary, as plugins are allowed
        to activate plugins themselves and that will not be visible here.
    """
    result = OrderedSet()

    if options:
        for plugin_enabled in options.plugins_enabled:
            result.update(
                getPluginNameConsideringRenames(plugin_name)
                for plugin_name in plugin_enabled.split(","))

    return tuple(result)
예제 #6
0
파일: Standalone.py 프로젝트: psydox/Nuitka
def _getPackageSpecificDLLDirectories(package_name):
    scan_dirs = OrderedSet()

    if package_name is not None:
        package_dir = locateModule(module_name=package_name,
                                   parent_package=None,
                                   level=0)[1]

        if os.path.isdir(package_dir):
            scan_dirs.add(package_dir)
            scan_dirs.update(
                getSubDirectories(package_dir, ignore_dirs=("__pycache__", )))

        scan_dirs.update(Plugins.getModuleSpecificDllPaths(package_name))

    return scan_dirs
예제 #7
0
파일: Standalone.py 프로젝트: psydox/Nuitka
def _getLdLibraryPath(package_name, python_rpath, original_dir):
    key = package_name, python_rpath, original_dir

    if key not in _ld_library_cache:

        ld_library_path = OrderedSet()
        if python_rpath:
            ld_library_path.add(python_rpath)

        ld_library_path.update(_getPackageSpecificDLLDirectories(package_name))
        if original_dir is not None:
            ld_library_path.add(original_dir)

        _ld_library_cache[key] = ld_library_path

    return _ld_library_cache[key]
예제 #8
0
파일: Options.py 프로젝트: psydox/Nuitka
def getPluginsDisabled():
    """*tuple*, user disabled (standard) plugins.

    Note:
        Do not use this outside of main binary, as other plugins, e.g.
        hinted compilation will activate plugins themselves and this
        will not be visible here.
    """
    result = OrderedSet()

    if options:
        for plugin_disabled in options.plugins_disabled:
            result.update(
                getPluginNameConsideringRenames(plugin_name)
                for plugin_name in plugin_disabled.split(","))

    return tuple(result)
예제 #9
0
def _findPythons(python_version):
    result = OrderedSet()

    if python_version == python_version_str:
        result.add(
            InstalledPython(python_exe=sys.executable,
                            python_version=python_version))

    if isWin32Windows():
        result.update(
            InstalledPython(python_exe=python_exe,
                            python_version=python_version)
            for python_exe in _getPythonInstallPathsWindows(python_version))

    candidate = getExecutablePath("python" + python_version)
    if candidate is not None:
        result.add(
            InstalledPython(python_exe=candidate,
                            python_version=python_version))

    return result
예제 #10
0
def _detectBinaryPathDLLsPosix(dll_filename, package_name, original_dir):
    # This is complex, as it also includes the caching mechanism
    # pylint: disable=too-many-branches

    if ldd_result_cache.get(dll_filename):
        return ldd_result_cache[dll_filename]

    # Ask "ldd" about the libraries being used by the created binary, these
    # are the ones that interest us.
    result = set()

    # This is the rpath of the Python binary, which will be effective when
    # loading the other DLLs too. This happens at least for Python installs
    # on Travis. pylint: disable=global-statement
    global _detected_python_rpath
    if _detected_python_rpath is None and not Utils.isPosixWindows():
        _detected_python_rpath = getSharedLibraryRPATH(sys.executable) or False

        if _detected_python_rpath:
            _detected_python_rpath = _detected_python_rpath.replace(
                "$ORIGIN", os.path.dirname(sys.executable))

    ld_library_path = OrderedSet()
    if _detected_python_rpath:
        ld_library_path.add(_detected_python_rpath)
    ld_library_path.update(getPackageSpecificDLLDirectories(package_name))

    if original_dir is not None:
        ld_library_path.add(original_dir)
        # ld_library_path.update(getSubDirectories(original_dir, ignore_dirs=("__pycache__",)))

    with withEnvironmentPathAdded("LD_LIBRARY_PATH", *ld_library_path):
        process = subprocess.Popen(
            args=["ldd", dll_filename],
            stdin=getNullInput(),
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )

        stdout, stderr = process.communicate()

        stderr = b"\n".join(
            line for line in stderr.splitlines() if not line.startswith(
                b"ldd: warning: you do not have execution permission for"))

        inclusion_logger.debug("ldd output for %s is:\n%s" %
                               (dll_filename, stdout))

        if stderr:
            inclusion_logger.debug("ldd error for %s is:\n%s" %
                                   (dll_filename, stderr))

        for line in stdout.split(b"\n"):
            if not line:
                continue

            if b"=>" not in line:
                continue

            part = line.split(b" => ", 2)[1]

            if b"(" in part:
                filename = part[:part.rfind(b"(") - 1]
            else:
                filename = part

            if not filename:
                continue

            if python_version >= 0x300:
                filename = filename.decode("utf-8")

            # Sometimes might use stuff not found or supplied by ldd itself.
            if filename in ("not found", "ldd"):
                continue

            # Do not include kernel / glibc specific libraries. This list has been
            # assembled by looking what are the most common .so files provided by
            # glibc packages from ArchLinux, Debian Stretch and CentOS.
            #
            # Online sources:
            #  - https://centos.pkgs.org/7/puias-computational-x86_64/glibc-aarch64-linux-gnu-2.24-2.sdl7.2.noarch.rpm.html
            #  - https://centos.pkgs.org/7/centos-x86_64/glibc-2.17-222.el7.x86_64.rpm.html
            #  - https://archlinux.pkgs.org/rolling/archlinux-core-x86_64/glibc-2.28-5-x86_64.pkg.tar.xz.html
            #  - https://packages.debian.org/stretch/amd64/libc6/filelist
            #
            # Note: This list may still be incomplete. Some additional libraries
            # might be provided by glibc - it may vary between the package versions
            # and between Linux distros. It might or might not be a problem in the
            # future, but it should be enough for now.
            if os.path.basename(filename).startswith((
                    "ld-linux-x86-64.so",
                    "libc.so.",
                    "libpthread.so.",
                    "libm.so.",
                    "libdl.so.",
                    "libBrokenLocale.so.",
                    "libSegFault.so",
                    "libanl.so.",
                    "libcidn.so.",
                    "libcrypt.so.",
                    "libmemusage.so",
                    "libmvec.so.",
                    "libnsl.so.",
                    "libnss_compat.so.",
                    "libnss_db.so.",
                    "libnss_dns.so.",
                    "libnss_files.so.",
                    "libnss_hesiod.so.",
                    "libnss_nis.so.",
                    "libnss_nisplus.so.",
                    "libpcprofile.so",
                    "libresolv.so.",
                    "librt.so.",
                    "libthread_db-1.0.so",
                    "libthread_db.so.",
                    "libutil.so.",
            )):
                continue

            result.add(filename)

    ldd_result_cache[dll_filename] = result

    sub_result = set(result)

    for sub_dll_filename in result:
        sub_result = sub_result.union(
            _detectBinaryPathDLLsPosix(
                dll_filename=sub_dll_filename,
                package_name=package_name,
                original_dir=original_dir,
            ))

    return sub_result
예제 #11
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
예제 #12
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
예제 #13
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
예제 #14
0
파일: Plugins.py 프로젝트: psydox/Nuitka
    def onModuleDiscovered(cls, module):
        # We offer plugins many ways to provide extra stuff
        # pylint: disable=too-many-branches,too-many-locals,too-many-statements

        full_name = module.getFullName()

        def _untangleLoadDesc(descs):
            if descs and inspect.isgenerator(descs):
                descs = tuple(descs)

            if descs:
                if type(descs[0]) not in (tuple, list):
                    descs = [descs]

                for desc in descs:
                    if len(desc) == 2:
                        code, reason = desc
                        flags = ()
                    else:
                        code, reason, flags = desc
                        if type(flags) is str:
                            flags = (flags, )

                    yield plugin, code, reason, flags

        def _untangleFakeDesc(descs):
            if descs and inspect.isgenerator(descs):
                descs = tuple(descs)

            if descs:
                if type(descs[0]) not in (tuple, list):
                    descs = [descs]

                for desc in descs:
                    assert len(desc) == 4, desc
                    yield plugin, desc[0], desc[1], desc[2], desc[3]

        preload_descs = []
        postload_descs = []
        fake_descs = []

        for plugin in getActivePlugins():
            plugin.onModuleDiscovered(module)

            preload_descs.extend(
                _untangleLoadDesc(
                    descs=plugin.createPreModuleLoadCode(module)))
            postload_descs.extend(
                _untangleLoadDesc(
                    descs=plugin.createPostModuleLoadCode(module)))
            fake_descs.extend(
                _untangleFakeDesc(
                    descs=plugin.createFakeModuleDependency(module)))

        if preload_descs:
            total_code = []
            total_flags = OrderedSet()

            for plugin, pre_code, reason, flags in preload_descs:
                if pre_code:
                    plugin.info(
                        "Injecting pre-module load code for module '%s':" %
                        full_name)
                    for line in reason.split("\n"):
                        plugin.info("    " + line)

                    total_code.append(pre_code)
                    total_flags.update(flags)

            if total_code:
                assert full_name not in pre_modules

                pre_modules[full_name] = cls._createTriggerLoadedModule(
                    module=module,
                    trigger_name=preload_trigger_name,
                    code="\n\n".join(total_code),
                    flags=total_flags,
                )

        if postload_descs:
            total_code = []
            total_flags = OrderedSet()

            for plugin, post_code, reason, flags in postload_descs:
                if post_code:
                    plugin.info(
                        "Injecting post-module load code for module '%s':" %
                        full_name)
                    for line in reason.split("\n"):
                        plugin.info("    " + line)

                    total_code.append(post_code)
                    total_flags.update(flags)

            if total_code:
                assert full_name not in post_modules

                post_modules[full_name] = cls._createTriggerLoadedModule(
                    module=module,
                    trigger_name=postload_trigger_name,
                    code="\n\n".join(total_code),
                    flags=total_flags,
                )

        if fake_descs:
            fake_modules[full_name] = []

            from nuitka.tree.Building import buildModule

            for (
                    plugin,
                    fake_module_name,
                    source_code,
                    fake_filename,
                    reason,
            ) in fake_descs:
                fake_module, _added = buildModule(
                    module_filename=fake_filename,
                    module_name=fake_module_name,
                    source_code=source_code,
                    is_top=False,
                    is_main=False,
                    is_extension=False,
                    is_fake=fake_module_name,
                    hide_syntax_error=False,
                )

                if fake_module.getCompilationMode() == "bytecode":
                    fake_module.setSourceCode(source_code)

                fake_modules[full_name].append((fake_module, plugin, reason))