Skip to content

Commit

Permalink
building: MERGE: fix symlink bookkeeping
Browse files Browse the repository at this point in the history
Implement separate bookkeeping for SYMLINK entries in the MERGE
processing. With symlinks, we must track `(dest_path, src_path)`
pairs instead of just `src_path`, because the `src_path` encodes
the relative link destination, and might appear multiple times
in different context. For example, on macOS, each Qt .framework
bundle has a symlink `Current -> A`.

So if the bookkeeping is done only based on `src_path`, only one
.framework bundle in first `Analysis` (where these bundles occur)
retains its symlink in the `binaries`/`datas` TOC, while the rest
are moved into `dependencies` TOC. This means that such executable
unncessarily gains onefile copy/extraction semantics if
`dependencies` are properly set to `EXE` (if it was originally
a onedir executable); on the other hand, if `dependencies` are not
passed on to `EXE` (mis-use of `MERGE`), the symlinks would end up
missing in the otherwise unchanged executable.
  • Loading branch information
rokm committed Nov 25, 2023
1 parent 75ca04f commit b2b188b
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 7 deletions.
27 changes: 20 additions & 7 deletions PyInstaller/building/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,7 @@ def __init__(self, *args):
directory name and executable name (e.g., `myapp/myexecutable`).
"""
self._dependencies = {}
self._symlinks = set()

# Process all given (analysis, identifier, path_to_exe) tuples
for analysis, identifier, path_to_exe in args:
Expand Down Expand Up @@ -1208,19 +1209,31 @@ def _process_toc(self, toc, path_to_exe):
toc_refs = []
for entry in toc:
dest_name, src_name, typecode = entry

# Special handling and bookkeeping for symbolic links. We need to account both for dest_name and src_name,
# because src_name might be the same in different contexts. For example, when collecting Qt .framework
# bundles on macOS, there are multiple relative symbolic links `Current -> A` (one in each .framework).
if typecode == 'SYMLINK':
key = dest_name, src_name
if key not in self._symlinks:
# First occurrence; keep the entry in "for-keep" TOC, same as we would for binaries and datas.
logger.debug("Keeping symbolic link %r entry in original TOC.", entry)
self._symlinks.add(key)
toc_keep.append(entry)
else:
# Subsequent occurrence; keep the SYMLINK entry intact, but add it to the references TOC instead of
# "for-keep" TOC, so it ends up in `a.dependencies`.
logger.debug("Moving symbolic link %r entry to references TOC.", entry)
toc_refs.append(entry)
del key # Block-local variable
continue

if src_name not in self._dependencies:
logger.debug("Adding dependency %s located in %s", src_name, path_to_exe)
self._dependencies[src_name] = path_to_exe
# Add entry to list of kept TOC entries
toc_keep.append(entry)
else:
# Keep SYMLINK entries intact (but add them to the references TOC instead of "for-keep" TOC, so they
# end up in `a.dependencies`).
if typecode == 'SYMLINK':
logger.debug("Referenced dependency %s is symbolic link; keeping it intact!", src_name)
toc_refs.append(entry)
continue

# Construct relative dependency path; i.e., the relative path from this executable (or rather, its
# parent directory) to the executable that contains the dependency.
dep_path = os.path.relpath(self._dependencies[src_name], os.path.dirname(path_to_exe))
Expand Down
4 changes: 4 additions & 0 deletions news/8124.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix symbolic link tracking in ``MERGE`` processing, so that distinct
symbolic links with same relative target (e.g. ``Current -> A``
symbolic links in Qt .framework bundles collected on macOS) are properly
processed, and kept in the original TOC upon their first occurrence.

0 comments on commit b2b188b

Please sign in to comment.