def qt_plugins_dir(namespace): """ Return list of paths searched for plugins. :param namespace: Import namespace, i.e., PyQt5 or PySide2 :return: Plugin directory paths """ if namespace not in ['PyQt5', 'PySide2']: raise Exception('Invalid namespace: {0}'.format(namespace)) if namespace == 'PyQt5': paths = [pyqt5_library_info.location['PluginsPath']] elif namespace == 'PySide2': paths = [pyside2_library_info.location['PluginsPath']] else: paths = hooks.eval_statement(""" from {0}.QtCore import QCoreApplication; app = QCoreApplication([]); print(list(app.libraryPaths())) """.format(namespace)) if not paths: raise Exception('Cannot find {0} plugin directories'.format(namespace)) else: valid_paths = [] for path in paths: if os.path.isdir(path): valid_paths.append( str(path)) # must be 8-bit chars for one-file builds qt_plugin_paths = valid_paths if not qt_plugin_paths: raise Exception(""" Cannot find existing {0} plugin directories Paths checked: {1} """.format(namespace, ", ".join(paths))) return qt_plugin_paths
def get_matplotlib_backend_module_names(): """ List the names of all matplotlib backend modules importable under the current Python installation. Returns ---------- list List of the fully-qualified names of all such modules. """ # Statement safely importing a single backend module. import_statement = """ import os, sys # Preserve stdout. sys_stdout = sys.stdout try: # Redirect output printed by this importation to "/dev/null", preventing # such output from being erroneously interpreted as an error. with open(os.devnull, 'w') as dev_null: sys.stdout = dev_null __import__('%s') # If this is an ImportError, print this exception's message without a traceback. # ImportError messages are human-readable and require no additional context. except ImportError as exc: sys.stdout = sys_stdout print(exc) # Else, print this exception preceded by a traceback. traceback.print_exc() # prints to stderr rather than stdout and must not be called here! except Exception: sys.stdout = sys_stdout import traceback print(traceback.format_exc()) """ # List of the human-readable names of all available backends. backend_names = eval_statement( 'import matplotlib; print(matplotlib.rcsetup.all_backends)') # List of the fully-qualified names of all importable backend modules. module_names = [] # If the current system is not OS X and the "CocoaAgg" backend is available, # remove this backend from consideration. Attempting to import this backend # on non-OS X systems halts the current subprocess without printing output # or raising exceptions, preventing its reliable detection. if not is_darwin and 'CocoaAgg' in backend_names: backend_names.remove('CocoaAgg') # For safety, attempt to import each backend in a unique subprocess. for backend_name in backend_names: module_name = 'matplotlib.backends.backend_%s' % backend_name.lower() stdout = exec_statement(import_statement % module_name) # If no output was printed, this backend is importable. if not stdout: module_names.append(module_name) logger.info(' Matplotlib backend "%s": added' % backend_name) else: logger.info(' Matplotlib backend "%s": ignored\n %s' % (backend_name, stdout)) return module_names
def get_qt_network_ssl_binaries(qt_library_info): # No-op if requested Qt-based package is not available if qt_library_info.version is None: return [] # Applicable only to Windows if not compat.is_win: return [] # Check if QtNetwork supports SSL ssl_enabled = hooks.eval_statement(""" from {}.QtNetwork import QSslSocket print(QSslSocket.supportsSsl())""".format(qt_library_info.namespace)) if not ssl_enabled: return [] # PyPI version of PySide2 requires user to manually install SSL # libraries into the PrefixPath. Other versions (e.g., the one # provided by Conda) put the libraries into the BinariesPath. # PyQt5 also uses BinariesPath. # Accommodate both options by searching both locations... locations = (qt_library_info.location['BinariesPath'], qt_library_info.location['PrefixPath']) dll_names = ('libeay32.dll', 'ssleay32.dll', 'libssl-1_1-x64.dll', 'libcrypto-1_1-x64.dll') binaries = [] for location in locations: for dll in dll_names: dll_path = os.path.join(location, dll) if os.path.exists(dll_path): binaries.append((dll_path, '.')) return binaries
def pre_safe_import_module(api): """ Add the `six.moves` module as a dynamically defined runtime module node and all modules mapped by `six._SixMetaPathImporter` as aliased module nodes to the passed graph. The `six.moves` module is dynamically defined at runtime by the `six` module and hence cannot be imported in the standard way. Instead, this hook adds a placeholder node for the `six.moves` module to the graph, which implicitly adds an edge from that node to the node for its parent `six` module. This ensures that the `six` module will be frozen into the executable. (Phew!) `six._SixMetaPathImporter` is a PEP 302-compliant module importer converting imports independent of the current Python version into imports specific to that version (e.g., under Python 3, from `from six.moves import tkinter_tix` to `import tkinter.tix`). For each such mapping, this hook adds a corresponding module alias to the graph allowing PyInstaller to translate the former to the latter. """ # Dictionary from conventional module names to "six.moves" attribute names # (e.g., from `tkinter.tix` to `six.moves.tkinter_tix`). real_to_six_module_name = eval_statement( ''' import six print('{') # Iterate over the "six._moved_attributes" list rather than the # "six._importer.known_modules" dictionary, as "urllib"-specific moved modules # are overwritten in the latter with unhelpful "LazyModule" objects. for moved_module in six._moved_attributes: # If this is a moved module or attribute, map the corresponding module. In # the case of moved attributes, the attribute's module is mapped while the # attribute itself is mapped at runtime and hence ignored here. if isinstance(moved_module, (six.MovedModule, six.MovedAttribute)): print(' %r: %r,' % ( moved_module.mod, 'six.moves.' + moved_module.name)) print('}') ''') # Add "six.moves" as a runtime package rather than module. Modules cannot # physically contain submodules; only packages can. In "from"-style import # statements (e.g., "from six.moves import queue"), this implies that: # # * Attributes imported from customary modules are guaranteed *NOT* to be # submodules. Hence, ModuleGraph justifiably ignores these attributes. # While some attributes declared by "six.moves" are ignorable non-modules # (e.g., functions, classes), others are non-ignorable submodules that # must be imported. Adding "six.moves" as a runtime module causes # ModuleGraph to ignore these submodules, which defeats the entire point. # * Attributes imported from packages could be submodules. To disambiguate # non-ignorable submodules from ignorable non-submodules (e.g., classes, # variables), ModuleGraph first attempts to import these attributes as # submodules. This is exactly what we want. if isinstance(real_to_six_module_name, str): raise SystemExit("pre-safe-import-module hook failed, needs fixing.") api.add_runtime_package(api.module_name) for real_module_name, six_module_name in real_to_six_module_name.items(): api.add_alias_module(real_module_name, six_module_name)
def get_gi_typelibs(module, version): """ Return a tuple of (binaries, datas, hiddenimports) to be used by PyGObject related hooks. Searches for and adds dependencies recursively. :param module: GI module name, as passed to 'gi.require_version()' :param version: GI module version, as passed to 'gi.require_version()' """ datas = [] binaries = [] hiddenimports = [] statement = """ import gi gi.require_version("GIRepository", "2.0") from gi.repository import GIRepository repo = GIRepository.Repository.get_default() module, version = (%r, %r) repo.require(module, version, GIRepository.RepositoryLoadFlags.IREPOSITORY_LOAD_FLAG_LAZY) get_deps = getattr(repo, 'get_immediate_dependencies', None) if not get_deps: get_deps = repo.get_dependencies print({'sharedlib': repo.get_shared_library(module), 'typelib': repo.get_typelib_path(module), 'deps': get_deps(module) or []}) """ statement %= (module, version) typelibs_data = eval_statement(statement) if not typelibs_data: logger.error("gi repository 'GIRepository 2.0' not found. " "Please make sure libgirepository-gir2.0 resp. " "lib64girepository-gir2.0 is installed.") # :todo: should we raise a SystemError here? else: logger.debug("Adding files for %s %s", module, version) if typelibs_data['sharedlib']: for lib in typelibs_data['sharedlib'].split(','): path = findSystemLibrary(lib.strip()) if path: logger.debug('Found shared library %s at %s', lib, path) binaries.append((path, '.')) d = gir_library_path_fix(typelibs_data['typelib']) if d: logger.debug('Found gir typelib at %s', d) datas.append(d) hiddenimports += collect_submodules( 'gi.overrides', lambda name: name.endswith('.' + module)) # Load dependencies recursively for dep in typelibs_data['deps']: m, _ = dep.rsplit('-', 1) hiddenimports += ['gi.repository.%s' % m] return binaries, datas, hiddenimports
def pre_safe_import_module(api): """ Add the `six.moves` module as a dynamically defined runtime module node and all modules mapped by `six._SixMetaPathImporter` as aliased module nodes to the passed graph. The `six.moves` module is dynamically defined at runtime by the `six` module and hence cannot be imported in the standard way. Instead, this hook adds a placeholder node for the `six.moves` module to the graph, which implicitly adds an edge from that node to the node for its parent `six` module. This ensures that the `six` module will be frozen into the executable. (Phew!) `six._SixMetaPathImporter` is a PEP 302-compliant module importer converting imports independent of the current Python version into imports specific to that version (e.g., under Python 3, from `from six.moves import tkinter_tix` to `import tkinter.tix`). For each such mapping, this hook adds a corresponding module alias to the graph allowing PyInstaller to translate the former to the latter. """ # Dictionary from conventional module names to "six.moves" attribute names # (e.g., from `tkinter.tix` to `six.moves.tkinter_tix`). real_to_six_module_name = eval_statement( ''' import six print('{') # Iterate over the "six._moved_attributes" list rather than the # "six._importer.known_modules" dictionary, as "urllib"-specific moved modules # are overwritten in the latter with unhelpful "LazyModule" objects. for moved_module in six._moved_attributes: # If this is a moved module or attribute, map the corresponding module. In # the case of moved attributes, the attribute's module is mapped while the # attribute itself is mapped at runtime and hence ignored here. if isinstance(moved_module, (six.MovedModule, six.MovedAttribute)): print(' %r: %r,' % ( moved_module.mod, 'six.moves.' + moved_module.name)) print('}') ''') # Add "six.moves" as a runtime package rather than module. Modules cannot # physically contain submodules; only packages can. In "from"-style import # statements (e.g., "from six.moves import queue"), this implies that: # # * Attributes imported from customary modules are guaranteed *NOT* to be # submodules. Hence, ModuleGraph justifiably ignores these attributes. # While some attributes declared by "six.moves" are ignorable non-modules # (e.g., functions, classes), others are non-ignorable submodules that # must be imported. Adding "six.moves" as a runtime module causes # ModuleGraph to ignore these submodules, which defeats the entire point. # * Attributes imported from packages could be submodules. To disambiguate # non-ignorable submodules from ignorable non-submodules (e.g., classes, # variables), ModuleGraph first attempts to import these attributes as # submodules. This is exactly what we want. api.add_runtime_package(api.module_name) for real_module_name, six_module_name in real_to_six_module_name.items(): api.add_alias_module(real_module_name, six_module_name)
def get_glib_system_data_dirs(): statement = """ import gi gi.require_version('GLib', '2.0') from gi.repository import GLib print(GLib.get_system_data_dirs()) """ data_dirs = eval_statement(statement) if not data_dirs: logger.error( "gi repository 'GLib 2.0' not found. Please make sure corresponding package is installed." ) # :todo: should we raise a SystemError here? return data_dirs
def pre_safe_import_module(api): real_to_six_module_name = eval_statement(""" import urllib3.packages.six as six print('{') for moved in six._moved_attributes: if isinstance(moved, (six.MovedModule, six.MovedAttribute)): print(' %r: %r,' % (moved.mod, 'urllib3.packages.six.moves.' + moved.name)) print('}') """) if isinstance(real_to_six_module_name, str): raise SystemExit("pre-safe-import-module hook failed, needs fixing.") api.add_runtime_package(api.module_name) for real_module_name, six_module_name in real_to_six_module_name.items(): api.add_alias_module(real_module_name, six_module_name)
def pre_safe_import_module(api): real_to_six_module_name = eval_statement(''' import requests.packages.urllib3.packages.six as six print('{') for moved in six._moved_attributes: if isinstance(moved, (six.MovedModule, six.MovedAttribute)): print(' %r: %r,' % ( moved.mod, 'requests.packages.urllib3.packages.six.moves.' + moved.name)) print('}') ''') api.add_runtime_package(api.module_name) for real_module_name, six_module_name in real_to_six_module_name.items(): api.add_alias_module(real_module_name, six_module_name)
def qt4_plugins_dir_my(ns='PyQt4'): qt4_plugin_dirs = eval_statement( "from %s.QtCore import QCoreApplication;" "app=QCoreApplication([]);" # For Python 2 print would give "<PyQt4.QtCore.QStringList # object at 0x....>", so we need to convert each element separately "str=getattr(__builtins__, 'unicode', str);" # for Python 2 "print([str(p) for p in app.libraryPaths()])" % ns) if not qt4_plugin_dirs: print ('Cannot find %s plugin directories' % ns) return "" for d in qt4_plugin_dirs: if os.path.isdir(d): return str(d) # must be 8-bit chars for one-file builds print ('Cannot find existing %s plugin directory' % ns) return ""
def pre_safe_import_module(api): real_to_six_module_name = eval_statement( ''' import requests.packages.urllib3.packages.six as six print('{') for moved in six._moved_attributes: if isinstance(moved, (six.MovedModule, six.MovedAttribute)): print(' %r: %r,' % ( moved.mod, 'requests.packages.urllib3.packages.six.moves.' + moved.name)) print('}') ''') api.add_runtime_package(api.module_name) for real_module_name, six_module_name in real_to_six_module_name.items(): api.add_alias_module(real_module_name, six_module_name)
def qt5_plugins_dir_my(): if 'QT_PLUGIN_PATH' in os.environ and os.path.isdir(os.environ['QT_PLUGIN_PATH']): return str(os.environ['QT_PLUGIN_PATH']) qt5_plugin_dirs = eval_statement( "from PyQt5.QtCore import QCoreApplication;" "app=QCoreApplication([]);" # For Python 2 print would give "<PyQt4.QtCore.QStringList # object at 0x....>", so we need to convert each element separately "str=getattr(__builtins__, 'unicode', str);" # for Python 2 "print([str(p) for p in app.libraryPaths()])") if not qt5_plugin_dirs: print ("Cannot find PyQt5 plugin directories") return "" for d in qt5_plugin_dirs: if os.path.isdir(d): return str(d) # must be 8-bit chars for one-file builds print("Cannot find existing PyQt5 plugin directory") return ""
def pre_safe_import_module(api): """ Add the `six.moves` module as a dynamically defined runtime module node and all modules mapped by `six._SixMetaPathImporter` as aliased module nodes to the passed graph. The `six.moves` module is dynamically defined at runtime by the `six` module and hence cannot be imported in the standard way. Instead, this hook adds a placeholder node for the `six.moves` module to the graph, which implicitly adds an edge from that node to the node for its parent `six` module. This ensures that the `six` module will be frozen into the executable. (Phew!) `six._SixMetaPathImporter` is a PEP 302-compliant module importer converting imports independent of the current Python version into imports specific to that version (e.g., under Python 3, from `from six.moves import tkinter_tix` to `import tkinter.tix`). For each such mapping, this hook adds a corresponding module alias to the graph allowing PyInstaller to translate the former to the latter. """ # Dictionary from conventional module names to "six.moves" attribute names # (e.g., from `tkinter.tix` to `six.moves.tkinter_tix`). real_to_six_module_name = eval_statement( ''' import six print('{') # Iterate over the "six._moved_attributes" list rather than the # "six._importer.known_modules" dictionary, as "urllib"-specific moved modules # are overwritten in the latter with unhelpful "LazyModule" objects. for moved_module in six._moved_attributes: # If this is a moved module or attribute, map the corresponding module. In # the case of moved attributes, the attribute's module is mapped while the # attribute itself is mapped at runtime and hence ignored here. if isinstance(moved_module, (six.MovedModule, six.MovedAttribute)): print(' %r: %r,' % ( moved_module.mod, 'six.moves.' + moved_module.name)) print('}') ''') api.module_graph.add_module(RuntimeModule('six.moves')) for real_module_name, six_module_name in real_to_six_module_name.items(): api.module_graph.alias_module(real_module_name, six_module_name)
def pre_safe_import_module(api): """ Add the `six.moves` module as a dynamically defined runtime module node and all modules mapped by `six._SixMetaPathImporter` as aliased module nodes to the passed graph. The `six.moves` module is dynamically defined at runtime by the `six` module and hence cannot be imported in the standard way. Instead, this hook adds a placeholder node for the `six.moves` module to the graph, which implicitly adds an edge from that node to the node for its parent `six` module. This ensures that the `six` module will be frozen into the executable. (Phew!) `six._SixMetaPathImporter` is a PEP 302-compliant module importer converting imports independent of the current Python version into imports specific to that version (e.g., under Python 3, from `from six.moves import tkinter_tix` to `import tkinter.tix`). For each such mapping, this hook adds a corresponding module alias to the graph allowing PyInstaller to translate the former to the latter. """ # Dictionary from conventional module names to "six.moves" attribute names # (e.g., from `tkinter.tix` to `six.moves.tkinter_tix`). real_to_six_module_name = eval_statement( ''' import six print('{') # Iterate over the "six._moved_attributes" list rather than the # "six._importer.known_modules" dictionary, as "urllib"-specific moved modules # are overwritten in the latter with unhelpful "LazyModule" objects. for moved_module in six._moved_attributes: # If this is a moved module or attribute, map the corresponding module. In # the case of moved attributes, the attribute's module is mapped while the # attribute itself is mapped at runtime and hence ignored here. if isinstance(moved_module, (six.MovedModule, six.MovedAttribute)): print(' %r: %r,' % ( moved_module.mod, 'six.moves.' + moved_module.name)) print('}') ''') api.add_runtime_module(api.module_name) for real_module_name, six_module_name in real_to_six_module_name.items(): api.add_alias_module(real_module_name, six_module_name)
def get_glib_sysconf_dirs(): """Try to return the sysconf directories, eg /etc.""" if compat.is_win: # On windows, if you look at gtkwin32.c, sysconfdir is actually # relative to the location of the GTK DLL. Since that's what # we're actually interested in (not the user path), we have to # do that the hard way''' return [os.path.join(get_gi_libdir('GLib', '2.0'), 'etc')] statement = """ import gi gi.require_version('GLib', '2.0') from gi.repository import GLib print(GLib.get_system_config_dirs()) """ data_dirs = eval_statement(statement) if not data_dirs: logger.error("gi repository 'GIRepository 2.0' not found. " "Please make sure libgirepository-gir2.0 resp. " "lib64girepository-gir2.0 is installed.") # :todo: should we raise a SystemError here? return data_dirs
def pre_safe_import_module(api): real_to_six_module_name = eval_statement(''' try: import setuptools._vendor.six as six except ImportError: import setuptools.extern.six as six print('{') for moved in six._moved_attributes: if isinstance(moved, (six.MovedModule, six.MovedAttribute)): print(' %r: %r,' % ( moved.mod, 'setuptools.extern.six.moves.' + moved.name)) print('}') ''') if isinstance(real_to_six_module_name, str): raise SystemExit("pre-safe-import-module hook failed, needs fixing.") api.add_runtime_package(api.module_name) for real_module_name, six_module_name in real_to_six_module_name.items(): api.add_alias_module(real_module_name, six_module_name)
def get_glib_sysconf_dirs(): """ Try to return the sysconf directories (e.g., /etc). """ if compat.is_win: # On Windows, if you look at gtkwin32.c, sysconfdir is actually relative to the location of the GTK DLL. Since # that is what we are actually interested in (not the user path), we have to do that the hard way... return [os.path.join(get_gi_libdir('GLib', '2.0'), 'etc')] statement = """ import gi gi.require_version('GLib', '2.0') from gi.repository import GLib print(GLib.get_system_config_dirs()) """ data_dirs = eval_statement(statement) if not data_dirs: logger.error( "gi repository 'GLib 2.0' not found. Please make sure corresponding package is installed." ) # :todo: should we raise a SystemError here? return data_dirs
#----------------------------------------------------------------------------- # Copyright (c) 2013-2019, PyInstaller Development Team. # # Distributed under the terms of the GNU General Public License with exception # for distributing bootloader. # # The full license is in the file COPYING.txt, distributed with this software. #----------------------------------------------------------------------------- import os.path from PyInstaller.utils.hooks import add_qt5_dependencies, eval_statement from PyInstaller.compat import is_win from PyInstaller.depend.bindepend import getfullnameof hiddenimports, binaries, datas = add_qt5_dependencies(__file__) # Add libraries needed for SSL if these are available. See issue #3520, #4048. if (is_win and eval_statement(""" from PyQt5.QtNetwork import QSslSocket print(QSslSocket.supportsSsl())""")): rel_data_path = ['PyQt5', 'Qt', 'bin'] binaries += [ # Per http://doc.qt.io/qt-5/ssl.html#enabling-and-disabling-ssl-support, # the SSL libraries are dynamically loaded, implying they exist in # the system path. Include these. (getfullnameof('libeay32.dll'), os.path.join(*rel_data_path)), (getfullnameof('ssleay32.dll'), os.path.join(*rel_data_path)), ]
#----------------------------------------------------------------------------- # Copyright (c) 2013-2016, PyInstaller Development Team. # # Distributed under the terms of the GNU General Public License with exception # for distributing bootloader. # # The full license is in the file COPYING.txt, distributed with this software. #----------------------------------------------------------------------------- from PyInstaller.utils.hooks import eval_statement hiddenimports = [ "PyQt5.QtCore", "PyQt5.QtWidgets", "PyQt5.QtGui", "PyQt5.QtSvg" ] if eval_statement("from PyQt5 import Qwt5; print(hasattr(Qwt5, 'toNumpy'))"): hiddenimports.append("numpy") if eval_statement("from PyQt5 import Qwt5; print(hasattr(Qwt5, 'toNumeric'))"): hiddenimports.append("Numeric") if eval_statement( "from PyQt5 import Qwt5; print(hasattr(Qwt5, 'toNumarray'))"): hiddenimports.append("numarray")
# # languages = { # 'da': 'sphinx.search.da.SearchDanish', # 'de': 'sphinx.search.de.SearchGerman', # 'en': SearchEnglish, # # So, we need all the languages in "sphinx.search". collect_submodules('sphinx.search') + collect_submodules('sphinx.websupport.search') + collect_submodules('sphinx.domains') + # # From sphinx.cmdline line 173: # # locale = __import__('locale') # due to submodule of the same name # # Add this module. ['locale'] + # # Sphinx relies on a number of built-in extensions that are dynamically # imported. Collect all those. list(eval_statement(""" from sphinx.application import builtin_extensions print(builtin_extensions) """)) ) # Sphinx also relies on a number of data files in its directory hierarchy: for # example, *.html and *.conf files in ``sphinx.themes``, translation files in # ``sphinx.locale``, etc. datas = collect_data_files('sphinx') + collect_data_files('alabaster')
from PyInstaller.compat import is_darwin from PyInstaller.utils.hooks import (eval_statement, exec_statement, logger) logger.info('######removing unused mpl-backend ######') excl_backend_names = eval_statement( 'import matplotlib; print(matplotlib.rcsetup.all_backends)') excl_backend_names.remove('WX') excl_backend_names.remove('WXAgg') print("Entering custom hook for matplotlib-backends") excl_module_names = [] for backend_name in excl_backend_names: module_name = 'matplotlib.backends.backend_%s' % backend_name.lower() excl_module_names.append(module_name) logger.info('## Matplotlib backend "%s": removed' % module_name) logger.info('###### end mpl-backend removal #########') excludedimports = excl_module_names
#----------------------------------------------------------------------------- # Copyright (c) 2013-2017, PyInstaller Development Team. # # Distributed under the terms of the GNU General Public License with exception # for distributing bootloader. # # The full license is in the file COPYING.txt, distributed with this software. #----------------------------------------------------------------------------- from PyInstaller.utils.hooks import eval_statement hiddenimports = ['PySide2.QtCore', 'PySide2.QtWidgets', 'PySide2.QtGui', 'PySide2.QtSvg'] if eval_statement("from PySide2 import Qwt5; print(hasattr(Qwt5, 'toNumpy'))"): hiddenimports.append("numpy") if eval_statement("from PySide2 import Qwt5; print(hasattr(Qwt5, 'toNumeric'))"): hiddenimports.append("Numeric") if eval_statement("from PySide2 import Qwt5; print(hasattr(Qwt5, 'toNumarray'))"): hiddenimports.append("numarray")
# ------------------------------------------------------------------ # Copyright (c) 2021 PyInstaller Development Team. # # This file is distributed under the terms of the GNU General Public # License (version 2.0 or later). # # The full license is available in LICENSE.GPL.txt, distributed with # this software. # # SPDX-License-Identifier: GPL-2.0-or-later # ------------------------------------------------------------------ from PyInstaller.utils.hooks import eval_statement, collect_submodules hiddenimports = collect_submodules("ffpyplayer") binaries = [] # ffpyplayer has an internal variable tells us where the libraries it was using for bin in eval_statement("import ffpyplayer; print(ffpyplayer.dep_bins)"): binaries += [(bin, '.')]