def test_ctypes_util_find_library_as_default_argument(): # Test-case for fix: # commit 55b542f135340c612a861cfcce0f86c4e5a968df # Author: Hartmut Goebel <*****@*****.**> # Date: Thu Nov 19 14:45:30 2015 +0100 code = """ def locate_library(loader=ctypes.util.find_library): pass """ code = textwrap.dedent(code) co = compile(code, '<ctypes_util_find_library_as_default_argument>', 'exec') utils.scan_code_for_ctypes(co)
def __scan_code_for_ctypes(code, monkeypatch): # _resolveCtypesImports would filter our some of our names monkeypatch.setattr(utils, '_resolveCtypesImports', lambda cbinaries: cbinaries) code = textwrap.dedent(code) co = compile(code, 'dummy', 'exec') #import pdb ; pdb.set_trace() return utils.scan_code_for_ctypes(co)
def assemble(self): """ This method is the MAIN method for finding all necessary files to be bundled. """ from PyInstaller.config import CONF for m in self.excludes: logger.debug("Excluding module '%s'" % m) self.graph = initialize_modgraph(excludes=self.excludes, user_hook_dirs=self.hookspath) # TODO Find a better place where to put 'base_library.zip' and when to created it. # For Python 3 it is necessary to create file 'base_library.zip' # containing core Python modules. In Python 3 some built-in modules # are written in pure Python. base_library.zip is a way how to have # those modules as "built-in". libzip_filename = os.path.join(CONF['workpath'], 'base_library.zip') create_py3_base_library(libzip_filename, graph=self.graph) # Bundle base_library.zip as data file. # Data format of TOC item: ('relative_path_in_dist_dir', 'absolute_path_on_disk', 'DATA') self.datas.append( (os.path.basename(libzip_filename), libzip_filename, 'DATA')) # Expand sys.path of module graph. # The attribute is the set of paths to use for imports: sys.path, # plus our loader, plus other paths from e.g. --path option). self.graph.path = self.pathex + self.graph.path self.graph.set_setuptools_nspackages() logger.info("running Analysis %s", self.tocbasename) # Get paths to Python and, in Windows, the manifest. python = compat.python_executable if not is_win: # Linux/MacOS: get a real, non-link path to the running Python executable. while os.path.islink(python): python = os.path.join(os.path.dirname(python), os.readlink(python)) depmanifest = None else: # Windows: Create a manifest to embed into built .exe, containing the same # dependencies as python.exe. depmanifest = winmanifest.Manifest( type_="win32", name=CONF['specnm'], processorArchitecture=winmanifest.processor_architecture(), version=(1, 0, 0, 0)) depmanifest.filename = os.path.join( CONF['workpath'], CONF['specnm'] + ".exe.manifest") # We record "binaries" separately from the modulegraph, as there # is no way to record those dependencies in the graph. These include # the python executable and any binaries added by hooks later. # "binaries" are not the same as "extensions" which are .so or .dylib # that are found and recorded as extension nodes in the graph. # Reset seen variable before running bindepend. We use bindepend only for # the python executable. bindepend.seen.clear() # Add binary and assembly dependencies of Python.exe. # This also ensures that its assembly depencies under Windows get added to the # built .exe's manifest. Python 2.7 extension modules have no assembly # dependencies, and rely on the app-global dependencies set by the .exe. self.binaries.extend( bindepend.Dependencies([('', python, '')], manifest=depmanifest, redirects=self.binding_redirects)[1:]) if is_win: depmanifest.writeprettyxml() ### Module graph. # # Construct the module graph of import relationships between modules # required by this user's application. For each entry point (top-level # user-defined Python script), all imports originating from this entry # point are recursively parsed into a subgraph of the module graph. This # subgraph is then connected to this graph's root node, ensuring # imported module nodes will be reachable from the root node -- which is # is (arbitrarily) chosen to be the first entry point's node. # List to hold graph nodes of scripts and runtime hooks in use order. priority_scripts = [] # Assume that if the script does not exist, Modulegraph will raise error. # Save the graph nodes of each in sequence. for script in self.inputs: logger.info("Analyzing %s", script) priority_scripts.append(self.graph.add_script(script)) # Analyze the script's hidden imports (named on the command line) self.graph.add_hiddenimports(self.hiddenimports) ### Post-graph hooks. self.graph.process_post_graph_hooks(self) # Update 'binaries' TOC and 'datas' TOC. deps_proc = DependencyProcessor(self.graph, self.graph._additional_files_cache) self.binaries.extend(deps_proc.make_binaries_toc()) self.datas.extend(deps_proc.make_datas_toc()) self.zipped_data.extend(deps_proc.make_zipped_data_toc()) # Note: zipped eggs are collected below ### Look for dlls that are imported by Python 'ctypes' module. # First get code objects of all modules that import 'ctypes'. logger.info('Looking for ctypes DLLs') # dict like: {'module1': code_obj, 'module2': code_obj} ctypes_code_objs = self.graph.get_code_using("ctypes") for name, co in ctypes_code_objs.items(): # Get dlls that might be needed by ctypes. logger.debug('Scanning %s for shared libraries or dlls', name) try: ctypes_binaries = scan_code_for_ctypes(co) self.binaries.extend(set(ctypes_binaries)) except Exception as ex: raise RuntimeError(f"Failed to scan the module '{name}'. " f"This is a bug. Please report it.") from ex self.datas.extend((dest, source, "DATA") for (dest, source) in format_binaries_and_datas( self.graph.metadata_required())) # Analyze run-time hooks. # Run-time hooks has to be executed before user scripts. Add them # to the beginning of 'priority_scripts'. priority_scripts = self.graph.analyze_runtime_hooks( self.custom_runtime_hooks) + priority_scripts # 'priority_scripts' is now a list of the graph nodes of custom runtime # hooks, then regular runtime hooks, then the PyI loader scripts. # Further on, we will make sure they end up at the front of self.scripts ### Extract the nodes of the graph as TOCs for further processing. # Initialize the scripts list with priority scripts in the proper order. self.scripts = self.graph.nodes_to_toc(priority_scripts) # Extend the binaries list with all the Extensions modulegraph has found. self.binaries = self.graph.make_binaries_toc(self.binaries) # Fill the "pure" list with pure Python modules. assert len(self.pure) == 0 self.pure = self.graph.make_pure_toc() # And get references to module code objects constructed by ModuleGraph # to avoid writing .pyc/pyo files to hdd. self.pure._code_cache = self.graph.get_code_objects() # Add remaining binary dependencies - analyze Python C-extensions and what # DLLs they depend on. logger.info('Looking for dynamic libraries') self.binaries.extend( bindepend.Dependencies(self.binaries, redirects=self.binding_redirects)) ### Include zipped Python eggs. logger.info('Looking for eggs') self.zipfiles.extend(deps_proc.make_zipfiles_toc()) # Verify that Python dynamic library can be found. # Without dynamic Python library PyInstaller cannot continue. self._check_python_library(self.binaries) if is_win: # Remove duplicate redirects self.binding_redirects[:] = list(set(self.binding_redirects)) logger.info("Found binding redirects: \n%s", self.binding_redirects) # Filter binaries to adjust path of extensions that come from # python's lib-dynload directory. Prefix them with lib-dynload # so that we'll collect them into subdirectory instead of # directly into _MEIPASS for idx, tpl in enumerate(self.binaries): name, path, typecode = tpl if typecode == 'EXTENSION' \ and not os.path.dirname(os.path.normpath(name)) \ and os.path.basename(os.path.dirname(path)) == 'lib-dynload': name = os.path.join('lib-dynload', name) self.binaries[idx] = (name, path, typecode) # Place Python source in data files for the noarchive case. if self.noarchive: # Create a new TOC of ``(dest path for .pyc, source for .py, type)``. new_toc = TOC() for name, path, typecode in self.pure: assert typecode == 'PYMODULE' # Transform a python module name into a file name. name = name.replace('.', os.sep) # Special case: modules have an implied filename to add. if os.path.splitext(os.path.basename(path))[0] == '__init__': name += os.sep + '__init__' # Append the extension for the compiled result. # In python 3.5 (PEP-488) .pyo files were replaced by # .opt-1.pyc and .opt-2.pyc. However, it seems that for # bytecode-only module distribution, we always need to # use the .pyc extension. name += '.pyc' new_toc.append((name, path, typecode)) # Put the result of byte-compiling this TOC in datas. Mark all entries as data. for name, path, typecode in compile_py_files( new_toc, CONF['workpath']): self.datas.append((name, path, 'DATA')) # Store no source in the archive. self.pure = TOC() # Write warnings about missing modules. self._write_warnings() # Write debug information about hte graph self._write_graph_debug()