def getDataFileTags(included_datafile): tags = OrderedSet([included_datafile.kind]) tags.update(Options.getDataFileTags(tags)) for plugin in getActivePlugins(): plugin.updateDataFileTags(included_datafile) return tags
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
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)
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
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)
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
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]
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)
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
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
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
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
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
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))