Skip to content

Commit

Permalink
building: automatically exclude Qt plugins from upx processing
Browse files Browse the repository at this point in the history
UPX is known to "corrupt" Qt plugins (it removes the .qtmetad
section and QTMETADATA magic string - see upx/upx#107).

Instead of requiring users to manually exclude Qt plugins from UPX
processing, do it automatically based on the file analysis, i.e.,
full back-to-front scan for the QTMETADATA magic string.

Fixes pyinstaller#4178.
  • Loading branch information
rokm committed Jun 2, 2021
1 parent 660a2e3 commit c409987
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 0 deletions.
3 changes: 3 additions & 0 deletions PyInstaller/building/utils.py
Expand Up @@ -275,6 +275,9 @@ def checkCache(fnm, strip=False, upx=False, upx_exclude=None, dist_nm=None,
# Flow Guard enabled, as it breaks them.
if is_win and versioninfo.pefile_check_control_flow_guard(fnm):
logger.info('Disabling UPX for %s due to CFG!', fnm)
elif misc.is_file_qt_plugin(fnm):
logger.info('Disabling UPX for %s due to it being a Qt plugin!',
fnm)
else:
bestopt = "--best"
# FIXME: Linux builds of UPX do not seem to contain LZMA
Expand Down
41 changes: 41 additions & 0 deletions PyInstaller/utils/misc.py
Expand Up @@ -251,3 +251,44 @@ def module_parent_packages(full_modname):
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
1 change: 1 addition & 0 deletions news/4178.feature.rst
@@ -0,0 +1 @@
Automatically exclude Qt plugins from UPX processing.

0 comments on commit c409987

Please sign in to comment.