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

RPATH support using ld wrapper (WIP) #1613

Merged
merged 6 commits into from
Nov 14, 2016
Merged
Show file tree
Hide file tree
Changes from 5 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
7 changes: 7 additions & 0 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version
from easybuild.tools.modules import ROOT_ENV_VAR_NAME_PREFIX, VERSION_ENV_VAR_NAME_PREFIX, DEVEL_ENV_VAR_NAME_PREFIX
from easybuild.tools.modules import get_software_root, modules_tool
from easybuild.tools.rpath import prepare_ld_wrapper
from easybuild.tools.package.utilities import package
from easybuild.tools.repository.repository import init_repository
from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME
Expand Down Expand Up @@ -1523,6 +1524,12 @@ def prepare_step(self):
# prepare toolchain: load toolchain module and dependencies, set up build environment
self.toolchain.prepare(self.cfg['onlytcmod'], silent=self.silent)

self.log.debug("prepare_step: PATH %s" % os.environ['PATH'])
if build_option('rpath'):
# Setup the environment and copy wrapper script into path
prepare_ld_wrapper()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, shouldn't we do this in the toolchain.prepare() itself? keep things nicely together?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see easybuild/tools/toolchain/toolchain.py:prepare


self.log.debug("prepare_step after rpath : PATH %s" % os.environ['PATH'])
# guess directory to start configure/build/install process in, and move there
self.guess_start_dir()

Expand Down
97 changes: 97 additions & 0 deletions easybuild/scripts/ld_wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/bin/bash
#
# Chandan Basu, cbasu@nsc.liu.se.
# Consider this piece of script GPL.
#
# Time-stamp: <2015-02-12 16:00:00>

# This is a linker-wrapper.
#
# The wrapped linker gets lib paths from compiler
# (e.g., gcc, gfortran, icc, ifort etc.) and adds
# rpaths to binary.

SCRIPTNAME=$(basename $0)
ALLSCRIPTS=( $( which -a $SCRIPTNAME ) )
$EB_LD_VERBOSE && echo "found scriptname: $SCRIPTNAME allscripts: ${ALLSCRIPTS[1]}"
LDORIG=${ALLSCRIPTS[1]}
LDWRAPPER=$EB_LD_FLAG
LINKER=$EB_LINKER_NAME ## not yet implemented
EB_LD_VERBOSE=${EB_LD_VERBOSE:-false}


$EB_LD_VERBOSE && echo "INFO: linking with rpath "

if [ -z "$LDWRAPPER" ] || [ "$LDWRAPPER" == 0 ]; then
## call the system linker
$LDORIG "$@"
exit
elif [ "$LDWRAPPER" == 1 ]; then
## Default rpath-ing option.
exclude_lib_paths=("/lib" "/lib64" "/usr" "/home" "/tmp" "/opt" "/proj")
elif [ "$LDWRAPPER" == 2 ]; then
## More aggressive rpath-ing
exclude_lib_paths=("/tmp" "/opt")
fi

L=""
lib_array=()
dir=`pwd`
for (( i=${#@}; i >= 0; i-- )); do
if [[ ${!i} == "--enable-new-dtags" ]]; then
## we are removing this flag if passed as it creates a copy of rpath to runpath.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't we at the mercy of what the default is set to here? We probably should (always) be adding --disable-new-dtags to the arguments as well as removing --enable-new-dtags

## If runpath exists in the binary it can be controlled by LD_LIBRARY_PATH.
## We want only rpath in the binary and no runpath.
## If the user wants to use runpath he should disable linker warpper
## by using EB_LD_FLAG=0
set -- "${@:1:$(( $i - 1 ))}" "${@:$(( $i + 1 ))}"
fi
done

for i in `echo $*`
do
x=`echo $i| cut -c -3`
if [ "$x" == "-L/" -o "$x" == "-L." ] ; then
x=`echo $i| cut -c 3-`
if [ -d "$x" ]; then
p=`cd $x; pwd -P`
lib_array=( "${lib_array[@]}" "$p" )
fi
fi
done

lib_array=( $(for x in "${lib_array[@]}"
do
echo "$x"
done | sort -u) )

for y in "${exclude_lib_paths[@]}"
do
lib_array=( $(for x in "${lib_array[@]}"
do
if [[ "${x:0:${#y}}" != "$y" ]]; then
echo "$x"
fi
done))
done

#### check extra libpaths and add to the lib_array ####
extra_lib_paths=(${NSC_LD_EXTRA_LIBPATH//:/ })
lib_array=( "${lib_array[@]}" "${extra_lib_paths[@]}" )
#######################################################

RPATH=""
for x in "${lib_array[@]}"
do
if [ "$RPATH" == "" ]; then
L=$x
RPATH="-rpath=$x"
else
L=$L:$x
RPATH="$RPATH -rpath=$x"
fi
done

$EB_LD_VERBOSE && echo "INFO: linking with rpath and NSC symbols "
$EB_LD_VERBOSE && echo "INFO: LDORIG: $LDORIG RPATH : $RPATH @: $@ ::"
$LDORIG "-rpath=$L" "$@"
1 change: 1 addition & 0 deletions easybuild/tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
'read_only_installdir',
'rebuild',
'robot',
'rpath',
'sequential',
'set_gid_bit',
'skip_test_cases',
Expand Down
10 changes: 10 additions & 0 deletions easybuild/tools/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,16 @@ def package_options(self):
self.log.debug("package_options: descr %s opts %s" % (descr, opts))
self.add_group_parser(opts, descr)

def rpath_options(self):
# rpath Options
descr = ("RPATH options", "changes linking to include rpaths")

opts = OrderedDict({
'rpath': ("Enable RPATH support", None, 'store_true', False),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you expect other options here?

if not, this shouldn't be an option group by itself?

})
self.log.debug("rpath_options: descr %s opts %s" % (descr, opts))
self.add_group_parser(opts, descr)

def easyconfig_options(self):
# easyconfig options (to be passed to easyconfig instance)
descr = ("Options for Easyconfigs", "Options to be passed to all Easyconfig.")
Expand Down
57 changes: 57 additions & 0 deletions easybuild/tools/rpath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""A module to setup and use an ld wrapper to support RPATH"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing license header + author


import os
import copy
import shutil
import stat
import tempfile
from vsc.utils import fancylogger
from easybuild.tools.filetools import adjust_permissions

_log = fancylogger.getLogger('tools.package')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not package, rpath


orig_os_environ = copy.deepcopy(os.environ)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have copies of the original environment in various places already, no need to keep another?

# RPATH is linux only
ld_wrapper_script_loc = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "scripts", "ld_wrapper.sh"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a constant, so name it LD_WRAPPER_SCRIPT_LOC


DEBUG = True


def prepare_ld_wrapper():
"""
Copy wrapper from framework for each iteration, a bit expensive, but might give flexibility
"""
_log.debug("rpath: preparing ld wrapper script")
wrapper_dir = tempfile.mkdtemp(prefix='eb-ldwrapper-')
# copy wrapper script from framework
wrapper_ld = os.path.join(wrapper_dir, "ld")
wrapper_ld_gold = os.path.join(wrapper_dir, "ld.gold")
shutil.copy(ld_wrapper_script_loc, wrapper_ld)
shutil.copy(ld_wrapper_script_loc, wrapper_ld_gold)
adjust_permissions(wrapper_ld, stat.S_IXUSR, add=True)
adjust_permissions(wrapper_ld_gold, stat.S_IXUSR, add=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for linker in ['ld', 'ld.gold']:
    wrapper = os.path.join(wrapper_dir, linker)
    shutil.copy(ld_wrapper_script_loc, wrapper)
    adjust_permissions(wrapper, stat.S_IXUSR, add=True)


# put wrapper script in PATH
# TODO: get path to existing ld and put it in EB_LD
os.environ['PATH'] = os.pathsep.join([wrapper_dir] +
[x for x in os.environ.get('PATH', '').split(os.pathsep) if len(x) > 0])

os.environ['EB_LD_FLAG'] = "1"
if DEBUG:
os.environ['EB_LD_VERBOSE'] = 'true'


def teardown_ld_wrapper():
"""
Return environment
"""
os.environ = copy.deepcopy(orig_os_environ)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the environment is reset for every new build already, I don't think we need to worry about this?



def check_rpath_support():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not called anywhere?

"""
If there are any special checks we need to do to make sure this will work they should
go here. Incuding whether experimental enabled to begin with.
"""

_log.experimental("Support for setting RPATH for dependencies.")