diff --git a/bin/BuildSystem/BuildSystemBase.py b/bin/BuildSystem/BuildSystemBase.py --- a/bin/BuildSystem/BuildSystemBase.py +++ b/bin/BuildSystem/BuildSystemBase.py @@ -6,6 +6,7 @@ import multiprocessing import os import re +import subprocess from CraftBase import * from CraftOS.osutils import OsUtils @@ -111,6 +112,23 @@ def clangOptions(self): return "" + + def _fixRpath(self, prefix : str, path : str) -> bool: + with os.scandir(path) as scan: + for f in scan: + if f.is_symlink(): + continue + elif f.is_dir(): + if not self._fixRpath(prefix, f.path): + return False + elif utils.isBinary(f.path): + for dep in utils.getLibraryDeps(f.path): + if dep.startswith(prefix): + newPrefix = f"@loader_path/{os.path.relpath(self.imageDir(), os.path.dirname(f.path))}/{os.path.relpath(dep, prefix)}" + if not utils.system(["install_name_tool", "-change", dep, newPrefix, f.path]): + return False + return True + def _fixInstallPrefix(self, prefix=CraftStandardDirs.craftRoot()): CraftCore.log.debug(f"Begin: fixInstallPrefix {self}: {prefix}") def stripPath(path): @@ -137,6 +155,10 @@ oldPrefix = OsUtils.toUnixPath(stripPath(prefix)).split("/", 1)[0] utils.rmtree(os.path.join(self.installDir(), oldPrefix)) + if CraftCore.compiler.isMacOS: + if not self._fixRpath(prefix, self.installDir()): + return False + CraftCore.log.debug(f"End: fixInstallPrefix {self}") return True diff --git a/bin/Packager/CollectionPackagerBase.py b/bin/Packager/CollectionPackagerBase.py --- a/bin/Packager/CollectionPackagerBase.py +++ b/bin/Packager/CollectionPackagerBase.py @@ -198,7 +198,7 @@ if self.__qtSdkDir not in filename: return True - if self.isBinary(filename): + if utils.isBinary(filename): if not CraftCore.cache.findApplication("dependencies"): raise BlueprintException("Deploying a QtSdk depends on dev-util/dependencies", CraftPackageObject.get("dev-util/dependencies")) _, imports = CraftCore.cache.getCommandOutput("dependencies", f"-imports {filename}") @@ -213,18 +213,6 @@ return out return True - def isBinary(self, fileName): - if os.path.islink(fileName): - return False - _, ext = os.path.splitext(fileName) - if CraftCore.compiler.isWindows: - if ext in {".dll", ".exe"}: - return True - else: - if ext in {".so", ".dylib"} or os.access(fileName, os.X_OK): - return True - return False - def traverse(self, root, whitelist=lambda f: True, blacklist=lambda g: False): """ Traverse through a directory tree and return every @@ -261,7 +249,7 @@ entry_target = os.path.join(destDir, os.path.relpath(entry, srcDir)) if not utils.copyFile(entry, entry_target, linkOnly=False): return False - if self.isBinary(entry_target): + if utils.isBinary(entry_target): if CraftCore.compiler.isGCCLike() and not dontStrip: self.strip(entry_target) if doSign: diff --git a/bin/Packager/MacDMGPackager.py b/bin/Packager/MacDMGPackager.py --- a/bin/Packager/MacDMGPackager.py +++ b/bin/Packager/MacDMGPackager.py @@ -190,8 +190,7 @@ if not self._fixupLibraryId(targetPath): return False - for dep in self._getLibraryDeps(targetPath): - path = dep.split()[0] + for path in utils.getLibraryDeps(str(targetPath)): # check there aren't any references to the original location: if path == str(libPath): CraftCore.log.error("%s: failed to fix reference to original location for '%s'", targetPath, path) @@ -207,15 +206,6 @@ self.checkedLibs.add(targetPath) return True - @staticmethod - def _getLibraryDeps(fullPath): - lines = io.StringIO(subprocess.check_output(["otool", "-L", str(fullPath)]).decode("utf-8")) - deps = [] - for line in lines: - if line.startswith("\t"): - deps.append(line.strip()) - return deps - @staticmethod def _updateLibraryReference(fileToFix: Path, oldRef: str, newRef: str = None) -> bool: if newRef is None: @@ -261,8 +251,7 @@ # Ensure we have the current library ID since we need to skip it in the otool -L output libraryId = self._getLibraryNameId(fileToFix) - for dep in self._getLibraryDeps(fileToFix): - path = dep.split()[0] + for path in utils.getLibraryDeps(str(fileToFix)): if path == libraryId: # The first line of the otool output is (usually?) the library itself: # $ otool -L PlugIns/printsupport/libcocoaprintersupport.dylib: @@ -336,9 +325,8 @@ found_bad_lib = False libraryId = self._getLibraryNameId(fullPath) relativePath = os.path.relpath(str(fullPath), self.appPath) - for dep in self._getLibraryDeps(fullPath): - path = dep.split()[0] - if path == libraryId and not os.path.isabs(libraryId): + for dep in utils.getLibraryDeps(str(fullPath)): + if dep == libraryId and not os.path.isabs(libraryId): continue # non-absolute library id is fine # @rpath and @executable_path is fine if dep.startswith("@rpath") or dep.startswith("@executable_path"): diff --git a/bin/Utils/PostInstallRoutines.py b/bin/Utils/PostInstallRoutines.py --- a/bin/Utils/PostInstallRoutines.py +++ b/bin/Utils/PostInstallRoutines.py @@ -44,4 +44,4 @@ flags = [] if CraftCore.debug.verbose() > 0: flags += ["-V"] - return utils.system(["update-mime-database"] + flags + [dataDir]) + return utils.system(["update-mime-database"] + flags + [dataDir]) \ No newline at end of file diff --git a/bin/utils.py b/bin/utils.py --- a/bin/utils.py +++ b/bin/utils.py @@ -804,3 +804,33 @@ if not system(command + [fileName], logCommand=False): return False return True + +def isBinary(fileName : str) -> bool: + if os.path.islink(fileName): + return False + _, ext = os.path.splitext(fileName) + if CraftCore.compiler.isWindows: + if ext in {".dll", ".exe"}: + return True + else: + if ext in {".so", ".dylib"} or os.access(fileName, os.X_OK): + return True + return False + + +def getLibraryDeps(path): + deps = [] + if CraftCore.compiler.isMacOS: + # based on https://github.com/qt/qttools/blob/5.11/src/macdeployqt/shared/shared.cpp + infoRe = re.compile("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "+ + "current version (\\d+\\.\\d+\\.\\d+)\\)$") + lines = subprocess.check_output(["otool", "-L", path], universal_newlines=True) + lines = lines.split("\n") + lines.pop(0)# name of the library + for line in lines: + if not line: + continue + match = infoRe.match(line) + if match: + deps.append(match[1]) + return deps \ No newline at end of file