From 0200d8d30235ffb061a6cc44ca9ccb556dfa73cf Mon Sep 17 00:00:00 2001
From: Mick Jordan <mick.jordan@oracle.com>
Date: Wed, 5 Oct 2016 18:03:07 -0700
Subject: [PATCH] more complete handling of rpath'ed libraries

---
 com.oracle.truffle.r.native/fficall/Makefile  |  9 +-
 .../gnur/Makefile.libs                        | 29 +++---
 mx.fastr/mx_copylib.py                        | 88 +++++++++++++++----
 mx.fastr/mx_fastr.py                          |  1 +
 4 files changed, 96 insertions(+), 31 deletions(-)

diff --git a/com.oracle.truffle.r.native/fficall/Makefile b/com.oracle.truffle.r.native/fficall/Makefile
index de4d9866cd..414f1c4d75 100644
--- a/com.oracle.truffle.r.native/fficall/Makefile
+++ b/com.oracle.truffle.r.native/fficall/Makefile
@@ -49,9 +49,12 @@ all: $(R_LIB) $(BOOTJNI_LIB)
 
 $(R_LIB): objs 
 ifeq ($(OS_NAME),Darwin)
-	$(DYLIB_LD) $(DYLIB_LDFLAGS) -Wl,-rpath,$(FASTR_LIB_DIR) -o $(R_LIB) $(wildcard lib/*.o) -L$(FASTR_LIB_DIR) -lRblas -lRlapack  -lpcre -lz $(VERSION_FLAGS)
+	$(DYLIB_LD) $(DYLIB_LDFLAGS) -Wl,-rpath,@loader_path/ -o $(R_LIB) $(wildcard lib/*.o) -L$(FASTR_LIB_DIR) -lRblas -lRlapack  -lpcre -lz $(VERSION_FLAGS)
 	install_name_tool -change libRblas.dylib @rpath/libRblas.dylib $(R_LIB)
 	install_name_tool -change libRlapack.dylib @rpath/libRlapack.dylib $(R_LIB)
+	install_name_tool -id @rpath/libR.dylib $(R_LIB)
+# check if we captured libpcre/libz, rpath those in libR
+	mx rupdatelib $(FASTR_LIB_DIR)
 else
 	$(DYLIB_LD) $(DYLIB_LDFLAGS) -Wl,-rpath,$(FASTR_LIB_DIR) -o $(R_LIB) $(wildcard lib/*.o) -L$(FASTR_LIB_DIR) -lRblas -lRlapack  -lpcre -lz
 endif
@@ -62,6 +65,9 @@ objs:
 
 $(BOOTJNI_LIB): bootobjs
 	$(DYLIB_LD) $(DYLIB_LDFLAGS) -o $(BOOTJNI_LIB) src/jniboot/jniboot.o $(VERSION_FLAGS)
+ifeq ($(OS_NAME),Darwin)
+	install_name_tool -id @rpath/libjniboot.dylib $(BOOTJNI_LIB)
+endif
 
 bootobjs:
 	$(MAKE) -C src/jniboot all
@@ -70,4 +76,5 @@ clean:
 	$(MAKE) -C src/common clean
 	$(MAKE) -C src/jni clean
 	rm -rf $(R_LIB)
+	rm -rf $(BOOTJNI_LIB)
 	
diff --git a/com.oracle.truffle.r.native/gnur/Makefile.libs b/com.oracle.truffle.r.native/gnur/Makefile.libs
index 7c29f4354a..7463102b12 100644
--- a/com.oracle.truffle.r.native/gnur/Makefile.libs
+++ b/com.oracle.truffle.r.native/gnur/Makefile.libs
@@ -33,14 +33,14 @@ endif
 
 BLAS_TARGET := $(FASTR_LIB_DIR)/libRblas$(DYLIB_EXT)
 LAPACK_TARGET := $(FASTR_LIB_DIR)/libRlapack$(DYLIB_EXT)
-# at a minimum we need to know where libpcre/libz are located,
-# to keep the Java side simpler, we (may) copy them to $(FASTR_LIB_DIR)
-# unless they are found in system dirs
-#PCRE_TARGET := $(FASTR_LIB_DIR)/libpcre$(DYLIB_EXT)
-#Z_TARGET := $(FASTR_LIB_DIR)/libz$(DYLIB_EXT)
-.PHONY: all pcre_target z_target
+# at a minimum we need to know where libpcre/libz/libgfortran/libquadmath are located,
+# to keep the Java side simpler, we (may) copy them to $(FASTR_LIB_DIR) unless
+# they were found in the standard system locations
+OTHER_LIB_TARGETS = pcre z gfortran quadmath gcc_s
 
-all: $(FASTR_LIB_DIR) $(BLAS_TARGET) $(LAPACK_TARGET) pcre_target z_target
+.PHONY: all other_lib_targets
+
+all: $(FASTR_LIB_DIR) $(BLAS_TARGET) $(LAPACK_TARGET) other_lib_targets
 
 $(FASTR_LIB_DIR):
 	mkdir -p $(FASTR_LIB_DIR)
@@ -53,20 +53,19 @@ $(LAPACK_TARGET): $(GNUR_HOME)/lib/libRlapack$(DYLIB_EXT)
 ifeq ($(OS_NAME),Darwin)
 # libRblas depends on libgfortran, libquadmath
 # libRlapack depends on libgfortran, libquadmath, libRblas, libR
-# use @loader_path to make references relocatable
+# use @rpath to make references relocatable
 	install_name_tool -change libRblas.dylib @rpath/libRblas.dylib $(LAPACK_TARGET)
 	install_name_tool -change libR.dylib @rpath/libR.dylib $(LAPACK_TARGET)
 	install_name_tool -id @rpath/libRblas.dylib $(BLAS_TARGET)
 	install_name_tool -id @rpath/libRlapack.dylib $(LAPACK_TARGET)
 endif
 
-pcre_target: 
-	mx rcopylib pcre $(FASTR_LIB_DIR)
-	
-z_target: 
-	mx rcopylib z $(FASTR_LIB_DIR)
+other_lib_targets:
+	for target in $(OTHER_LIB_TARGETS); do \
+		mx rcopylib $$target $(FASTR_LIB_DIR) || exit 1; \
+	done
 	
 clean:
-	rm -f $(BLAS_TARGET) $(LAPACK_TARGET) $(PCRE_TARGET) $(Z_TARGET)
+	rm -f $(BLAS_TARGET) $(LAPACK_TARGET) 
+	rm -f $(foreach target,$(OTHER_LIB_TARGETS),$(wildcard $(FASTR_LIB_DIR)/lib$(target).*))
 
-		
diff --git a/mx.fastr/mx_copylib.py b/mx.fastr/mx_copylib.py
index b52609cebf..9a64d80939 100644
--- a/mx.fastr/mx_copylib.py
+++ b/mx.fastr/mx_copylib.py
@@ -26,6 +26,21 @@ import subprocess
 import shutil
 import mx
 
+def _darwin_extract_realpath(lib, libpath):
+    '''
+    If libpath has a dependency on lib, return the path in the library, else None
+    '''
+    try:
+        output = subprocess.check_output(['otool', '-L', libpath])
+        lines = output.split('\n')
+        for line in lines[1:]:
+            if lib in line:
+                parts = line.split(' ')
+                return parts[0].strip()
+        return None
+    except subprocess.CalledProcessError:
+        mx.abort('copylib: otool failed')
+
 def _copylib(lib, libpath, target):
     '''
     Just copying libxxx.so/dylib isn't sufficient as versioning is involved.
@@ -34,15 +49,7 @@ def _copylib(lib, libpath, target):
     Unfortunately getting that info is is OS specific.
     '''
     if platform.system() == 'Darwin':
-        try:
-            output = subprocess.check_output(['otool', '-L', libpath])
-            lines = output.split('\n')
-            for line in lines[1:]:
-                if lib in line:
-                    parts = line.split(' ')
-                    real_libpath = parts[0].strip()
-        except subprocess.CalledProcessError:
-            mx.abort('copylib: otool failed')
+        real_libpath = _darwin_extract_realpath(lib, libpath)
     else:
         try:
             output = subprocess.check_output(['objdump', '-p', libpath])
@@ -55,10 +62,20 @@ def _copylib(lib, libpath, target):
             mx.abort('copylib: otool failed')
     # copy both files
     shutil.copy(real_libpath, target)
+    libpath_base = os.path.basename(libpath)
     os.chdir(target)
-    if os.path.exists(os.path.basename(libpath)):
-        os.remove(os.path.basename(libpath))
-    os.symlink(os.path.basename(real_libpath), os.path.basename(libpath))
+    if libpath != real_libpath:
+        # create a symlink
+        if os.path.exists(libpath_base):
+            os.remove(libpath_base)
+        os.symlink(os.path.basename(real_libpath), libpath_base)
+    # On Darwin we change the id to use @rpath
+    if platform.system() == 'Darwin':
+        try:
+            subprocess.check_call(['install_name_tool', '-id', '@rpath/' + libpath_base, libpath_base])
+        except subprocess.CalledProcessError:
+            mx.abort('copylib: install_name_tool failed')
+    # TODO @rpath references within the library?
     mx.log('copied ' + lib + ' library from ' + libpath + ' to ' + target)
 
 def copylib(args):
@@ -80,14 +97,55 @@ def copylib(args):
     if os.environ.has_key('PKG_LDFLAGS_OVERRIDE'):
         parts = os.environ['PKG_LDFLAGS_OVERRIDE'].split(' ')
         ext = '.dylib' if platform.system() == 'Darwin' else '.so'
-        name = 'lib' + args[0] + ext
+        lib_prefix = 'lib' + args[0]
+        plain_libpath = lib_prefix + ext
         for part in parts:
             path = part.strip('"').lstrip('-L')
             for f in os.listdir(path):
-                if name == f:
+                if f.startswith(lib_prefix):
+                    if os.path.exists(os.path.join(path, plain_libpath)):
+                        f = plain_libpath
                     target_dir = args[1]
-                    if not os.path.exists(os.path.join(target_dir, name)):
+                    if not os.path.exists(os.path.join(target_dir, f)):
                         _copylib(args[0], os.path.join(path, f), args[1])
                     return 0
 
     mx.log(args[0] + ' not found in PKG_LDFLAGS_OVERRIDE, assuming system location')
+
+def updatelib(args):
+    '''
+    If we captured a library then, on Darwin, we patch up the references
+    in the target library passed as argument to use @rpath.
+    args:
+      0 directory containing library
+    '''
+    ignore_list = ['R', 'Rblas', 'Rlapack', 'jniboot']
+
+    def ignorelib(name):
+        for ignore in ignore_list:
+            x = 'lib' + ignore + '.dylib'
+            if x == name:
+                return True
+        return False
+
+    libdir = args[0]
+    cap_libs = []
+    libs = []
+    for lib in os.listdir(libdir):
+        if not os.path.islink(os.path.join(libdir, lib)):
+            libs.append(lib)
+        if ignorelib(lib) or os.path.islink(os.path.join(libdir, lib)):
+            continue
+        cap_libs.append(lib)
+    # for each of the libs, check whether they depend
+    # on any of the captured libs, @rpath the dependency if so
+    for lib in libs:
+        targetlib = os.path.join(libdir, lib)
+        for cap_lib in cap_libs:
+            try:
+                real_libpath = _darwin_extract_realpath(cap_lib, targetlib)
+                if real_libpath and not '@rpath' in real_libpath:
+                    cmd = ['install_name_tool', '-change', real_libpath, '@rpath/' + cap_lib, targetlib]
+                    subprocess.check_call(cmd)
+            except subprocess.CalledProcessError:
+                mx.abort('update: install_name_tool failed')
diff --git a/mx.fastr/mx_fastr.py b/mx.fastr/mx_fastr.py
index bbb01999d9..d880ef32dd 100644
--- a/mx.fastr/mx_fastr.py
+++ b/mx.fastr/mx_fastr.py
@@ -545,6 +545,7 @@ _commands = {
     'installpkgs' : [mx_fastr_pkgs.installpkgs, '[options]'],
     'mkgramrd': [mx_fastr_mkgramrd.mkgramrd, '[options]'],
     'rcopylib' : [mx_copylib.copylib, '[]'],
+    'rupdatelib' : [mx_copylib.updatelib, '[]'],
     }
 
 mx.update_commands(_fastr_suite, _commands)
-- 
GitLab