Skip to content
Snippets Groups Projects

Migrate to pyenv Python Platform; Fix MacOS package (Major change)

Merged Ammar Nejati requested to merge fixMacPackage into r21.1
1 file
+ 555
492
Compare changes
  • Side-by-side
  • Inline
@@ -5,28 +5,38 @@ Build BornAgain with multiple versions of Python
import sys, os, shutil, glob, platform, re, subprocess as subp
from collections import namedtuple
def mkdir(path:str):
TITLE="* MacOS Package"
PKGROOT = '<PKG-ROOT>'
#------------------------------------------------------------------------------80
def mkdirs(path:str):
if not os.path.exists(path):
os.makedirs(path)
def removeprefix(str_:str, pfx:str):
return str_.split(pfx, 1)[-1]
def splat(str_list:str, separator = ' '):
return [x for x in str_list.split(separator) if x]
def copyfile(src:str, dst:str, overwrite=False):
if not overwrite and os.path.exists(dst): return
shutil.copy(src, dst)
def dylib_id(libpath:str):
""" return the base library id for the given library """
return subp.check_output(["otool", "-XD", libpath], encoding='utf-8')
return subp.check_output(["otool", "-XD", libpath], encoding='utf-8').strip()
def rm_list_duplicates(list0:list):
""" remove duplicates from a given list """
return list(set(list0))
def dylib_deps(libpath:str):
""" use otool to get the 1st-order dependencies of a library (raw output) """
# obtain the dependencies for a given library;
@@ -38,10 +48,12 @@ def dylib_deps(libpath:str):
deps = subp.check_output(["otool", "-XL", libpath], encoding='utf-8').split('\n')
return [d.strip() for d in deps if d and not basename_rx.search(d)]
def find_dylibs(abspath:str):
""" return the filenames corresponding to the pattern *.so or *.dylib """
return glob.glob(abspath + '/*.so') + glob.glob(abspath + '/*.dylib')
def find_common_root(abspath1:str, abspath2:str):
""" find the longest common root of two given _absolute_ paths """
if abspath1 == abspath2:
@@ -51,9 +63,9 @@ def find_common_root(abspath1:str, abspath2:str):
# convert paths to arrays of directories:
# replace '/' with blank and make an array out of the result;
# eg. '/root/lib/opt' => [root, lib, opt]
dirs1 = abspath1.split('/')
dirs1 = [d for d in abspath1.split('/') if d]
idxMax1 = len(dirs1)
dirs2 = abspath2.split('/')
dirs2 = [d for d in abspath2.split('/') if d]
idxMax2 = len(dirs2)
common_root = ""
for idx in range(min(idxMax1, idxMax2)):
@@ -70,6 +82,7 @@ def find_common_root(abspath1:str, abspath2:str):
# return the longest common root
return common_root
def find_rpath(bin_abspath:str, lib_abspath:str, lib_relpath:str=""):
""" find the proper rpath for given binary pointing to a reference library
# usage: find_rpath(bin_abspath, lib_abspath, lib_relpath)
@@ -79,30 +92,31 @@ def find_rpath(bin_abspath:str, lib_abspath:str, lib_relpath:str=""):
# lib_relpath='Qux/lib'
# returns: `../../Frameworks`
"""
# TODO: rm lib_relpath
# drop the final '/' chars from all paths
bin_abspath = os.path.dirname(bin_abspath) # target binary for which a rpath is obtained
lib_abspath = os.path.dirname(lib_abspath) # referenced library
lib_relpath = os.path.dirname(lib_relpath) # relative path to the referenced library
# find the longest common root path
root_path_ = find_common_root(bin_abspath, lib_abspath)
root_path = os.path.dirname(root_path_)
root_path = find_common_root(bin_abspath, lib_abspath)
if not root_path: return lib_abspath
# obtain the path from the binary to the root
# eg. '/root/local/opt' => 'local/opt' => '../..'
binpth_to_root_ = re.sub(root_path + '(/|$)', '', bin_abspath)
binpth_to_root = re.sub('[^/]+', '..', binpth_to_root_)
_binpth_to_root = re.sub(root_path + '(/|$)', '', bin_abspath)
binpth_to_root = re.sub('[^/]+', '..', _binpth_to_root)
# obtain the path from root to the referenced library;
# eg. '/root/local/opt' => 'local/opt'
# then, drop the relative path of the library from the end
libpth_from_root_ = re.sub(root_path + '(/|$)', '', lib_abspath)
libpth_from_root = re.sub('(^|/)' + lib_relpath + '$', '', libpth_from_root_)
_libpth_from_root = re.sub(root_path + '(/|$)', '', lib_abspath)
libpth_from_root = re.sub('(^|/)' + lib_relpath + '$', '', _libpth_from_root)
# return the proper relative RPATH to the referenced library
# eg. '../../Frameworks/Qt'
if not binpth_to_root:
libpth_from_bin = libpth_from_root
else:
libpth_from_bin = binpth_to_root + '/' + libpth_from_root
libpth_from_bin = libpth_from_root if not binpth_to_root \
else binpth_to_root + '/' + libpth_from_root
return "@loader_path/" + libpth_from_bin
return libpth_from_bin
def get_depends1(libpath:str):
""" get 1st-order dependencies for a given file """
@@ -115,551 +129,600 @@ def get_depends1(libpath:str):
deps = list()
for d in deps_:
# extract library path
m_ = re.match('\s*([\w@/\.]+)\s+.*', d)
m_ = re.match(r'\s*([\w@/\.]+)\s+.*', d)
if m_: deps.append(m_.group(1))
return deps
def get_python_dependence(dependencies:list) -> (str, str):
""" extract the Python dependency from the given dependencies
# NOTE: Under OSX, Python's library name is either 'Python', or
# for Python3.9, 'libpython3.9.dylib'.
"""
class PythonFramework:
""" Python framework details """
# regexp to extract the Python dependence;
# eg., '/Frameworks/Python.framework/Versions/3.9/Python'
libdir_re = r'[\w\./@-]+'
pylibname_re = r'Python|libpython.+\.dylib'
py_fmwk_re = re.compile('(' + libdir_re + ')/(' + pylibname_re + ')')
# regexp to correct the Python dependence; eg.:
# '/Users/usr1/.pyenv/versions/3.9.18/lib/libpython3.9.dylib' => 'libpython3.9.dylib'
# '/Frameworks/Python.framework/Versions/3.9/Python' => 'libpython3.9.dylib'
# '/Frameworks/Python.framework/Versions/3.9/libpython3.9.dylib' => 'libpython3.9.dylib'
# obtain the dependencies
pydepends_path_list = [d for d in dependencies if py_fmwk_re.match(d)]
pydepends_path = pydepends_filename = None
if pydepends_path_list:
pydepends_filename = pydepends_path_list[0]
mtch = py_fmwk_re.match(pydepends_filename)
pydepends_path = mtch[1]
pydepends_filename = mtch[2]
# return the Python dependence fullpath and filename
return (pydepends_path, pydepends_filename)
def get_python_framework_path(pydepends_path:str):
""" produce proper Python framework paths for a given Python dependency """
pydepends_path = pydepends_path.lower()
# regexp to extract the Python framework path
# eg. '/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/Python'
# => '/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/'
py_fmwk_path_re = r'(.*)/(Python|libpython).*'
py_fmwk_dir = re.match(py_fmwk_path_re, pydepends_path)[1]
# when the library is like '.../Versions/3.9/Python', then
# add an extra '/lib' to the framework path;
# this is needed since it refers to the standard location of the
# Python shared library on OSX, '.../Versions/3.9/lib/libpython39.dylib'.
if pydepends_path.endswith('Python'):
py_fmwk_dir += '/lib'
# regexp to extract the Python version; eg. '3.9'
pyversion = None
pyversion_m = re.match(r'.+versions/([0-9.]+).*', pydepends_path)
if pyversion_m:
pyversion = pyversion_m[1]
else:
pyversion_m = re.match(r'.+/libpython(.+)\.dylib', pydepends_path)
# possible framework paths on MacOS
framework_paths = ("/usr/local/Library/Frameworks",
"/Library/Frameworks", "/usr/local/Frameworks",
"/opt/homebrew/Frameworks")
@staticmethod
def get_python_dependence(dependencies:list) -> (str, str):
""" extract the Python dependency from the given dependencies
# NOTE: Under OSX, Python's library name is either 'Python', or
# for Python3.9, 'libpython3.9.dylib'.
"""
# regexp to correct the Python dependence; eg.:
# '/Users/usr1/.pyenv/versions/3.9.18/lib/libpython3.9.dylib' => 'libpython3.9.dylib'
# '/Frameworks/Python.framework/Versions/3.9/Python' => 'libpython3.9.dylib'
# '/Frameworks/Python.framework/Versions/3.9/libpython3.9.dylib' => 'libpython3.9.dylib'
# obtain the dependencies
pydepends_path_list = [d for d in dependencies if PythonFramework.py_fmwk_re.match(d)]
pydepends_path = pydepends_filename = None
if pydepends_path_list:
pydepends_filename = pydepends_path_list[0]
mtch = PythonFramework.py_fmwk_re.match(pydepends_filename)
pydepends_path = mtch[1]
pydepends_filename = mtch[2]
# return the Python dependence fullpath and filename
return pydepends_path, pydepends_filename
@staticmethod
def get_python_framework_path(pydepends_path:str) -> (str, str):
""" produce proper Python framework paths for a given Python dependency """
pydepends_path = pydepends_path.lower()
# extract the Python framework path
py_fmwk_dir = re.match(PythonFramework.py_fmwk_path_re, pydepends_path)[1]
# when the library is like '.../Versions/3.9/Python', then
# add an extra '/lib' to the framework path;
# this is needed since it refers to the standard location of the
# Python shared library on OSX, '.../Versions/3.9/lib/libpython39.dylib'.
if pydepends_path.endswith('Python'):
py_fmwk_dir += '/lib'
# regexp to extract the Python version; eg. '3.9'
pyversion = None
pyversion_m = re.match(r'.+versions/([0-9.]+).*', pydepends_path)
if pyversion_m:
pyversion = pyversion_m[1]
else:
pyversion_m = re.match(r'.+/libpython(.+)\.dylib', pydepends_path)
if pyversion_m:
pyversion = pyversion_m[1]
# '3.9.18' => '3.9'
pyversion = re.match(r'(\d+.\d+).*', pyversion)[1]
if not pyversion:
raise ValueError("Cannot extract Python version from path '%s'"
% pydepends_path)
# '3.9.18' => '3.9'
pyversion = re.match(r'(\d+.\d+).*', pyversion)[1]
if not pyversion:
raise ValueError("Cannot extract Python version from path '%s'"
% pydepends_path)
# RPATHs corresponding to the common OSX framework paths
py_fmwk_basepath = "Python.framework/Versions/" + pyversion + "/lib"
framework_paths = ("/usr/local/Library/Frameworks",
"/Library/Frameworks", "/usr/local/Frameworks",
"/opt/homebrew/Frameworks")
# RPATHs corresponding to the common OSX framework paths
py_fmwk_basepath = "Python.framework/Versions/" + pyversion + "/lib"
# collect proper RPATHs for Python framework
py_fmwk_rpaths = [py_fmwk_dir]
for pth in framework_paths:
py_fmwk_rpaths.append(pth + '/' + py_fmwk_basepath)
# collect proper RPATHs for Python framework
py_fmwk_rpaths = [py_fmwk_dir]
for pth in PythonFramework.framework_paths:
py_fmwk_rpaths.append(pth + '/' + py_fmwk_basepath)
# return a list of possible framework paths
return py_fmwk_rpaths
# return a list of possible framework paths
return py_fmwk_rpaths, pyversion
class PkgLibrary:
def __init__(self):
self.dtype = "" # dependency type
self.src = "" # path to the library file
self.ref = "" # reference to the library
self.rpath = "" # rpath used for the library in the package
self.dst = "" # destination of the library in the package
self.dependencies = list() # list of dependencies of the library
class dylibRPATHs:
""" Function with internal constants to obtain the RPATHs of a library """
def __str__(self):
return ("PkgLibrary: dtype=%s, src=%s, ref=%s, rpath=%s, dst=%s"
% (self.dtype, self.src, self.ref, self.rpath, self.dst))
# extract the LC_RPATH sections from the output of `otool -l` command
LC_RPATH_rx = re.compile(r'.+RPATH.*\n.+\n.+\n')
# extract 'rpath' from a LC_RPATH section
rpath_rx = re.compile(r'path\s+([@\w/\.]+)\s+.*')
# aux. dictionary to find the source file from a library reference
libId_src_tbl = dict()
@staticmethod
def find(libpath:str) -> tuple:
""" return the RPATHs for the given library """
otool_output = subp.check_output(["otool", "-l", libpath], encoding='utf-8')
# 'raw' rpaths which might include '@loader_path'
_rpaths = dylibRPATHs.rpath_rx.findall(
'\n'.join(dylibRPATHs.LC_RPATH_rx.findall(otool_output)))
return tuple(r.rstrip('/') for r in _rpaths)
@staticmethod
def resolve(rpathref:str, rpaths:list[str]):
""" resolve a given rpath-reference to a list of possible paths
according to a given list of RPATHs """
return tuple(rpathref.replace('@rpath', p) for p in rpaths) if rpaths else tuple()
class FrameworkLibrary:
""" Function with internal constants used to find
and analyse references to framework libraries """
@staticmethod
def resolve_by_table(ref:str):
""" resolve a given rpath-reference to a table of reference => path """
return dylibRPATHs.libId_src_tbl.get(ref, '')
class LibraryFile:
""" Library binary file """
def __init__(self, dtype:str='', src:str='', ref:str='', rpaths:list[str]=None,
dst:str='', ID:str='', dependencies:set[str]=None, parent=None):
self.dtype = dtype # dependency type
self.src = src # path to the library file
self.ref = ref # reference to the library
self.rpaths = rpaths if rpaths else set() # rpaths of the library
self.dst = dst # destination of the library in the package
self.ID = ID # library ID
self.ID_new = '' # new library ID
# list of dependencies of the library
self.dependencies = dependencies if dependencies else set()
self.parent = parent
def follow(self):
""" determine if the library dependencies should be followed """
# do not follow Python dependencies
ref_basename = os.path.basename(self.ref).lower()
if 'python' in ref_basename and 'boost' not in ref_basename:
return False
# follow only libraries for which there is an absolute source path
return self.src.startswith('/')
rx = re.compile(r'.+/([^/]+\.[Ff]ramework/[^\s]+).*')
@staticmethod
def find_source(lib, parent_lib):
""" find the absolute path to the source, as far as possible """
if lib.ref.startswith('@rpath') and parent_lib:
if parent_lib.rpaths:
parent_lib_dir = os.path.dirname(parent_lib.src)
rpaths_fixed = tuple(os.path.realpath(r.replace('@loader_path', parent_lib_dir))
for r in parent_lib.rpaths)
# TODO: store fixed RPATHs
lib.src = next(
filter(os.path.isfile, dylibRPATHs.resolve(lib.ref, rpaths_fixed)), '')
elif lib.ref.startswith('@loader_path'):
# relative reference; eg. '@rpath/foo.dylib'
lib.dtype = 'rel@loader_path'
lib.src = (os.path.dirname(parent_lib.src) + '/'
+ removeprefix(lib.ref, '@loader_path'))
elif lib.ref.startswith('/'):
# absolute path
lib.src = lib.ref
# last attempt
if not lib.src: lib.src = dylibRPATHs.resolve_by_table(lib.ref)
if lib.src: lib.src = os.path.realpath(lib.src)
return lib.src
@staticmethod
def find(lib_path:str, dep_ref:str, fw_root:str):
fwdir_dep = FrameworkLibrary.rx.match(dep_ref).group(1)
basename = os.path.basename(dep_ref)
fwdir = os.path.dirname(fwdir_dep)
srcpath = dep_ref
dstpath_rel = basename
if dep_ref.startswith('@loader_path'):
dstpath_rel = fwdir_dep
srcpath = os.path.dirname(lib) + '/' + dstpath_rel
elif dep_ref.startswith('@rpath'):
dstpath_rel = dep_ref.split('@rpath')[-1]
srcpath = fw_root + '/Frameworks/' + dstpath_rel # TODO: Check this
print("AN>> FrameworkLibrary: src='%s', fw='%s', dst='%s'"
% (srcpath, fwdir, dstpath_rel))
return srcpath, fwdir, dstpath_rel
def dependency_type(dep:str, pkglib:PkgLibrary):
if 'python' in dep and 'boost' not in dep:
# Python dependencies must be considered separately (exclude 'libboost_python')
# eg. '/opt/py@3.9/Frameworks/Python.framework/Versions/3.9/Python'
pkglib.dtype = 'framework_py'
pkglib.src = dep # TODO: add to dep_next
elif '.framework/' in dep.lower() and os.path.basename(dep).startswith('Qt'):
pkglib.dtype = 'framework_qt'
pkglib.src, _, pkglib.dst = \
FrameworkLibrary.find(lib, dep, pkgdirs['qt_root'])
elif dep.startswith('@rpath'):
# relative reference; eg. '@rpath/foo.dylib'
pkglib.dtype = 'rel@rpath'
pkglib.src = ''
elif dep.startswith('@loader_path'):
# relative reference; eg. '@rpath/foo.dylib'
pkglib.dtype = 'rel@loader_path'
pkglib.src = os.path.dirname(lib) + '/' + removeprefix(dep, '@loader_path')
elif '.framework/' in dep.lower():
# Frameworks must be considered separately
# eg. '/opt/qt@5/lib/QtGui.framework/Versions/5/QtGui'
pkglib.dtype = 'framework'
pkglib.src = dep
else:
# absolute reference; eg. '/usr/opt/libA.so'
pkglib.dtype = 'abs'
pkglib.src = dep
#END if
return
def dependency_type(dep:str, parent:str, pkglib:PkgLibrary):
""" find the dependency type """
pkglib.ref = dep
pkglib.src = ''
if 'python' in dep and 'boost' not in dep:
# Python dependencies must be considered separately (exclude 'libboost_python')
# eg. '/opt/py@3.9/Frameworks/Python.framework/Versions/3.9/Python'
pkglib.dtype = 'framework_py'
pkglib.src = dep # TODO: add to dep_next
elif '.framework/' in dep.lower() and os.path.basename(dep).startswith('Qt'):
pkglib.dtype = 'framework_qt'
pkglib.src, _, pkglib.dst = FrameworkLibrary.find(parent, dep, pkgdirs['qt_root'])
elif dep.startswith('@rpath'):
# relative reference; eg. '@rpath/foo.dylib'
pkglib.dtype = 'rel@rpath'
pkglib.src = ''
elif dep.startswith('@loader_path'):
# relative reference; eg. '@rpath/foo.dylib'
pkglib.dtype = 'rel@loader_path'
pkglib.src = os.path.dirname(parent) + '/' + removeprefix(dep, '@loader_path')
elif '.framework/' in dep.lower():
# Frameworks must be considered separately
# eg. '/opt/qt@5/lib/QtGui.framework/Versions/5/QtGui'
pkglib.dtype = 'framework'
pkglib.src = dep
else:
# absolute reference; eg. '/usr/opt/libA.so'
pkglib.dtype = 'abs'
pkglib.src = dep
if pkglib.src: pkglib.src = os.path.realpath(pkglib.src)
def find_dependencies(libraries_ini:list, pkgdirs:dict, LEVELMAX=10):
def dependency_type(lib):
""" find the dependency type """
if lib.dtype: return
dep = lib.ref
dep_basename_l = os.path.basename(dep).lower()
if 'python' in dep_basename_l and 'boost' not in dep_basename_l:
# Python dependencies must be considered separately (exclude 'libboost_python')
# eg. '/opt/py@3.9/Frameworks/Python.framework/Versions/3.9/Python'
lib.dtype = 'framework_py'
elif '.framework/' in dep.lower() and os.path.basename(dep).startswith('Qt'):
lib.dtype = 'framework_qt'
elif dep.startswith('@loader_path'):
# relative reference; eg. '@rpath/foo.dylib'
lib.dtype = 'rel@loader_path'
elif dep.startswith('@rpath'):
# relative reference; eg. '@rpath/foo.dylib'
lib.dtype = 'rel@rpath'
elif '.framework/' in dep.lower():
# Frameworks must be considered separately
# eg. '/opt/qt@5/lib/QtGui.framework/Versions/5/QtGui'
lib.dtype = 'framework'
elif dep.startswith('/'):
# absolute reference; eg. '/usr/opt/libA.so'
lib.dtype = 'abs'
else:
lib.dtype = 'unknown'
return lib.dtype
def __str__(self):
return ("LibraryFile: dtype=%s, src=%s, ref=%s,"
"rpaths=%s, dst=%s, ID=%s, dependencies=%s, parent-ref=%s"
% (self.dtype, self.src, self.ref, self.rpaths, self.dst, self.ID,
self.dependencies, self.parent.ref if self.parent else None))
def __repr__(self):
return self.__str__()
def find_dependencies_level(libs_cur:dict[(str, LibraryFile)], pkg_libs:dict) -> dict:
# obtain all dependencies at the current level
# map {ref => parent-LibraryFile}
deps_lv:dict[(str, LibraryFile)] = dict()
for lib_ref, parent_lib in libs_cur.items():
# if not at the root level (i.e. library has parent) and
# library has been already discovered, then
# neglect the library to avoid infinite loops
if parent_lib and lib_ref in pkg_libs: continue
lib = pkg_libs.setdefault(lib_ref, LibraryFile())
lib.ref = lib_ref
lib.parent = parent_lib
# determine the type of the library and its source path
if not lib.src: LibraryFile.find_source(lib, parent_lib)
LibraryFile.dependency_type(lib)
# consider only absolute paths for the next level
if not lib.follow(): continue
# verify the existence of the file
if not os.path.isfile(lib.src):
raise FileNotFoundError("%s: dependency does not exist: '%s'"
% (TITLE, lib.src))
# find the 1st-level dependencies of the library
lib.dependencies = get_depends1(lib.src)
# find RPATH references of the library
lib.rpaths = set(dylibRPATHs.find(lib.src))
# find library ID; eg., '@rpath/local/libA.dylib'
lib.ID = dylib_id(lib.src)
# add the library ID to the ID-table
dylibRPATHs.libId_src_tbl[lib.ID] = lib.src
# add the library and its parent for the next iteration
deps_lv.update({d: lib for d in lib.dependencies})
return deps_lv
def find_dependencies(pkg_libs:dict, pkg_dirs:dict, level_max:int):
""" gather all dependencies for the given initial libraries
up to a maximum (but arbitrary) level.
"""
print("%s: Find dependencies (up to level %s)..." % (TITLE, LEVELMAX))
# NOTE: Sets are used to keep a list of entries without repetition
pkglibs = list()
libs_lv = set(libraries_ini) # libraries at the current level
level = 0 # current level nr.
libraries = dict() # {reference/abs. path => package}
print("%s: Find dependencies (up to level %s)..." % (TITLE, level_max))
# initial libraries: find dependencies
for lib in libs_lv:
pkglib = PkgLibrary()
pkglib.dtype = 'init'
pkglib.src = lib
pkglib.dependencies = get_depends1(lib)
libraries[lib] = pkglib
libs_lv = {l: None for l in libs_lv} # { library ref => parent }
print("AN>> libs_lv = ", libs_lv)
for ref, pkglib in pkg_libs.items():
pkglib.parent = None
pkglib.src = ref
libs_lv = dict(pkg_libs) # root level libraries
#-- map {library-id => library source path}
dylibRPATHs.libId_src_tbl = {dylib_id(l): l for l in libs_init}
# find dependencies of libraries recursively,
# beginning from the initial libraries
level = 0 # current level nr.
libs_lv = {l: None for l in pkg_libs.keys()} # map {library ref => parent}
while libs_lv:
level += 1
# avoid going infinitely deep (eg. due to some mistake)
if level > LEVELMAX:
print("Error: Dependency level %i exceeds the maximum allowed depth (%i)."
% (level, LEVELMAX))
if level > level_max:
print("%s: Dependency level %i exceeds the maximum allowed depth (%i)."
% (TITLE, level, level_max))
break
if not libs_lv:
print("AN>> END")
break
# eg. at level 3, print '==>[L3]'
print("==>[L%i]" % level)
# obtain all dependencies at the current level
_deps_lv = dict() # dependencies at the current level (tmp.)
for lib_ref, parent_lib in libs_lv.items():
# neglect previously-found references to avoid duplicate packages
if level > 1 and lib_ref in libraries: continue
print("[L%i] %s <= %s" % (level, lib_ref, parent_lib))
# find dependencies, if not found before
pkglib = libraries[lib_ref] = PkgLibrary()
# determine the type of the library and its source path
dependency_type(lib_ref, parent_lib, pkglib)
if not pkglib.src: continue
if not os.path.isfile(pkglib.src):
print("MacPackage: dependency does not exist: '%s'" % pkglib.src)
continue
pkglib.dependencies = get_depends1(pkglib.src)
_deps_lv.update({d: pkglib.src for d in pkglib.dependencies})
# libraries for next level are the absolute dependencies at current level
libs_lv = _deps_lv
#END while
return libraries
def install_qt_plugins(qt_plugins:list, pkgdirs:dict):
""" install the required Qt plugins """
print("%s: Copy required Qt plugins from '%s':" % (TITLE, qt_framework_root))
pkg_plugins_dir = pkgdirs['root'] + '/' + pkgdirs['FW_qt_plugin']
mkdir(pkg_plugins_dir)
plugins = []
for plg in qt_plugins:
# full path of the plugin;
# eg. '/opt/qt@5/plugins/platforms/libqcocoa.dylib'
plgpth0 = pkgdirs['qt_root'] + '/' + pkgdirs['qt_plugins_rel_dir'] + '/' + plg
# copy the plugin to the same directory under the _package_ plugins dir;
# eg. '<Package>/PlugIns/platforms/libqcocoa.dylib'
pth = pkg_plugins_dir + '/' + plg
pth_dir = os.path.dirname(pth)
mkdir(pth_dir)
copyfile(plgpth0, pth)
# add Qt plugin to the list of initial binaries
plugins.append(pth)
return plugins
#========================================
_extra_libs="@MACPKG_EXTRA_LIBS@"
pkg_root = sys.argv[1] # root dir, eg. '/tmp/bornagain.app/Contents'
main_exe = pkg_root + "/MacOS/bornagain"
extra_libs = splat(_extra_libs) # TODO: use ';' as splitting
print("==>[L%i]" % level)
for ref, parent_lib in libs_lv.items():
print("[L%i] %s <= %s"
% (level, ref.replace(pkg_dirs['root'], PKGROOT),
parent_lib.src.replace(pkg_dirs['root'], PKGROOT)
if parent_lib and parent_lib.src else 'None'))
# parents for the next iteration
libs_lv = find_dependencies_level(libs_lv, pkg_libs)
# TODO:
# if no source file is found for a ref., make another attempt
# to find the source via the reference table
# for lib in (l for l in libs_lv.values() if l and not l.src):
# lib.src = dylibRPATHs.resolve_by_table(lib.ref)
print("==>END.\n")
return pkg_libs
def find_destinations(pkg_libs, pkg_dirs, libs_init) -> (dict, dict):
""" Determine destinations for the binaries in the package """
# regex to obtain Qt framework subdirectory
# e.g., "/usr/local/Cellar/qt/6.5.1_3/lib/QtWidgets.framework/Versions/A/QtWidgets"
# => ['QtWidgets.framework', '/Versions/A/QtWidgets']
qt_fw_rpath_rx = re.compile(r'.+/(Qt\w+.framework)/(.+)', re.IGNORECASE)
# Qt framework destination
pkg_qt_framework_dir = pkg_dirs['root'] + '/' + pkg_dirs['FW_qt']
# Qt plugins destination
pkg_qt_plugins_dir = os.path.realpath(pkg_dirs['root'] + '/' + pkg_dirs['FW_qt_plugin'])
# extra libraries destination
# eg. 'usr/local/opt/libA.9.dylib' => '<Package>/Library/libA.9.dylib'
# the name of the libraries must be the same as the library ids
pkg_exlib_dir = os.path.realpath(pkg_dirs['root'] + '/' + pkg_dirs['exlib'])
# core libraries destination
# eg. 'buildir/lib/CoreLib.so' => '<Package>/lib/CoreLib.so'
# the name of the libraries must be the same as the library ids
pkg_corelib_dir = os.path.realpath(pkg_dirs['root'] + '/' + pkg_dirs['corelib'])
# map {source-path => destination-path}
pkg_copy_tbl = {p.src: p.dst for p in pkg_libs.values() if p.dst}
# map {destination-path => LibraryFile}
pkg_dst_tbl = dict()
for ref, lib in pkg_libs.items():
# neglect libraries which have no source path or
# those which have already a destination
if not lib.src or lib.dst: continue
basename_new = os.path.basename(lib.ID)
# make new library ids:
# eg. '/usr/opt/lib/libtiff.6.dylib' => '@rpath/libtiff.6.dylib'
# '@loader_path/../lib/libicudata.73.dylib' => '@rpath/libicudata.73.dylib'
if basename_new: lib.ID_new = "@rpath/" + basename_new
# Qt framework libraries
if lib.dtype == 'framework_qt':
_mg = qt_fw_rpath_rx.match(lib.src).groups()
# eg. destination = <PKG-ROOT>/Frameworks/qt/QtWidgets.framework/Versions/A/QtWidgets"
_libID = _mg[0] + '/' + basename_new
# eg. '/opt/qt/QtCore.framework/Versions/A/QtCore' =>
# '@rpath/QtCore.framework/Versions/A/QtCore'
lib.ID_new = "@rpath/" + _libID
lib.dst = pkg_qt_framework_dir + '/' + _libID
elif lib.dtype == 'plugin_qt':
# full source path of the plugin; eg. '/opt/qt@5/plugins/platforms/libqcocoa.dylib'.
# full destination path of the plugin (same directory under the _package_ plugins dir);
# eg. '<Package>/PlugIns/platforms/libqcocoa.dylib'
lib.dst = pkg_qt_plugins_dir + '/' \
+ removeprefix(lib.src, pkg_dirs['qt_root_plugins_dir'] + '/')
elif lib.dtype == 'core_lib':
lib.dst = pkg_corelib_dir + '/' + basename_new
elif lib.dtype == 'extra_lib':
lib.dst = pkg_exlib_dir + '/' + basename_new
# dependencies with absolute path
elif lib.dtype == 'abs':
lib.dst = pkg_exlib_dir + '/' + basename_new
# dependencies with relative '@loader_path' reference for which a source path is found
elif lib.dtype == 'rel@loader_path':
if lib.src in pkg_copy_tbl:
lib.dst = pkg_copy_tbl[lib.src]
else:
lib.dst = pkg_exlib_dir + '/' + basename_new
# dependencies with '@rpath' reference for which a source path is found
elif lib.dtype == 'rel@rpath':
if lib.src in pkg_copy_tbl:
lib.dst = pkg_copy_tbl[lib.src]
else:
lib.dst = pkg_exlib_dir + '/' + basename_new
if not lib.dst: continue
# verify that when the source is the same, the destinations is the same, regardless of the ref.
if lib.src in pkg_copy_tbl and lib.dst != pkg_copy_tbl[lib.src]:
raise AssertionError("Multiple destinations found for a single source file"
"'%s': %s != %s" % (lib.src, lib.dst, pkg_copy_tbl[lib.src]))
pkg_copy_tbl[lib.src] = lib.dst
pkg_dst_tbl[lib.dst] = lib
return pkg_copy_tbl, pkg_dst_tbl
def reference_destinations(pkg_libs:dict) -> dict:
""" Determine destinations for the references """
# map {basename => destination-path}
pkg_basename_dst_tbl = {os.path.basename(p.dst): p.dst
for p in pkg_libs.values() if p.dst}
# map {reference => destination-path}
pkg_ref_dst_tbl = dict()
for lib in pkg_libs.values():
for dep in lib.dependencies:
# attempt to obtain the destination from package library
dep_lib = pkg_libs.get(dep)
# ignore Python framework files
if dep_lib and dep_lib.dtype == "framework_py": continue
_dst = dep_lib.dst if dep_lib else None
# otherwise, attempt to find the destination upon the basename
pkg_ref_dst_tbl[dep] = _dst \
or pkg_basename_dst_tbl.get(os.path.basename(dep)) or ''
return pkg_ref_dst_tbl
def determine_rpaths(pkg_libs:dict, pkg_dst_tbl:dict, pkg_ref_dst_tbl:dict) -> (dict, tuple):
# map {reference_initial => reference_new}
pkg_ref_tbl = dict()
# map {destination path => LibraryFile}
pkg_dst_lib = {l.dst: l for l in pkg_dst_tbl.values()}
for ref, lib in ((r, l) for r, l in pkg_dst_tbl.items() if l.dst):
rpaths_ini = set(lib.rpaths) # initial RPATHs
rpaths_new = set() # new RPATHs
# record references conversions
if lib.ID_new: pkg_ref_tbl[ref] = lib.ID_new
for dep in lib.dependencies:
_dep_dst = pkg_ref_dst_tbl.get(dep)
if not _dep_dst: continue
# find the proper RPATH
_rel_path = pkg_dst_lib[_dep_dst].ID_new.replace("@rpath/", "")
_rpth = find_rpath(lib.dst, _dep_dst, _rel_path)
rpaths_new.add(_rpth.rstrip('/'))
# new RPATHs should be different from the initial ones
lib.rpaths = rpaths_new.difference(rpaths_ini)
#-- determine proper Python RPATHs
# find all Python framework binaries
py_dir = py_lib = py_version = py_rpaths = None
py_framework_files = {l.ref: l for l in pkg_libs.values()
if l.dtype == "framework_py"}
if not py_framework_files:
return pkg_ref_tbl, (py_dir, py_lib, py_version, py_rpaths)
py_dir, py_lib = PythonFramework.get_python_dependence(py_framework_files.keys())
py_rpaths, py_version = PythonFramework.get_python_framework_path(py_dir + '/' + py_lib)
for ref, lib in py_framework_files.items():
lib.rpaths.update(py_rpaths)
# eg. '/opt/Python/Python.framework/Version/A/Python' => '@rpath/libpython3.9.dylib'
pkg_ref_tbl[ref] = "@rpath/" + py_lib
return pkg_ref_tbl, (py_dir, py_lib, py_version, py_rpaths)
#------------------------------------------------------------------------------80
_core_libs = "@MACPKG_CORE_LIBS@" # core libraries and executables
_extra_libs = "@MACPKG_EXTRA_LIBS@"
main_exe_src = "@MACPKG_MAIN_EXE@"
pkg_root = os.path.dirname(sys.argv[1] + '/') # root dir, eg. '/tmp/bornagain.app/Contents'
core_libs = splat(_core_libs, ';')
extra_libs = splat(_extra_libs, ';')
# eg. input Qt dir = '/usr/local/opt/qt@5/lib/cmake/Qt'
# => '/usr/local/opt/qt@5'
qtdir = "@Qt_DIR@"
qtdir = "@Qt_DIR@/"
# Qt plugins paths relative to Qt root dir
qt_plugins_rel_dir = "@MACPKG_QT_PLUGINS_RELDIR@"
qt_framework_root = qtdir
qt_plugins_rel_dir = "@MACPKG_QT_PLUGINS_RELDIR@/"
qt_framework_root = os.path.dirname(qtdir)
qt_framework_plugins_dir = os.path.dirname(qt_framework_root + '/' + qt_plugins_rel_dir)
# list of required Qt plugins
_qt_plugins = "@MACPKG_QT_PLUGINS@"
qt_plugins_dir = "@MACPKG_QT_PLUGINS_DIR@"
qt_plugins = splat(_qt_plugins) # TODO: use ';' as splitting
TITLE="* MacOS Package"
qt_plugins = splat(_qt_plugins, ';')
if not pkg_root:
raise ValueError("%s: Error: Provide the root directory of the package." % TITLE)
if not main_exe_src:
raise ValueError("%s: Error: Main executable does not exist." % TITLE)
if not core_libs:
raise ValueError("%s: Error: Provide the core libraries of the package." % TITLE)
if not qt_framework_root:
raise ValueError("%s: Error: Provide the root directory of the Qt framework." % TITLE)
if not main_exe:
raise ValueError("%s: Error: Main executable does not exist." % TITLE)
print("%s: package root = '%s'" % (TITLE, pkg_root))
print("%s: main executable = '%s'" % (TITLE, main_exe))
print("%s: main executable = '%s'" % (TITLE, main_exe_src))
print("%s: core libraries = '%s'" % (TITLE, core_libs))
print("%s: extra libraries = '%s'" % (TITLE, extra_libs))
print("%s: Qt framework root = '%s'" % (TITLE, qt_framework_root))
print("%s: Qt plugings dir = '%s'" % (TITLE, qt_plugins_dir))
#-- directories (relative to root) which define the package structure
fwdir = "Frameworks"
pkgdirs = {
"lib": "lib", # main library dir
pkg_dirs = {
#-- absolute paths
"root": pkg_root, # package root dir
"qt_root": qt_framework_root,
"qt_root_plugins_dir": qt_framework_plugins_dir,
"main_exe_file": os.path.basename(main_exe_src), # basename of main exe
#-- relative paths
"exe": "MacOS", # main executable dir
"corelib": "lib", # core library dir
"exlib": "Library", # external libraries dir
"FW": fwdir, # frameworks dir
"FW_qt": fwdir, # Qt framework dir
"FW_qt": fwdir + '/Qt', # Qt framework dir
"FW_qt_plugin": qt_plugins_dir, # Qt plugins dir
"root": pkg_root, # package root dir
"qt_root": qt_framework_root,
"qt_plugins_rel_dir": qt_plugins_rel_dir
}
#-- copy extra libraries to the Framework-libraries dir;
# the name of the libraries must be the same as the library ids.
if extra_libs:
dst = pkgdirs['root'] + '/' + pkgdirs['exlib']
print("%s: Copy extra libraries to '%s':" % (TITLE, dst))
mkdir(dst)
for lib in extra_libs:
# eg. 'usr/local/opt/libA.9.dylib' -> '<Package>/Library/libA.9.dylib'
fnm = dylib_id(lib)
copyfile(lib, dst + '/' + os.path.basename(fnm))
#-- collect a list of binaries which are already placed in the package
libs_init = [main_exe] \
+ find_dylibs(pkgdirs['root'] + '/' + pkgdirs['lib']) \
+ find_dylibs(pkgdirs['root'] + '/' + pkgdirs['exlib'])
qtplugins = install_qt_plugins(qt_plugins, pkgdirs)
libs_init.extend(qtplugins)
print("%s: Initially installed binaries under '%s':" % (TITLE, pkgdirs['root']))
#-- main executable
_main_exe_dst = pkg_dirs['root'] + '/' + pkg_dirs['exe'] + '/' + pkg_dirs['main_exe_file']
# map {reference/abs. path => LibraryFile}
pkg_libs = {main_exe_src: LibraryFile(dtype='main_exe', src=main_exe_src,
dst=pkg_dirs['root'] + '/' + pkg_dirs['exe'] + '/' + pkg_dirs['main_exe_file'])}
#-- core libraries
pkg_libs.update({l: LibraryFile(dtype='core_lib', src=l) for l in core_libs})
#-- extra libraries
pkg_libs.update({l: LibraryFile(dtype='extra_lib', src=l) for l in extra_libs})
#-- copy Qt plugins to the corresponding dir
pkg_libs.update({pkg_dirs['qt_root_plugins_dir'] + '/' + l:
LibraryFile(dtype='plugin_qt', src=l) for l in qt_plugins})
#-- collect a list of binaries plus those which are already placed in the package
libs_init = tuple(pkg_libs.keys())
#-- print package information
print("%s: package directories:" % TITLE)
for lbl, val in pkg_dirs.items():
print(" %s: %s" % (lbl, val))
print("%s: Initial binaries:" % TITLE)
for fnm in libs_init:
# eg., '+ lib/libA.dylib'
print(" + '%s'" % removeprefix(fnm, pkgdirs['root'] + '/'))
print(" + '%s'" % removeprefix(fnm, pkg_dirs['root'] + '/'))
#-- find the dependencies of the binaries
pkg_libs = find_dependencies(libs_init, pkgdirs)
sys.exit()
pkg_libs = find_dependencies(pkg_libs, pkg_dirs, level_max=10)
#-- determine the destination of libraries in the package
# map {source-path => destination-path}
pkg_src_dst_tbl, pkg_dst_tbl = find_destinations(pkg_libs, pkg_dirs, libs_init)
# print all discovered dependencies
print("%s: All dependencies:" % TITLE)
# a sorted list of dependencies
for lib in pkg_libs.values():
print(" + '%s' [%s]" % (lib.src, lib.ref))
print("dependencies:\n%s\n" % lib.dependencies)
sys.exit()
#-- distinguish absolute and relative references within dependencies
abs_refs = []
rel_refs = []
py_refs = []
fw_refs = []
for ref in refs_all:
_ref = ref.lower()
if 'python' in _ref and 'boost' not in _ref:
# Python dependencies must be considered separately (exclude 'libboost_python')
# eg. '/opt/py@3.9/Frameworks/Python.framework/Versions/3.9/Python'
py_refs.append(ref)
elif _ref.startswith('@'):
# relative reference; eg. '@rpath/foo.dylib'
rel_refs.append(ref)
elif '.framework/' in _ref:
# Frameworks must be considered separately
# eg. '/opt/qt@5/lib/QtGui.framework/Versions/5/QtGui'
fw_refs.append(ref)
else:
# absolute reference; eg. '/usr/opt/libA.so'
abs_refs.append(ref)
#-- copy all absolute dependencies to the package
dst = pkgdirs['root'] + '/' + pkgdirs['exlib']
mkdir(dst)
print("%s: Copy external libraries to '%s':" % (TITLE, dst))
# pkglib: map{ library reference => destination full-path }
# e.g., /usr/local/opt/libA.dylib => /pkg-root/Library/libA.dylib
pkglib = dict()
for ref in abs_refs:
pth = dst + '/' + os.path.basename(ref) # destination full-path
pkglib[ref] = pth
copyfile(ref, pth)
#-- copy all framework dependencies to the package
# Qt framework
qt_fwdir = pkgdirs['root'] + '/' + pkgdirs['FW_qt']
mkdir(qt_fwdir)
print("%s: Copy Qt-framework libraries to '%s':" % (TITLE, qt_fwdir))
# extract framework path:
# eg. '/usr/local/opt/qt@5/lib/QtWidgets.framework/Versions/5/QtWidgets (...)'
# => 'QtWidgets.framework/Versions/5/QtWidgets'
framework_re = re.compile(r'.+/([^/]+\.[Ff]ramework/[^\s]+).*')
for ref in fw_refs:
# only Qt framework is considered
if os.path.basename(ref).startswith('Qt'):
# eg., copy '/opt/qt@5/lib/QtGui.framework/Versions/5/QtGui'
# to '<Frameworks>/Qt/QtGui.framework/Versions/5/QtGui'
qtfwdir0 = framework_re.match(ref).group(1)
pth = qt_fwdir + '/' + qtfwdir0
mkdir(os.path.dirname(pth))
pkglib[ref] = pth
copyfile(ref, pth)
else:
print("Framework '%s' neglected." % ref)
# Add relatively-referenced Qt framework;
# e.g., QtDBus framework referenced as `@rpath/QtDBus.framework/Versions/A/QtDBus`
print("%s: Add relatively-referenced Qt framework libraries:" % TITLE)
qt_exlib_dir = pkgdirs['root'] + '/' + pkgdirs['exlib']
for ref in rel_refs:
# select only Qt relative references
if os.path.basename(ref).startswith('Qt'):
# '@rpath/QtDBus.framework/Versions/A/QtDBus' => 'QtDBus.framework/Versions/A/QtDBus'
ref_qt = removeprefix(ref, '@rpath/')
qt_src = qt_framework_root + '/lib/' + ref_qt
qt_dst = qt_fwdir + '/' + ref_qt
qt_dst_dir = os.dirname(qt_dst)
pkglib["REL-" + qt_dst] = qt_dst
mkdir(qt_dst_dir)
copyfile(qt_src, qt_dst)
# add the *1st-order optional* dependencies of the Qt library (if any)
# to the same folder as that of the Qt library
# deps1 = $(dylib_deps "$qt_dst" | sed -nE "s;[[:blank:]]*(/.+/opt/.+)[[:blank:]]\(.+;\1;p")
has_deps = 0
for libpth_ in deps1:
has_deps = 1
# '/usr/local/opt/libA.1.2.dylib' => 'libA.1.2.dylib'
libname_ = os.path.basename(libpth_)
# copy the library to the external library dir of the package
libdst = qt_exlib_dir + '/' + libname_
pkglib[libpth_] = libdst
copyfile(libpth_, libdst)
#-- collect all package binaries for later process
pkgbins = sorted(rm_list_duplicates([main_exe] + list(pkglib.values()) + libs_init))
print("%s: All binaries:" % TITLE)
# a sorted list of binaries
for lib in pkgbins:
print(" + '" + removeprefix(lib, pkgdirs['root'] + '/'))
#-- adjust references to libraries
print("%s: Adjust references to libraries:" % TITLE)
libdir = pkgdirs['root'] + '/' + pkgdirs['lib']
rpaths = dict()
bin_deps = dict()
for bin in pkgbins:
rpaths_tmp = set()
bin_deps[bin] = set(dylib_deps(bin))
bindir = os.path.dirname(bin) # abs. dir of target binary
# abspth0 = original abs. full-path of the library
# abspth_pkg = abs. full-path of the library in the package
for abspth0, abspth_pkg in pkglib.items():
# if the binary does not depend on the current library, do nothing
if not abspth0 in bin_deps[bin]: continue
# change the library reference in the binary
# eg. '/usr/local/opt/lib/libA.5.dylib' => '@rpath/libA.5.dylib'
libname = os.path.basename(abspth_pkg) # library filename
libdir = os.path.dirname(abspth_pkg) # abs. dir of the referenced library
librelpth = "" # rel. path of the library
# make a framework-relative path for the Qt framework libraries
# eg. '/opt/qt@5/lib/QtGui.framework/Versions/5/QtGui'
# => 'QtGui.framework/Versions/5'
if libname.startswith('Qt'):
# rm framework root dir from the beginning;
# eg. '/opt/qt@5/lib/QtGui.framework/Versions/5/QtGui'
# => 'QtGui.framework/Versions/5/QtGui'
librelpth = removeprefix(abspth_pkg, qt_fwdir + '/')
# rm filename from the end
# eg. 'QtGui.framework/Versions/5/QtGui'
# => 'QtGui.framework/Versions/5'
librelpth = os.path.dirname(librelpth)
ref_new = libname
# prepend with library rel. path, if any
if not librelpth:
ref_new = librelpth + '/' + ref_new
subp.check_output(["install_name_tool", bin, "-change",
abspath0, "@rpath/" + ref_new], encoding='utf-8')
# make a proper RPATH to refer to the library within the package
# eg. '@loader_path/../Frameworks/Qt/'
rpath = "@loader_path/" + find_rpath(bindir, libdir, librelpth)
rpaths_tmp.add(rpath.strip())
# store a duplicateless list of rpaths needed for the binary,
# only if some rpaths are set.
# NOTE: libraries under the package lib dir. often need
# an extra RPATHs '../Library'.
if bin.startswith(libdir + '/'):
rpaths_tmp.add('@loader_path/../' + pkgdirs['exlib'])
if rpaths_tmp:
rpaths[bin] = rpaths_tmp
# find the Python dependence for the *main* libraries
libs_main = find_dylibs(pkgdirs['root'] + '/' + pkgdirs['lib'])
libs_pydep = list() # list of Python-dependent libraries
for lib in libs_main:
# get the first element the list Python dependences (all others must be the same)
_pydep = get_python_dependence(bin_deps[lib])
# record the list of Python-dependent libraries
if not _pydep[1]:
libs_pydep.append(lib)
# NOTE: zsh array indexing starts at 1 (unless option KSH_ARRAYS is set)
if not _pydep[1]:
raise RuntimeError("No Python dependence found in the binaries.")
pydepends_path=_pydep[0]
pydepends_filename=_pydep[1]
pydepends_fullpath = pydepends_path + '/' + pydepends_filename
# relative Python reference; e.g. '@rpath/libpython3.11.dylib'
if pydepends_path.startswith('@'):
py_fmwk_rpaths = get_python_framework_path_rel(pydepends_fullpath)
for lib in sorted(pkg_libs.values(), key=lambda l: (l.dtype, l.src)):
print(" + [%s]: %s {%s} => %s" %
(lib.dtype,
lib.src.replace(pkg_root, PKGROOT),
lib.ref.replace(pkg_root, PKGROOT),
lib.dst.replace(pkg_root, PKGROOT) if lib.dst else 'None'))
print("%s: Installation table:" % TITLE)
for src in sorted(pkg_src_dst_tbl.keys()):
dst = pkg_src_dst_tbl.get(src)
print(" + %s => %s" %
(src, dst.replace(pkg_root, PKGROOT) if dst else 'None'))
#-- determine proper RPATHs for each library
print("%s: Building reference-destination table..." % TITLE)
pkg_ref_dst_tbl = reference_destinations(pkg_libs)
for ref, dst in pkg_ref_dst_tbl.items():
if not dst:
raise AssertionError("%s: reference '%s' is not resolved" % (TITLE, ref))
pkg_ref_tbl, (py_dir, py_lib, py_version, py_rpaths) = determine_rpaths(pkg_libs, pkg_dst_tbl, pkg_ref_dst_tbl)
print("%s: Python dependence of package libraries:" % TITLE)
if py_lib:
print(" + path: '%s'" % py_dir)
print(" + library: '%s'" % py_lib)
print(" + version: '%s'" % py_version)
print(" + framework paths:")
for pth in py_rpaths:
print(" - %s" % pth)
else:
py_fmwk_rpaths = get_python_framework_path(pydepends_fullpath)
for lib in libs_pydep:
rpaths[lib].extend(py_fmwk_rpaths)
print("%s: Python dependence for the main libraries in '%s/%s':"
% (TITLE, pkgdirs['root'], pkgdirs['lib']))
print(" + path: '%s'" % pydepends_path)
print(" + library: '%s'" % pydepends_filename)
print(" + framework paths:")
for pth in py_fmwk_rpaths:
print(" - '%s'" % pth)
print("%s: Add proper RPATHs to the binaries:" % TITLE)
for bin, rpaths_bin in rpaths.items():
print(" No Python dependence found.")
print("%s: Reference conversion table:" % TITLE)
for ref_old, ref_new in pkg_ref_tbl.items():
print(" + %s => %s" % (ref_old, ref_new))
print("%s: Library RPATHs:" % TITLE)
for lib in (l for l in pkg_dst_tbl.values() if l.rpaths):
print(" + %s: %s" % (lib.src.replace(pkg_root, PKGROOT),
lib.rpaths))
#-- copy binaries to their package destinations
print("%s: Install package files..." % TITLE)
for dst, lib in pkg_dst_tbl.items():
mkdirs(os.path.dirname(dst))
copyfile(lib.src, dst)
#-- apply RPATHs
print("%s: Add proper RPATHs to the binaries..." % TITLE)
for lib in (l for l in pkg_dst_tbl.values() if l.rpaths):
# eg. RPATHS for 'lib/libA.dylib': ../Library , ../Frameworks/Qt
if not rpaths_bin: continue
# eg. install_name_tool libA.so -add_rpath RPATH1 -add_rpath RPATH2 ...
cmd_list = ["install_name_tool", bin]
for cmd in [["-add_rpath", r] for r in rpaths_bin]:
cmd_list.extend(cmd)
out = subp.check_output(cmd_list, encoding='utf-8')
print("%s: Done." % TITLE)
_cmd_list = ["install_name_tool", lib.dst]
for cmd in (["-add_rpath", r] for r in lib.rpaths):
_cmd_list.extend(cmd)
# execute the command
subp.check_output(_cmd_list)
# modify the library references;
# eg. '/usr/local/opt/foo.dylib' => '@rpath/foo.dylib'
print("%s: Change library references in binaries..." % TITLE)
for lib in (l for l in pkg_dst_tbl.values() if l.dependencies):
# eg. install_name_tool libA.so -change REF1_old REF1_new -change REF2_old REF2_new ...
_cmd_list = ["install_name_tool", lib.dst]
for ref_old in lib.dependencies:
ref_new = pkg_ref_tbl[ref_old]
if ref_old != ref_new: _cmd_list.extend(["-change", ref_old,ref_new])
# execute the command
subp.check_output(_cmd_list)
#-- END
print("%s: Done.\n" % TITLE)
Loading