Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use relative path in installed libraries #7301

Merged
merged 3 commits into from
Oct 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions drake/bindings/python/pydrake/test/testCommonInstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,12 @@ def testDrakeFindResourceOrThrowInInstall(self):
os.remove(element)
self.assertEqual(os.listdir("."), [tmp_folder])

# Set the correct PYTHONPATH and (DY)LD_LIBRARY_PATH to use the
# correct pydrake module.
# Set the correct PYTHONPATH to use the correct pydrake module.
env_python_path = "PYTHONPATH"
if sys.platform == 'darwin':
env_ld_library = "DYLD_LIBRARY_PATH"
else:
env_ld_library = "LD_LIBRARY_PATH"
tool_env = dict(os.environ)
tool_env[env_python_path] = os.path.abspath(
os.path.join(tmp_folder, "lib", "python2.7", "site-packages")
)
tool_env[env_ld_library] = os.path.abspath(
os.path.join(tmp_folder, "lib")
)
data_folder = os.path.join(tmp_folder, "share", "drake", "drake")
# Call python2 to force using python brew install. Calling python
# system would result in a crash since pydrake was built against
Expand Down
1 change: 1 addition & 0 deletions setup/ubuntu/16.04/install_prereqs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ bash-completion
binutils
cmake
cmake-curses-gui
chrpath
coinor-libipopt-dev
diffstat
doxygen
Expand Down
137 changes: 107 additions & 30 deletions tools/install/install.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
import argparse
import filecmp
import os
import re
import shutil
import stat
import sys
from subprocess import check_output, check_call
from subprocess import check_output, check_call, CalledProcessError

subdirs = set()
prefix = None
libs = {}
list_only = False
# On linux, dynamic libraries may have their version number
# as a suffix (e.g. my_lib.so.x.y.z).
dylib_match = r"(.*\.so)(\.\d+)*$|(.*\.dylib)$"


def needs_install(src, dst):
Expand Down Expand Up @@ -59,9 +63,9 @@ def install(src, dst):
installed = True
else:
print("[Up to date] %s" % dst)

if dst.endswith('.so') or dst.endswith('.dylib'):
basename = os.path.basename(dst)
re_result = re.match(dylib_match, dst)
if re_result is not None:
basename = os.path.basename(re_result.group(0))
# Check that dependency is only referenced once
# in the library dictionary. If it is referenced multiple times,
# we do not know which one to use, and fail fast.
Expand All @@ -72,36 +76,108 @@ def install(src, dst):
libs[basename] = (dst_full, installed)


def fix_library_rpaths():
def fix_libraries_rpaths():
# Only fix libraries that are installed now.
fix_libs = [(k, libs[k][0]) for k in libs.keys() if libs[k][1]]
for basename, dst_full in fix_libs:
# Enable write permissions to allow modification.
os.chmod(dst_full, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH
)
# Update library ID (remove relative path).
stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
if sys.platform == "darwin":
macos_fix_libraries_rpaths(basename, dst_full)
else:
linux_fix_libraries_rpaths(dst_full)


def macos_fix_libraries_rpaths(basename, dst_full):
# Update library ID (remove relative path).
check_call(
['install_name_tool', "-id", "@rpath/" + basename, dst_full]
)
# Check if library dependencies are specified with relative paths.
file_output = check_output(["otool", "-L", dst_full])
for line in file_output.splitlines():
# keep only file path, remove version information.
relative_path = line.split(' (')[0].strip()
# If path is relative, it needs to be replaced by absolute path.
if "@loader_path" not in relative_path:
continue
dep_basename = os.path.basename(relative_path)
# Look for the absolute path in the dictionary of libraries.
if dep_basename not in libs.keys():
continue
lib_dirname = os.path.dirname(dst_full)
diff_path = os.path.relpath(libs[dep_basename][0], lib_dirname)
check_call(
['install_name_tool', "-id", "@rpath/" + basename, dst_full]
['install_name_tool',
"-change", relative_path,
os.path.join('@loader_path', diff_path),
dst_full]
)
# Check if library dependencies are specified with relative paths.
file_output = check_output(["otool", "-L", dst_full])
for line in file_output.splitlines():
# keep only file path, remove version information.
relative_path = line.split(' (')[0].strip()
# If path is relative, it needs to be replaced by absolute path.
if "@loader_path" not in relative_path:
continue
dep_basename = os.path.basename(relative_path)
# Look for the absolute path in the dictionary of libraries.
if dep_basename not in libs.keys():
continue
# Remove RPATH values that contain $ORIGIN. These are from the build
# tree and are irrelevant in the install tree. RPATH is not necessary as
# relative or absolute path to each library is already known.
file_output = check_output(["otool", "-l", dst_full])
for line in file_output.splitlines():
split_line = line.strip().split(' ')
if len(split_line) >= 2 \
and split_line[0] == 'path' \
and split_line[1].startswith('$ORIGIN'):
check_call(
['install_name_tool',
"-change", relative_path,
libs[dep_basename][0],
dst_full]
)
['install_name_tool', "-delete_rpath", split_line[1], dst_full]
)


def linux_fix_libraries_rpaths(dst_full):
# Check that library has an rpath or a runpath tag
try:
check_output(["chrpath", "-l", dst_full])
except OSError as ex:
if ex.errno == 2 and ex.strerror == "No such file or directory":
print("`chrpath` not found. Please run install_prereqs.sh.")
raise ex
except CalledProcessError as ex:
if ex.returncode == 2 and \
ex.output.strip().endswith('no rpath or runpath tag found.'):
# Cannot be modified with `chrpath`, so we skip it.
return
raise ex
# Check if library dependencies are found and not in install prefix.
# The found libraries outside of the install prefix are the system
# libraries and we do not worry about these. We only make sure that
# the installed libraries would be found.
file_output = check_output(["ldd", dst_full])
rpath = set()
for line in file_output.splitlines():
ldd_result = line.strip().split(' => ')
# If library found, then skip.
if len(ldd_result) < 2 or \
not (ldd_result[1] == "not found"
or ldd_result[1].startswith(prefix)):
continue
re_result = re.match(dylib_match, ldd_result[0])
# Look for the absolute path in the dictionary of libraries using the
# library name without its possible version number.
soname, _, _ = re_result.groups()
if soname not in libs.keys():
continue
lib_dirname = os.path.dirname(dst_full)
diff_path = os.path.dirname(
os.path.relpath(libs[soname][0], lib_dirname)
)
rpath.add('$ORIGIN' + '/' + diff_path)
# Replace build tree RPATH with computed install tree RPATH. Build tree
# RPATH are automatically removed by this call. RPATH will contain the
# necessary relative paths to find the libraries that are needed. RPATH
# will typically be set to `$ORIGIN` or `$ORIGIN/../../..` .
if rpath:
str_rpath = ":".join(x for x in rpath)
check_output(
['chrpath',
"-r", str_rpath,
dst_full]
)


def create_java_launcher(filename, classpath, main_class):
# In list-only mode, just display the filename, don't do any real work.
Expand Down Expand Up @@ -136,7 +212,9 @@ done
java %s
""" % (strclasspath, main_class)
launcher_file.write(content)
os.chmod(filename, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
os.chmod(filename, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH |
stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)


def main(args):
global prefix
Expand Down Expand Up @@ -166,9 +244,8 @@ def main(args):
# Execute the install actions.
<<actions>>

# On MacOS, library paths may need to be updated.
if sys.platform == "darwin":
fix_library_rpaths()
# Libraries paths may need to be updated.
fix_libraries_rpaths()


if __name__ == "__main__":
Expand Down