mit neuen venv und exe-Files
This commit is contained in:
719
venv3_12/Lib/site-packages/cx_Freeze/darwintools.py
Normal file
719
venv3_12/Lib/site-packages/cx_Freeze/darwintools.py
Normal file
@@ -0,0 +1,719 @@
|
||||
# ruff: noqa
|
||||
from __future__ import annotations
|
||||
import sysconfig
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
from tempfile import TemporaryDirectory
|
||||
from collections.abc import Iterable
|
||||
from pathlib import Path
|
||||
from cx_Freeze.exception import PlatformError
|
||||
|
||||
|
||||
# In a MachO file, need to deal specially with links that use @executable_path,
|
||||
# @loader_path, @rpath
|
||||
#
|
||||
# @executable_path - where ultimate calling executable is
|
||||
# @loader_path - directory of current object
|
||||
# @rpath - list of paths to check
|
||||
# (earlier rpaths have higher priority, i believe)
|
||||
#
|
||||
# Resolving these variables (particularly @rpath) requires tracing through the
|
||||
# sequence linked MachO files leading the the current file, to determine which
|
||||
# directories are included in the current rpath.
|
||||
|
||||
|
||||
def isMachOFile(path: Path) -> bool:
|
||||
"""Determines whether the file is a Mach-O file."""
|
||||
if not path.is_file():
|
||||
return False
|
||||
if b"Mach-O" in subprocess.check_output(("file", path)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class MachOReference:
|
||||
"""Represents a linking reference from MachO file to another file."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source_file: DarwinFile,
|
||||
raw_path: str,
|
||||
resolved_path: Path | None,
|
||||
):
|
||||
""":param source_file: DarwinFile object for file in which the reference
|
||||
was found
|
||||
:param raw_path: The load path that appears in the file
|
||||
(may include @rpath, etc.)
|
||||
:param resolved_path: The path resolved to an explicit path to a file
|
||||
on system. Or None, if the path could not be resolved at the time the
|
||||
DarwinFile was processed.
|
||||
"""
|
||||
self.source_file: DarwinFile = source_file
|
||||
self.raw_path: str = raw_path
|
||||
self.resolved_path: Path | None = resolved_path
|
||||
|
||||
# True if the referenced file is copied into the frozen package
|
||||
# (i.e., not a non-copied system file)
|
||||
self.is_copied = False
|
||||
# reference to target DarwinFile (but only if file is copied into app)
|
||||
self.target_file: DarwinFile | None = None
|
||||
|
||||
def isResolved(self) -> bool:
|
||||
return self.resolved_path is not None
|
||||
|
||||
def setTargetFile(self, darwin_file: DarwinFile):
|
||||
self.target_file = darwin_file
|
||||
self.is_copied = True
|
||||
|
||||
|
||||
class DarwinFile:
|
||||
"""A DarwinFile object represents a file that will be copied into the
|
||||
application, and record where it was ultimately moved to in the application
|
||||
bundle. Mostly used to provide special handling for copied files that are
|
||||
Mach-O files.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path: str | Path,
|
||||
referencing_file: DarwinFile | None = None,
|
||||
strict: bool = False,
|
||||
):
|
||||
""":param path: The original path of the DarwinFile
|
||||
(before copying into app)
|
||||
:param referencing_file: DarwinFile object representing the referencing
|
||||
source file
|
||||
:param strict: Do not make guesses about rpath resolution. If the
|
||||
load does not resolve, throw an Exception.
|
||||
"""
|
||||
self.path = Path(path).resolve()
|
||||
self.referencing_file: DarwinFile | None = None
|
||||
self.strict = strict
|
||||
|
||||
# path to file in build directory (set as part of freeze process)
|
||||
self._build_path: Path | None = None
|
||||
|
||||
# commands in a Mach-O file
|
||||
self.commands: list[MachOCommand] = []
|
||||
self.loadCommands: list[MachOLoadCommand] = []
|
||||
self.rpathCommands: list[MachORPathCommand] = []
|
||||
|
||||
# note: if file gets referenced twice (or more), it will only be the
|
||||
# first reference that gets recorded.
|
||||
# mapping of raw load paths to absolute resolved paths
|
||||
# (or None, if no resolution was determined)
|
||||
self.libraryPathResolution: dict[str, Path | None] = {}
|
||||
# the is of entries in the rpath in effect for this file.
|
||||
self._rpath: list[Path] | None = None
|
||||
|
||||
# dictionary of MachOReference objects, by their paths.
|
||||
# Path used is the resolved path, if available, and otherwise the
|
||||
# unresolved load path.
|
||||
self.machOReferenceForTargetPath: dict[Path, MachOReference] = {}
|
||||
|
||||
if not isMachOFile(self.path):
|
||||
self.isMachO = False
|
||||
return
|
||||
|
||||
# if this is a MachO file, extract linking information from it
|
||||
self.isMachO = True
|
||||
self.commands = MachOCommand._getMachOCommands(self.path)
|
||||
self.loadCommands = [
|
||||
c for c in self.commands if isinstance(c, MachOLoadCommand)
|
||||
]
|
||||
self.rpathCommands = [
|
||||
c for c in self.commands if isinstance(c, MachORPathCommand)
|
||||
]
|
||||
self.referencing_file = referencing_file
|
||||
|
||||
self.getRPath()
|
||||
self.resolveLibraryPaths()
|
||||
|
||||
# Create MachOReference objects for all the binaries referenced form
|
||||
# this file.
|
||||
for raw_path, resolved_path in self.libraryPathResolution.items():
|
||||
# the path to use for storing in dictionary
|
||||
if resolved_path is None:
|
||||
dict_path = Path(raw_path)
|
||||
else:
|
||||
dict_path = resolved_path
|
||||
if dict_path in self.machOReferenceForTargetPath:
|
||||
if self.strict:
|
||||
raise PlatformError(
|
||||
f"ERROR: Multiple dynamic libraries from {self.path}"
|
||||
f" resolved to the same file ({dict_path})."
|
||||
)
|
||||
print(
|
||||
f"WARNING: Multiple dynamic libraries from {self.path}"
|
||||
f" resolved to the same file ({dict_path})."
|
||||
)
|
||||
continue
|
||||
reference = MachOReference(
|
||||
source_file=self,
|
||||
raw_path=raw_path,
|
||||
resolved_path=resolved_path,
|
||||
)
|
||||
self.machOReferenceForTargetPath[dict_path] = reference
|
||||
|
||||
def __str__(self):
|
||||
parts = []
|
||||
# parts.append("RPath Commands: {}".format(self.rpathCommands))
|
||||
# parts.append("Load commands: {}".format(self.loadCommands))
|
||||
parts.append(f"Mach-O File: {self.path}")
|
||||
parts.append("Resolved rpath:")
|
||||
for rpath in self.getRPath():
|
||||
parts.append(f" {rpath}")
|
||||
parts.append("Loaded libraries:")
|
||||
for rpath in self.libraryPathResolution:
|
||||
parts.append(f" {rpath} -> {self.libraryPathResolution[rpath]}")
|
||||
return "\n".join(parts)
|
||||
|
||||
def fileReferenceDepth(self) -> int:
|
||||
"""Returns how deep this Mach-O file is in the dynamic load order."""
|
||||
if self.referencing_file is not None:
|
||||
return self.referencing_file.fileReferenceDepth() + 1
|
||||
return 0
|
||||
|
||||
def printFileInformation(self):
|
||||
"""Prints information about the Mach-O file."""
|
||||
print(f"[{self.fileReferenceDepth()}] File: {self.path}")
|
||||
print(" Commands:")
|
||||
if len(self.commands) > 0:
|
||||
for cmd in self.commands:
|
||||
print(f" {cmd}")
|
||||
else:
|
||||
print(" [None]")
|
||||
|
||||
# This can be included for even more detail on the problem file.
|
||||
# print(" Load commands:")
|
||||
# if len(self.loadCommands) > 0:
|
||||
# for cmd in self.loadCommands: print(f' {cmd}')
|
||||
# else: print(" [None]")
|
||||
|
||||
print(" RPath commands:")
|
||||
if len(self.rpathCommands) > 0:
|
||||
for rpc in self.rpathCommands:
|
||||
print(f" {rpc}")
|
||||
else:
|
||||
print(" [None]")
|
||||
print(" Calculated RPath:")
|
||||
rpath = self.getRPath()
|
||||
if len(rpath) > 0:
|
||||
for path in rpath:
|
||||
print(f" {path}")
|
||||
else:
|
||||
print(" [None]")
|
||||
if self.referencing_file is not None:
|
||||
print("Referenced from:")
|
||||
self.referencing_file.printFileInformation()
|
||||
|
||||
def setBuildPath(self, path: Path):
|
||||
self._build_path = path
|
||||
|
||||
def getBuildPath(self) -> Path | None:
|
||||
return self._build_path
|
||||
|
||||
@staticmethod
|
||||
def isExecutablePath(path: str) -> bool:
|
||||
return path.startswith("@executable_path")
|
||||
|
||||
@staticmethod
|
||||
def isLoaderPath(path: str) -> bool:
|
||||
return path.startswith("@loader_path")
|
||||
|
||||
@staticmethod
|
||||
def isRPath(path: str) -> bool:
|
||||
return path.startswith("@rpath")
|
||||
|
||||
def resolveLoader(self, path: str) -> Path | None:
|
||||
"""Resolve a path that includes @loader_path. @loader_path represents
|
||||
the directory in which the DarwinFile is located.
|
||||
"""
|
||||
if self.isLoaderPath(path):
|
||||
return self.path.parent / Path(path).relative_to("@loader_path")
|
||||
raise PlatformError(f"resolveLoader() called on bad path: {path}")
|
||||
|
||||
def resolveExecutable(self, path: str) -> Path:
|
||||
"""@executable_path should resolve to the directory where the original
|
||||
executable was located. By default, we set that to the directory of
|
||||
the library, so it would resolve in the same was as if linked from an
|
||||
executable in the same directory.
|
||||
"""
|
||||
# consider making this resolve to the directory of the python
|
||||
# interpreter? Apparently not a big issue in practice, since the
|
||||
# code has been like this forever.
|
||||
if self.isExecutablePath(path):
|
||||
return self.path.parent / Path(path).relative_to(
|
||||
"@executable_path/"
|
||||
)
|
||||
raise PlatformError(f"resolveExecutable() called on bad path: {path}")
|
||||
|
||||
def resolveRPath(self, path: str) -> Path | None:
|
||||
for rpath in self.getRPath():
|
||||
test_path = rpath / Path(path).relative_to("@rpath")
|
||||
if isMachOFile(test_path):
|
||||
return test_path
|
||||
if not self.strict:
|
||||
# If not strictly enforcing rpath, return None here, and leave any
|
||||
# error to .finalizeReferences() instead.
|
||||
return None
|
||||
print(f"\nERROR: Problem resolving RPath [{path}] in file:")
|
||||
self.printFileInformation()
|
||||
raise PlatformError(f"resolveRPath() failed to resolve path: {path}")
|
||||
|
||||
def getRPath(self) -> list[Path]:
|
||||
"""Returns the rpath in effect for this file. Determined by rpath
|
||||
commands in this file and (recursively) the chain of files that
|
||||
referenced this file.
|
||||
"""
|
||||
if self._rpath is not None:
|
||||
return self._rpath
|
||||
raw_paths = [c.rpath for c in self.rpathCommands]
|
||||
rpath = []
|
||||
for raw_path in raw_paths:
|
||||
test_rp = Path(raw_path)
|
||||
if test_rp.is_absolute():
|
||||
rpath.append(test_rp)
|
||||
elif self.isLoaderPath(raw_path):
|
||||
rpath.append(self.resolveLoader(raw_path).resolve())
|
||||
elif self.isExecutablePath(raw_path):
|
||||
rpath.append(self.resolveExecutable(raw_path).resolve())
|
||||
rpath = [raw_path for raw_path in rpath if raw_path.exists()]
|
||||
|
||||
if self.referencing_file is not None:
|
||||
rpath = self.referencing_file.getRPath() + rpath
|
||||
self._rpath = rpath
|
||||
return rpath
|
||||
|
||||
def resolvePath(self, path: str) -> Path | None:
|
||||
"""Resolves any @executable_path, @loader_path, and @rpath references
|
||||
in a path.
|
||||
"""
|
||||
if self.isLoaderPath(path): # replace @loader_path
|
||||
return self.resolveLoader(path)
|
||||
if self.isExecutablePath(path): # replace @executable_path
|
||||
return self.resolveExecutable(path)
|
||||
if self.isRPath(path): # replace @rpath
|
||||
return self.resolveRPath(path)
|
||||
test_path = Path(path)
|
||||
if test_path.is_absolute(): # just use the path, if it is absolute
|
||||
return test_path
|
||||
test_path = self.path.parent / path
|
||||
if isMachOFile(test_path):
|
||||
return test_path.resolve()
|
||||
if self.strict:
|
||||
raise PlatformError(
|
||||
f"Could not resolve path: {path} from file {self.path}."
|
||||
)
|
||||
print(
|
||||
f"WARNING: Unable to resolve reference to {path} from "
|
||||
f"file {self.path}. Frozen application may not "
|
||||
f"function correctly."
|
||||
)
|
||||
return None
|
||||
|
||||
def resolveLibraryPaths(self):
|
||||
for cmd in self.loadCommands:
|
||||
raw_path = cmd.load_path
|
||||
resolved_path = self.resolvePath(raw_path)
|
||||
self.libraryPathResolution[raw_path] = resolved_path
|
||||
|
||||
def getDependentFilePaths(self) -> set[Path]:
|
||||
"""Returns a list the available resolved paths to dependencies."""
|
||||
dependents: set[Path] = set()
|
||||
for ref in self.machOReferenceForTargetPath.values():
|
||||
# skip load references that could not be resolved
|
||||
if ref.isResolved():
|
||||
dependents.add(ref.resolved_path)
|
||||
return dependents
|
||||
|
||||
def getMachOReferenceList(self) -> list[MachOReference]:
|
||||
return list(self.machOReferenceForTargetPath.values())
|
||||
|
||||
def getMachOReferenceForPath(self, path: Path) -> MachOReference:
|
||||
"""Returns the reference pointing to the specified path, baed on paths
|
||||
stored in self.machOReferenceTargetPath. Raises Exception if not
|
||||
available.
|
||||
"""
|
||||
try:
|
||||
return self.machOReferenceForTargetPath[path]
|
||||
except KeyError:
|
||||
raise PlatformError(
|
||||
f"Path {path} is not a path referenced from DarwinFile"
|
||||
) from None
|
||||
|
||||
|
||||
class MachOCommand:
|
||||
"""Represents a load command in a MachO file."""
|
||||
|
||||
def __init__(self, lines: list[str]):
|
||||
self.lines = lines
|
||||
|
||||
def displayString(self) -> str:
|
||||
parts: list[str] = []
|
||||
if len(self.lines) > 0:
|
||||
parts.append(self.lines[0].strip())
|
||||
if len(self.lines) > 1:
|
||||
parts.append(self.lines[1].strip())
|
||||
return " / ".join(parts)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<MachOCommand ({self.displayString()})>"
|
||||
|
||||
@staticmethod
|
||||
def _getMachOCommands(path: Path) -> list[MachOCommand]:
|
||||
"""Returns a list of load commands in the specified file, using
|
||||
otool.
|
||||
"""
|
||||
shell_command = ("otool", "-l", path)
|
||||
commands: list[MachOCommand] = []
|
||||
current_command_lines = None
|
||||
|
||||
# split the output into separate load commands
|
||||
out = subprocess.check_output(shell_command, encoding="utf_8")
|
||||
for raw_line in out.splitlines():
|
||||
line = raw_line.strip()
|
||||
if line[:12] == "Load command":
|
||||
if current_command_lines is not None:
|
||||
commands.append(
|
||||
MachOCommand.parseLines(current_command_lines)
|
||||
)
|
||||
current_command_lines = []
|
||||
if current_command_lines is not None:
|
||||
current_command_lines.append(line)
|
||||
if current_command_lines is not None:
|
||||
commands.append(MachOCommand.parseLines(current_command_lines))
|
||||
return commands
|
||||
|
||||
@staticmethod
|
||||
def parseLines(lines: list[str]) -> MachOCommand:
|
||||
if len(lines) < 2:
|
||||
return MachOCommand(lines)
|
||||
parts = lines[1].split(" ")
|
||||
if parts[0] != "cmd":
|
||||
return MachOCommand(lines)
|
||||
if parts[1] == "LC_LOAD_DYLIB":
|
||||
return MachOLoadCommand(lines)
|
||||
if parts[1] == "LC_RPATH":
|
||||
return MachORPathCommand(lines)
|
||||
return MachOCommand(lines)
|
||||
|
||||
|
||||
class MachOLoadCommand(MachOCommand):
|
||||
def __init__(self, lines: list[str]):
|
||||
super().__init__(lines)
|
||||
self.load_path = None
|
||||
if len(self.lines) < 4:
|
||||
return
|
||||
pathline = self.lines[3]
|
||||
pathline = pathline.strip()
|
||||
if not pathline.startswith("name "):
|
||||
return
|
||||
pathline = pathline[4:].strip()
|
||||
pathline = pathline.split("(offset")[0].strip()
|
||||
self.load_path = pathline
|
||||
|
||||
def getPath(self):
|
||||
return self.load_path
|
||||
|
||||
def __repr__(self):
|
||||
return f"<LoadCommand path={self.load_path!r}>"
|
||||
|
||||
|
||||
class MachORPathCommand(MachOCommand):
|
||||
def __init__(self, lines: list[str]):
|
||||
super().__init__(lines)
|
||||
self.rpath = None
|
||||
if len(self.lines) < 4:
|
||||
return
|
||||
pathline = self.lines[3]
|
||||
pathline = pathline.strip()
|
||||
if not pathline.startswith("path "):
|
||||
return
|
||||
pathline = pathline[4:].strip()
|
||||
pathline = pathline.split("(offset")[0].strip()
|
||||
self.rpath = pathline
|
||||
|
||||
def __repr__(self):
|
||||
return f"<RPath path={self.rpath!r}>"
|
||||
|
||||
|
||||
def _printFile(
|
||||
darwinFile: DarwinFile,
|
||||
seenFiles: set[DarwinFile],
|
||||
level: int,
|
||||
noRecurse=False,
|
||||
):
|
||||
"""Utility function to prints details about a DarwinFile and (optionally)
|
||||
recursively any other DarwinFiles that it references.
|
||||
"""
|
||||
print("{}{}".format(level * "| ", os.fspath(darwinFile.path)), end="")
|
||||
print(" (already seen)" if noRecurse else "")
|
||||
if noRecurse:
|
||||
return
|
||||
for ref in darwinFile.machOReferenceForTargetPath.values():
|
||||
if not ref.is_copied:
|
||||
continue
|
||||
file = ref.target_file
|
||||
_printFile(
|
||||
file,
|
||||
seenFiles=seenFiles,
|
||||
level=level + 1,
|
||||
noRecurse=(file in seenFiles),
|
||||
)
|
||||
seenFiles.add(file)
|
||||
return
|
||||
|
||||
|
||||
def printMachOFiles(fileList: list[DarwinFile]):
|
||||
seenFiles = set()
|
||||
for file in fileList:
|
||||
if file not in seenFiles:
|
||||
seenFiles.add(file)
|
||||
_printFile(file, seenFiles=seenFiles, level=0)
|
||||
|
||||
|
||||
def change_load_reference(
|
||||
filename: str, old_reference: str, new_reference: str, verbose: bool = True
|
||||
):
|
||||
"""Utility function that uses install_name_tool to change old_reference to
|
||||
new_reference in the machO file specified by filename.
|
||||
"""
|
||||
if verbose:
|
||||
print("Redirecting load reference for ", end="")
|
||||
print(f"<{filename}> {old_reference} -> {new_reference}")
|
||||
original = os.stat(filename).st_mode
|
||||
new_mode = original | stat.S_IWUSR
|
||||
if new_mode != original:
|
||||
os.chmod(filename, new_mode)
|
||||
subprocess.call(
|
||||
(
|
||||
"install_name_tool",
|
||||
"-change",
|
||||
old_reference,
|
||||
new_reference,
|
||||
filename,
|
||||
)
|
||||
)
|
||||
if new_mode != original:
|
||||
os.chmod(filename, original)
|
||||
|
||||
|
||||
def apply_adhoc_signature(filename: str):
|
||||
if sysconfig.get_platform().endswith("x86_64"):
|
||||
return
|
||||
# Apply for universal2 and arm64 machines
|
||||
print("Applying AdHocSignature")
|
||||
args = (
|
||||
"codesign",
|
||||
"--sign",
|
||||
"-",
|
||||
"--force",
|
||||
"--preserve-metadata=entitlements,requirements,flags,runtime",
|
||||
filename,
|
||||
)
|
||||
if subprocess.call(args):
|
||||
# It may be a bug in Apple's codesign utility
|
||||
# The workaround is to copy the file to another inode, then move it
|
||||
# back erasing the previous file. The sign again.
|
||||
with TemporaryDirectory(prefix="cxfreeze-") as tmp_dir:
|
||||
tempname = os.path.join(tmp_dir, os.path.basename(filename))
|
||||
shutil.copy(filename, tempname)
|
||||
shutil.move(tempname, filename)
|
||||
subprocess.call(args)
|
||||
|
||||
|
||||
class DarwinFileTracker:
|
||||
"""Object to track the DarwinFiles that have been added during a freeze."""
|
||||
|
||||
def __init__(self, strict: bool = False):
|
||||
self.strict = strict
|
||||
# list of DarwinFile objects for files being copied into project
|
||||
self._copied_file_list: list[DarwinFile] = []
|
||||
|
||||
# mapping of (build directory) target paths to DarwinFile objects
|
||||
self._darwin_file_for_build_path: dict[Path, DarwinFile] = {}
|
||||
|
||||
# mapping of (source location) paths to DarwinFile objects
|
||||
self._darwin_file_for_source_path: dict[Path, DarwinFile] = {}
|
||||
|
||||
# a cache of MachOReference objects pointing to a given source path
|
||||
self._reference_cache: dict[Path, MachOReference] = {}
|
||||
|
||||
def __iter__(self) -> Iterable[DarwinFile]:
|
||||
return iter(self._copied_file_list)
|
||||
|
||||
def pathIsAlreadyCopiedTo(self, target_path: Path) -> bool:
|
||||
"""Check if the given target_path has already has a file copied to
|
||||
it.
|
||||
"""
|
||||
if target_path in self._darwin_file_for_build_path:
|
||||
return True
|
||||
return False
|
||||
|
||||
def getDarwinFile(
|
||||
self, source_path: Path, target_path: Path
|
||||
) -> DarwinFile:
|
||||
"""Gets the DarwinFile for file copied from source_path to target_path.
|
||||
If either (i) nothing, or (ii) a different file has been copied to
|
||||
targetPath, raises a PlatformError.
|
||||
"""
|
||||
# check that the target file came from the specified source
|
||||
targetDarwinFile: DarwinFile
|
||||
try:
|
||||
targetDarwinFile = self._darwin_file_for_build_path[target_path]
|
||||
except KeyError:
|
||||
raise PlatformError(
|
||||
f"File {target_path} already copied to, "
|
||||
"but no DarwinFile object found for it."
|
||||
) from None
|
||||
real_source = source_path.resolve()
|
||||
target_real_source = targetDarwinFile.path.resolve()
|
||||
if real_source != target_real_source:
|
||||
# raise PlatformError(
|
||||
print(
|
||||
"*** WARNING ***\n"
|
||||
f"Attempting to copy two files to {target_path}\n"
|
||||
f"source 1: {targetDarwinFile.path} "
|
||||
f"(real: {target_real_source})\n"
|
||||
f"source 2: {source_path} (real: {real_source})\n"
|
||||
"(This may be caused by including modules in the zip file "
|
||||
"that rely on binary libraries with the same name.)"
|
||||
"\nUsing only source 1."
|
||||
)
|
||||
return targetDarwinFile
|
||||
|
||||
def recordCopiedFile(self, target_path: Path, darwin_file: DarwinFile):
|
||||
"""Record that a DarwinFile is being copied to a given path. If a
|
||||
file has been copied to that path, raise a PlatformError.
|
||||
"""
|
||||
if self.pathIsAlreadyCopiedTo(target_path):
|
||||
raise PlatformError(
|
||||
"addFile() called with target_path already copied to "
|
||||
f"(target_path={target_path})"
|
||||
)
|
||||
|
||||
self._copied_file_list.append(darwin_file)
|
||||
self._darwin_file_for_build_path[target_path] = darwin_file
|
||||
self._darwin_file_for_source_path[darwin_file.path] = darwin_file
|
||||
|
||||
def cacheReferenceTo(self, source_path: Path, reference: MachOReference):
|
||||
self._reference_cache[source_path] = reference
|
||||
|
||||
def getCachedReferenceTo(self, source_path: Path) -> MachOReference | None:
|
||||
return self._reference_cache.get(source_path)
|
||||
|
||||
def findDarwinFileForFilename(self, filename: str) -> DarwinFile | None:
|
||||
"""Attempts to locate a copied DarwinFile with the specified filename
|
||||
and returns that. Otherwise returns None.
|
||||
"""
|
||||
basename = Path(filename).name
|
||||
for file in self._copied_file_list:
|
||||
if file.path.name == basename:
|
||||
return file
|
||||
return None
|
||||
|
||||
def finalizeReferences(self):
|
||||
"""This function does a final pass through the references for all the
|
||||
copied DarwinFiles and attempts to clean up any remaining references
|
||||
that are not already marked as copied. It covers two cases where the
|
||||
reference might not be marked as copied:
|
||||
1) Files where _CopyFile was called without copyDependentFiles=True
|
||||
(in which the information would not have been added to the
|
||||
references at that time).
|
||||
2) Files with broken @rpath references. We try to fix that up here by
|
||||
seeing if the relevant file was located *anywhere* as part of the
|
||||
freeze process.
|
||||
"""
|
||||
copied_file: DarwinFile
|
||||
reference: MachOReference
|
||||
for copied_file in self._copied_file_list:
|
||||
for reference in copied_file.getMachOReferenceList():
|
||||
if not reference.is_copied:
|
||||
if reference.isResolved():
|
||||
# if reference is resolve, simply check if the resolved
|
||||
# path was otherwise copied and lookup the DarwinFile
|
||||
# object.
|
||||
target_path = reference.resolved_path.resolve()
|
||||
if target_path in self._darwin_file_for_source_path:
|
||||
reference.setTargetFile(
|
||||
self._darwin_file_for_source_path[target_path]
|
||||
)
|
||||
else:
|
||||
# if reference is not resolved, look through the copied
|
||||
# files and try to find a candidate, and use it if
|
||||
# found.
|
||||
potential_target = self.findDarwinFileForFilename(
|
||||
reference.raw_path
|
||||
)
|
||||
if potential_target is None:
|
||||
# If we cannot find any likely candidate, fail.
|
||||
if self.strict:
|
||||
copied_file.printFileInformation()
|
||||
raise PlatformError(
|
||||
f"finalizeReferences() failed to resolve"
|
||||
f" path [{reference.raw_path}] in file "
|
||||
f"[{copied_file.path}]."
|
||||
)
|
||||
print(
|
||||
"\nWARNING: Could not resolve dynamic link to "
|
||||
f"[{reference.raw_path}] in file "
|
||||
f"[{copied_file.path}], and could "
|
||||
"not find any likely intended target."
|
||||
)
|
||||
continue
|
||||
print(
|
||||
f"WARNING: In file [{copied_file.path}]"
|
||||
f" guessing that {reference.raw_path} "
|
||||
f"resolved to {potential_target.path}."
|
||||
)
|
||||
reference.resolved_path = potential_target.path
|
||||
reference.setTargetFile(potential_target)
|
||||
|
||||
def set_relative_reference_paths(self, build_dir: str, bin_dir: str):
|
||||
"""Make all the references from included Mach-O files to other included
|
||||
Mach-O files relative.
|
||||
"""
|
||||
darwin_file: DarwinFile
|
||||
|
||||
for darwin_file in self._copied_file_list:
|
||||
# Skip text files
|
||||
if darwin_file.path.suffix == ".txt":
|
||||
continue
|
||||
|
||||
# get the relative path to darwin_file in build directory
|
||||
print(f"Setting relative_reference_path for: {darwin_file}")
|
||||
relative_copy_dest = os.path.relpath(
|
||||
darwin_file.getBuildPath(), build_dir
|
||||
)
|
||||
# figure out directory where it will go in binary directory for
|
||||
# .app bundle, this would be the Content/MacOS subdirectory in
|
||||
# bundle. This is the file that needs to have its dynamic load
|
||||
# references updated.
|
||||
file_path_in_bin_dir = os.path.join(bin_dir, relative_copy_dest)
|
||||
# for each file that this darwin_file references, update the
|
||||
# reference as necessary; if the file is copied into the binary
|
||||
# package, change the reference to be relative to @executable_path
|
||||
# (so an .app bundle will work wherever it is moved)
|
||||
for reference in darwin_file.getMachOReferenceList():
|
||||
if not reference.is_copied:
|
||||
# referenced file not copied -- assume this is a system
|
||||
# file that will also be present on the user's machine,
|
||||
# and do not change reference
|
||||
continue
|
||||
# this is the reference in the machO file that needs to be
|
||||
# updated
|
||||
raw_path = reference.raw_path
|
||||
ref_target_file: DarwinFile = reference.target_file
|
||||
# this is where file copied in build dir
|
||||
abs_build_dest = ref_target_file.getBuildPath()
|
||||
rel_build_dest = os.path.relpath(abs_build_dest, build_dir)
|
||||
exe_path = f"@executable_path/{rel_build_dest}"
|
||||
change_load_reference(
|
||||
file_path_in_bin_dir, raw_path, exe_path, verbose=False
|
||||
)
|
||||
|
||||
apply_adhoc_signature(file_path_in_bin_dir)
|
||||
Reference in New Issue
Block a user