Skip to content
Snippets Groups Projects
Commit 1547cd2f authored by Ammar Nejati's avatar Ammar Nejati Committed by AlQuemist
Browse files

rm unneeded devtools/deploy/mac/shutils.py

parent 5cdfa68c
No related branches found
No related tags found
3 merge requests!2050rebase main on r21/v21.1,!2047<root>/CMakeLists.txt: add 'BornAgain_LIBRARIES' cached variable to store...,!1988Migrate to pyenv Python Platform; Fix MacOS package (Major change)
# ************************************************************************** #
# BornAgain: simulate and fit reflection and scattering
#
# @file shutils.zsh
# @brief A collection of useful zshell functions.
#
# @homepage http://apps.jcns.fz-juelich.de/BornAgain
# @license GNU General Public License v3 or higher (see COPYING)
# @copyright Forschungszentrum Juelich GmbH 2016
# @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
# ************************************************************************** #
# Shell helper functions
# For details of ZSH parameter expansions, consult `man zshexpn` or
# <https://zsh.sourceforge.io/Doc/Release/Expansion.html#Parameter-Expansion-Flags>
import re, subprocess as subp
# turn logging off (0), log to stdout (1)
LOG_FLAG = 1
LOG_TOKEN = ".:"
def log(msg:str):
print("%s %s" % (LOG_TOKEN, msg))
out = subp.check_output(["otool", "-XL", lib], encoding='utf-8')
def dylib_id(libpath:str):
""" return the base library id for the given library """
return subp.check_output(["otool", "-XD", libpath], encoding='utf-8')
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;
# remove the name of the library itself from the references.
# Under MacOS, a Mach-O binary sometimes depends on itself.
# otool output example: ' /usr/local/opt/foo.dylib (compatibility ...)'
basename = os.path.basename(libpath)
basename_rx = re.compile(basename)
deps = subp.check_output(["otool", "-XL", libpath], encoding='utf-8').split('\n')
return [d for d in deps if d and not basename_rx.search(d)]
def rm_list_duplicates(list0:list):
""" remove duplicates from a given list, $@; (separator is a space char) """
return list(set(list0))
def find_common_root(abspath1:str, abspath2:str):
""" find the longest common root of two given _absolute_ paths ($1 and $2) """
if abspath1 == abspath2:
# if paths are equal, the root is equal to either of them
return abspath1
# 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('/')
idxMax1 = len(dirs1)
dirs2 = abspath2.split('/')
idxMax2 = len(dirs2)
common_root = ""
for idx in range(min(idxMax1, idxMax2)):
# extract the head (topmost) directory name from paths
# eg. 'root/lib/opt' => 'root'
head1 = dirs1[idx]
head2 = dirs2[idx]
# if both paths have the same head, then add head to the common root;
# otherwise, the longest common root is already obtained
if head1 != head2: break
# add the common head to the root
common_root += '/' + head1
# 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 (needs extended glob)
# usage: find_rpath(bin_abspath, lib_abspath, lib_relpath)
# example:
# ```
# bin_abspath='/root/usr/opt/bin'
# lib_abspath='/root/usr/Frameworks/Qux/lib'
# lib_relpath='Qux/lib'
# echo $(find_rpath $bin_abspath $lib_abspath $lib_relpath)
# ```
# returns `../../Frameworks`
"""
# 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)
# 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)
# 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, '')
# 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
return libpth_from_bin
def get_depends1(libpath):
""" get 1st-order dependencies for a given file """
# obtain the 'non-system' dependencies for a given library ($1)
# eg. ' /usr/local/opt/foo.1.dylib (compatibility ...)' => '/usr/local/opt/foo.dylib'
# system dependencies pattern: /System/, /Library/, /usr/lib/, /usr/local/lib
deps_ = [d for d in dylib_deps(libpath)
if not re.match(r'/(usr/lib|usr/local/lib|System|Library)/', d.strip())]
deps = list()
for d in deps_:
m_ = re.match('\s*([\w/\.]+)\s+.*', d)
if m_: deps.append(m_.group(1))
return deps
def find_dependencies(libraries):
""" gather all dependencies for the given initial libraries ($@)
up to a maximum (but arbitrary) level.
"""
LEVELMAX = 20 # max. allowed level
log("%s: Find dependencies (up to level %s)..." % (TITLE, LEVELMAX))
# NOTE: Associative arrays are used to keep a set of entries without repetition
all_deps = set() # absolute dependencies
libs_lv = set(libraries) # libraries at the current level
level = 0 # current level nr.
while libs_lv:
level += 1
abs_deps_lv = set() # _absolute_ dependencies at the current level
# avoid going infinitely deep (due to some error)
if level > LEVELMAX:
log("Error: Dependency level %i exceeds the maximum allowed depth (%i)."
% (level, LEVELMAX))
break
# eg. at level 3, print '==>[L3]'
log("==>[L%i]" % level)
# obtain all dependencies at the current level
for lib in libs_lv:
# neglect previously-observed libraries
if lib not in all_deps: continue
log("[L%i] %s" % (level, lib))
for dep in get_depends1(lib):
# store relative dependencies which begin with '@'
# eg. '@rpath/foo.dylib'
if dep.startswith('@'):
all_deps.add(dep)
# abs. path to relative dependencies is not known, therefore
# they should not be added to the current abs. dependencies
continue
# add dependency to the current abs. dependencies
abs_deps_lv.add(dep)
# add libraries of the current level to set of all dependencies;
# libraries at the initial level are neglected.
if level > 1:
for libpath in libs_lv:
all_deps.append(libpath)
# libraries for next level are the absolute dependencies at current level
libs_lv = abs_deps_lv
# return all discovered dependencies
return all_deps.keys()
def get_python_dependence(dependencies):
""" 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 extract the Python dependence;
# eg., '/Frameworks/Python.framework/Versions/3.9/Python'
libdir_re= '[\w\./@-]+'
pylibname_re = 'Python|libpython.+\.dylib'
py_fmwk_re = '(' + libdir_re + ')/(' + pylibname_re + ')\s+'
# regexp to correct the Python dependence; eg.:
# '/Frameworks/Python.framework/Versions/3.9/Python' => 'libpython3.9.dylib'
# '/Frameworks/Python.framework/Versions/3.9/libpython3.9.dylib' => 'libpython3.9.dylib'
pydylib_re = '.*[pP]ython.+[Vv]ersions/([0-9.]+).+'
# obtain the dependencies
pydepends_fullpath = $(echo $@ | sed -nE $py_fmwk_re)
pydepends_filename = $(echo "$pydepends_fullpath" | sed -E $pydylib_re)
# return the Python dependence fullpath and filename
return (pydepends_fullpath, pydepends_filename)
def get_python_framework_path(pydepends_fullpath):
""" produce proper Python framework paths for a given Python dependency ($1) """
# 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 = '(.*)/(Python|libpython).*'
py_fmwk_dir = $(echo "$1" | sed -E $py_fmwk_path_re)
# 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_fullpath.endswith('Python'):
py_fmwk_dir = py_fmwk_dir + '/lib'
# regexp to extract the Python version; eg. '3.9'
pyversion_re = '.+[Vv]ersions/([0-9.]+).*'
pyversion=$(echo "$1" | sed -E $pyversion_re)
# 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")
# 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)
# return a list of possible framework paths
return py_fmwk_rpaths
def get_python_framework_path_rel(pydepends_relpath:str):
""" produce proper Python framework paths for a given _relative_ Python dependency ($1) """
# regexp to extract the Python framework path
# eg. '@rpath/libpython39.dylib' => 'libpython39.dylib'
pyversion_re = '.*/libpython([0-9].[0-9]+).*'
pyversion = $(echo "$1" | sed -E $pyversion_re)
# 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")
# collect proper RPATHs for Python framework
py_fmwk_rpaths = ""
for pth in framework_paths:
py_fmwk_rpaths.append(pth + '/' + py_fmwk_basepath)
# return a list of possible framework paths
return py_fmwk_rpaths
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment