| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- #-----------------------------------------------------------------------------
- # Copyright (c) 2013-2023, PyInstaller Development Team.
- #
- # Distributed under the terms of the GNU General Public License (version 2
- # or later) with exception for distributing the bootloader.
- #
- # The full license is in the file COPYING.txt, distributed with this software.
- #
- # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
- #-----------------------------------------------------------------------------
- """
- This module contains miscellaneous functions that do not fit anywhere else.
- """
- import glob
- import os
- import pprint
- import codecs
- import re
- import tokenize
- import io
- import pathlib
- from PyInstaller import log as logging
- from PyInstaller.compat import is_win
- logger = logging.getLogger(__name__)
- def dlls_in_subdirs(directory):
- """
- Returns a list *.dll, *.so, *.dylib in the given directory and its subdirectories.
- """
- filelist = []
- for root, dirs, files in os.walk(directory):
- filelist.extend(dlls_in_dir(root))
- return filelist
- def dlls_in_dir(directory):
- """
- Returns a list of *.dll, *.so, *.dylib in the given directory.
- """
- return files_in_dir(directory, ["*.so", "*.dll", "*.dylib"])
- def files_in_dir(directory, file_patterns=None):
- """
- Returns a list of files in the given directory that match the given pattern.
- """
- file_patterns = file_patterns or []
- files = []
- for file_pattern in file_patterns:
- files.extend(glob.glob(os.path.join(directory, file_pattern)))
- return files
- def get_path_to_toplevel_modules(filename):
- """
- Return the path to top-level directory that contains Python modules.
- It will look in parent directories for __init__.py files. The first parent directory without __init__.py is the
- top-level directory.
- Returned directory might be used to extend the PYTHONPATH.
- """
- curr_dir = os.path.dirname(os.path.abspath(filename))
- pattern = '__init__.py'
- # Try max. 10 levels up.
- try:
- for i in range(10):
- files = set(os.listdir(curr_dir))
- # 'curr_dir' is still not top-level; go to parent dir.
- if pattern in files:
- curr_dir = os.path.dirname(curr_dir)
- # Top-level dir found; return it.
- else:
- return curr_dir
- except IOError:
- pass
- # No top-level directory found, or error was encountered.
- return None
- def mtime(fnm):
- try:
- # TODO: explain why this does not use os.path.getmtime() ?
- # - It is probably not used because it returns float and not int.
- return os.stat(fnm)[8]
- except Exception:
- return 0
- def save_py_data_struct(filename, data):
- """
- Save data into text file as Python data structure.
- :param filename:
- :param data:
- :return:
- """
- dirname = os.path.dirname(filename)
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- with open(filename, 'w', encoding='utf-8') as f:
- pprint.pprint(data, f)
- def load_py_data_struct(filename):
- """
- Load data saved as python code and interpret that code.
- :param filename:
- :return:
- """
- with open(filename, 'r', encoding='utf-8') as f:
- if is_win:
- # import versioninfo so that VSVersionInfo can parse correctly.
- from PyInstaller.utils.win32 import versioninfo # noqa: F401
- return eval(f.read())
- def absnormpath(apath):
- return os.path.abspath(os.path.normpath(apath))
- def module_parent_packages(full_modname):
- """
- Return list of parent package names.
- 'aaa.bb.c.dddd' -> ['aaa', 'aaa.bb', 'aaa.bb.c']
- :param full_modname: Full name of a module.
- :return: List of parent module names.
- """
- prefix = ''
- parents = []
- # Ignore the last component in module name and get really just parent, grandparent, great grandparent, etc.
- for pkg in full_modname.split('.')[0:-1]:
- # Ensure that first item does not start with dot '.'
- prefix += '.' + pkg if prefix else pkg
- parents.append(prefix)
- return parents
- def is_file_qt_plugin(filename):
- """
- Check if the given file is a Qt plugin file.
- :param filename: Full path to file to check.
- :return: True if given file is a Qt plugin file, False if not.
- """
- # Check the file contents; scan for QTMETADATA string. The scan is based on the brute-force Windows codepath of
- # findPatternUnloaded() from qtbase/src/corelib/plugin/qlibrary.cpp in Qt5.
- with open(filename, 'rb') as fp:
- fp.seek(0, os.SEEK_END)
- end_pos = fp.tell()
- SEARCH_CHUNK_SIZE = 8192
- QTMETADATA_MAGIC = b'QTMETADATA '
- magic_offset = -1
- while end_pos >= len(QTMETADATA_MAGIC):
- start_pos = max(end_pos - SEARCH_CHUNK_SIZE, 0)
- chunk_size = end_pos - start_pos
- # Is the remaining chunk large enough to hold the pattern?
- if chunk_size < len(QTMETADATA_MAGIC):
- break
- # Read and scan the chunk
- fp.seek(start_pos, os.SEEK_SET)
- buf = fp.read(chunk_size)
- pos = buf.rfind(QTMETADATA_MAGIC)
- if pos != -1:
- magic_offset = start_pos + pos
- break
- # Adjust search location for next chunk; ensure proper overlap.
- end_pos = start_pos + len(QTMETADATA_MAGIC) - 1
- if magic_offset == -1:
- return False
- return True
- BOM_MARKERS_TO_DECODERS = {
- codecs.BOM_UTF32_LE: codecs.utf_32_le_decode,
- codecs.BOM_UTF32_BE: codecs.utf_32_be_decode,
- codecs.BOM_UTF32: codecs.utf_32_decode,
- codecs.BOM_UTF16_LE: codecs.utf_16_le_decode,
- codecs.BOM_UTF16_BE: codecs.utf_16_be_decode,
- codecs.BOM_UTF16: codecs.utf_16_decode,
- codecs.BOM_UTF8: codecs.utf_8_decode,
- }
- BOM_RE = re.compile(rb"\A(%s)?(.*)" % b"|".join(map(re.escape, BOM_MARKERS_TO_DECODERS)), re.DOTALL)
- def decode(raw: bytes):
- """
- Decode bytes to string, respecting and removing any byte-order marks if present, or respecting but not removing any
- PEP263 encoding comments (# encoding: cp1252).
- """
- bom, raw = BOM_RE.match(raw).groups()
- if bom:
- return BOM_MARKERS_TO_DECODERS[bom](raw)[0]
- encoding, _ = tokenize.detect_encoding(io.BytesIO(raw).readline)
- return raw.decode(encoding)
- def is_iterable(arg):
- """
- Check if the passed argument is an iterable."
- """
- try:
- iter(arg)
- except TypeError:
- return False
- return True
- def path_to_parent_archive(filename):
- """
- Check if the given file path points to a file inside an existing archive file. Returns first path from the set of
- parent paths that points to an existing file, or `None` if no such path exists (i.e., file is an actual stand-alone
- file).
- """
- for parent in pathlib.Path(filename).parents:
- if parent.is_file():
- return parent
- return None
|