Skip to content

add support for RPATH linking (REVIEW)#1942

Merged
boegel merged 102 commits intoeasybuilders:developfrom
boegel:rpath
Nov 14, 2016
Merged

add support for RPATH linking (REVIEW)#1942
boegel merged 102 commits intoeasybuilders:developfrom
boegel:rpath

Conversation

@boegel
Copy link
Member

@boegel boegel commented Oct 4, 2016

(continuation of the work started in #1613 by @rjeschmi)

With --rpath enabled, EasyBuild will generate a bash wrapper script for each compiler and linker command (e.g. icc, icpc, ifort, ld and ld.gold) during the toolchain prepare phase, where a couple of things are hardcoded into:

  • the name of the command being wrapped
  • the path to the original command
  • the path to a Python utility script (rpath_args.py)
  • the path to a log file specific to the wrapper script

rpath_args.py takes care of determining which -rpath option should be passed down to the original command $RPATH), based on the specified command line arguments for that command.
It may also filter/tweak the specified command line arguments that will actually be passed down to the original command ($CMD_ARGS).
The bash wrapper script will run rpath_args.py, evaluate the statements it prints out (which define $RPATH and $CMD_ARGS), and then call the original command using $RPATH and $CMD_ARGS).

The location of these wrapper scripts is prepended to $PATH to they get preference over the compiler/linker commands they wrap around.

The sanity check was extended to check the output of ldd and readelf -d on all dynamically linked executables and libraries (that reside in bin, lib and lib64 subdirectories) in case --rpath was used.

TODO:

[ ] more testing

  • in combination with option that avoids that $LD_LIBRARY_PATH is defined by generated modules (TODO)
    [ ] fix the FIXMEs in rpath_args.py
    [ ] end-to-end test for --rpath
    [ ] documentation

Feedback welcome!

cc @geimer, @pescobar

rjeschmi and others added 30 commits January 29, 2016 06:29
@boegel boegel changed the title add support for RPATH linking (WIP) add support for RPATH linking (REVIEW) Nov 11, 2016
@boegel
Copy link
Member Author

boegel commented Nov 11, 2016

OK, time for one more update...

With the current state combined with easybuilders/easybuild-easyblocks#1031 (and easybuilders/easybuild-easyblocks#1030, but I think that's mostly irrelevant), eb --rpath can build the whole foss/2016a stack + all dependencies for OpenFOAM 3.0.0 (i.e. CMake, Boost, METIS, ...) without issues! \o/
The corresponding list of modules:

M4/1.4.17
Autoconf/2.69
Automake/1.15
libtool/2.4.6
flex/2.5.39
Autotools/20150215
Bison/3.0.4
zlib/1.2.8
binutils/2.25
GCCcore/4.9.3
M4/1.4.17-GCCcore-4.9.3
flex/2.5.39-GCCcore-4.9.3
Bison/3.0.4-GCCcore-4.9.3
zlib/1.2.8-GCCcore-4.9.3
binutils/2.25-GCCcore-4.9.3
GCC/4.9.3-2.25
OpenBLAS/0.2.15-GCC-4.9.3-2.25-LAPACK-3.6.0
numactl/2.0.11-GCC-4.9.3-2.25
hwloc/1.11.2-GCC-4.9.3-2.25
OpenMPI/1.10.2-GCC-4.9.3-2.25
gompi/2016a
FFTW/3.3.4-gompi-2016a
ScaLAPACK/2.0.2-gompi-2016a-OpenBLAS-0.2.15-LAPACK-3.6.0
foss/2016a
bzip2/1.0.6-foss-2016a
flex/2.5.39-foss-2016a
zlib/1.2.8-foss-2016a
M4/1.4.17-foss-2016a
ncurses/6.0-foss-2016a
SCOTCH/6.0.4-foss-2016a
CMake/3.4.3-foss-2016a
Boost/1.60.0-foss-2016a
Bison/3.0.4-foss-2016a
libreadline/6.3-foss-2016a
METIS/5.1.0-foss-2016a-32bitIDX

The OpenFOAM build itself is still running...

So, unless further testing reveals any serious problems, this is the version that is going to go in under --experimental in EasyBuild v3.0.

@wpoely86 do you mind giving this implementation a code review?

Once this is merged, I'll open up an issue to track any problems that pop up with the --rpath support that is included in EasyBuild v3.0.

dirpaths = [
os.path.join(self.installdir, 'bin'),
os.path.join(self.installdir, 'lib'),
os.path.join(self.installdir, 'lib64'),
Copy link
Member

Choose a reason for hiding this comment

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

some software (think OpenFOAM) doesn't follow this. In principle, you need to search everywhere.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have reworked this so sanity_check_rpath (now a public method) accepts an argument rpath_dirs so we can specialise this in easyblocks (e.g. for OpenFOAM) as needed. We can also consider adding an easyconfig parameter like sanity_check_rpath_dirs in addition.

# ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped
if "dynamically linked" in out:
# check whether all required libraries are found via 'ldd'
out, ec = run_cmd("ldd %s" % path, simple=False)
Copy link
Member

Choose a reason for hiding this comment

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

it's not because a library is found, that we are using the correct one. A library can be both installed in the system and by EB (zlib, ...)

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think his intention was to check this very deeply. It is just a sanity check.

I agree that a good check is needed before this goes into production. It is pretty difficult though, since EB doesn't enforce linking only within EB.

You would need to have information about each dependency library to ensure those are linked against the correct EB version and ignore ones that are OS deps. I don't think this is reliable given current information in the easyconfigs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, but at least the library is either found via RPATH or in a default system location.

This is the best we can do in terms of a sanity check for RPATH imho, there's no guarantee here indeed...

I'm open to suggestions on how to check this more strictly (while keeping it relatively lightweight).

Copy link
Member

Choose a reason for hiding this comment

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

I would at the minimum print a warning about linkage to non EB paths. We can whitelist all glibc things but any other library needs to be looked after. It's not necessarily an error, but it needs to be checked.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, but I won't block this PR for that, we can do that as a follow-up, since it'll take a while before we get the white-listed right probably.

Also, that's sort of orthogonal to --rpath anyway, we should always be doing that to catch unintended linking to system libraries (but then we also need to take into account --filter-deps, which further complicates things).

I opened an issue for this, see #1991

@@ -0,0 +1,80 @@
#!/usr/bin/env python
Copy link
Member

Choose a reason for hiding this comment

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

-E?

Copy link
Member Author

Choose a reason for hiding this comment

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

That doesn't work, you can only pass a single argument to a shebang.

And anyway, this doesn't matter much, we call the script with python rpath_args.py ... anyway (i.e. with the python that is used to run eb, i.e. sys.executable).

Copy link
Member

Choose a reason for hiding this comment

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

env passes the arguments. env python -E will just work.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, ok, seems like it does work.

But still, this is irrelevant, since we call this script with python rpath_args.py, bypassing the shebang entirely...

return res


cmd = sys.argv[1]
Copy link
Member

Choose a reason for hiding this comment

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

put in if __main__


# output: statement to define $RPATH
if lib_paths:
print "export RPATH='%s=%s'" % (rpath_flag, ':'.join(sorted(lib_paths)))
Copy link
Member

Choose a reason for hiding this comment

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

use print( ) (py3)

# add -rpath flags in front
cmd_args = cmd_args_rpath + cmd_args

if not version_mode:
Copy link
Member

Choose a reason for hiding this comment

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

what's the meaning of this version mode anyway?

Copy link
Member Author

Choose a reason for hiding this comment

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

see above, commands like gcc -v should not get broken by injecting -rpath flags


# logging function
function log {
echo "($$) [$(date "+%%Y-%%m-%%d %%H:%%M:%%S")] $1" >> %(rpath_wrapper_log)s
Copy link
Member

Choose a reason for hiding this comment

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

explain why the %% are here.

Copy link
Contributor

Choose a reason for hiding this comment

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

It does seem like it might be more obvious if the file had an extension other than .sh (.sh.in, or template or something). Is there not a saner template system for python used already by EB?

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, will do.

Copy link
Member Author

Choose a reason for hiding this comment

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

@rjeschmi I like your suggestion to name it .sh.in, I'll apply that change.

@@ -0,0 +1,26 @@
#!/bin/bash
Copy link
Member

Choose a reason for hiding this comment

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

why both a bash and python wrapper?

Copy link
Member Author

Choose a reason for hiding this comment

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

because iterating of the command line arguments in bash is damn nasty to implement imho, it can be done way cleaner in a Python script (which is easier to unit test too).

Copy link
Member

Choose a reason for hiding this comment

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

so again, why both a bash and python wrapper? Use a purely python one? Forks are costly.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure that calling the original compiler/linker command is going to work when called from within a Python shell, I suspect that may lead to problems.

Total overhead of this bash+Python wrapper approach is 10-15%, which is very acceptable imho, you'll barely notice.

return var_defs, hits


def find_eb_script(script_name):
Copy link
Member

Choose a reason for hiding this comment

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

lets cache this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Pfft, I don't think that's worth the effort. find_eb_script is called once for the bash wrapper script, once for rpath_args.py per installation (by toolchain.prepare()). Caching isn't going to make a significant difference here (even when multiple installations are being done in one session).

"""Return list of relevant compilers for this toolchain"""

if self.name == DUMMY_TOOLCHAIN_NAME:
c_comps = ['gcc', 'g++']
Copy link
Member

Choose a reason for hiding this comment

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

cc, c++?

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess we could, but if we don't the RPATH sanity check should fail on builds done with dummy, and I haven't seen that happening.
We can do this when the need arises?

We could also (always!) wrap cc and c++ with a script that breaks the compilation, so we know when these are used. Because if they are, the compilers provided by a non-dummy toolchain won't be used...

Copy link
Member

Choose a reason for hiding this comment

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

Also fine by me. Some projects use cc/c++ before gcc/g++

Copy link
Member Author

Choose a reason for hiding this comment

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

see #1993

Copy link
Contributor

@rjeschmi rjeschmi left a comment

Choose a reason for hiding this comment

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

will work on some testing...

# ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped
if "dynamically linked" in out:
# check whether all required libraries are found via 'ldd'
out, ec = run_cmd("ldd %s" % path, simple=False)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think his intention was to check this very deeply. It is just a sanity check.

I agree that a good check is needed before this goes into production. It is pretty difficult though, since EB doesn't enforce linking only within EB.

You would need to have information about each dependency library to ensure those are linked against the correct EB version and ignore ones that are OS deps. I don't think this is reliable given current information in the easyconfigs.


# logging function
function log {
echo "($$) [$(date "+%%Y-%%m-%%d %%H:%%M:%%S")] $1" >> %(rpath_wrapper_log)s
Copy link
Contributor

Choose a reason for hiding this comment

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

It does seem like it might be more obvious if the file had an extension other than .sh (.sh.in, or template or something). Is there not a saner template system for python used already by EB?

arg = args[idx]

# if command is run in 'version check' mode, make sure we don't include *any* -rpath arguments
if arg in ['-v', '-V', '--version', '-dumpversion']:
Copy link
Contributor

Choose a reason for hiding this comment

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

I think these options are related to flags that some build sent to ld or ld.gold and are being caught. The version mode below I think is to catch the bug related to rpath GCC that was noticed elsewhere.

@boegel
Copy link
Member Author

boegel commented Nov 12, 2016

@wpoely86 I consider this good to go now. This is certainly not 100% finished, but that's exactly why we've pushed this under --experimental.

It will allow people to easily provide feedback on what we have in place now though, since it will be included in the upcoming EasyBuild v3.0.

Please submit an approved review, so this can be merged?

rpath_filter = rpath_filter.split(',')
else:
# don't RPATH system library locations by default
rpath_filter = ['/lib.*', '/usr/lib.*']
Copy link
Contributor

Choose a reason for hiding this comment

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

Remember that on a multiarch system (Debian, Ubuntu), system libraries may also be located in /usr/lib/x86_64-linux-gnu (and i386-linux-gnu, and aarch64-linux-gnu, and armhf-linux-gnu, and ...).

Copy link
Member Author

Choose a reason for hiding this comment

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

@geimer that's fine, those paths will match the /usr/lib* regex, so we don't inject -rpath flags for those locations, which is as intended

@boegel
Copy link
Member Author

boegel commented Nov 13, 2016

@wpoely86 now updated to included build dir and tmpdir in RPATH filter (passed down from prepare_step to toolchain.prepare)


# list of paths to include in RPATH filter;
# only include builddir if we're not building in installation directory
if os.getenv('TMPDIR'):
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't we better use tempfile.tempdir?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think you mean tempfile.gettempdir(), but sure, good point.

@boegel
Copy link
Member Author

boegel commented Nov 13, 2016

Thanks for the thorough review @wpoely86 and @rjeschmi!

@boegel boegel mentioned this pull request Nov 13, 2016
18 tasks
@boegel boegel merged commit 6c87b1f into easybuilders:develop Nov 14, 2016
@boegel boegel deleted the rpath branch November 14, 2016 23:47
@EricDeveaud
Copy link

EricDeveaud commented Nov 16, 2016

Hello,

maybee it will looks a really dumb comment, but why don't include LIBRARY_PATH and LD_RUN_PATH export in modulefiles

we use this kind of approach on our current module setup.
egg boost C modulefile defines the following

those environment varaiables are natively used by the linker.

cixi:~ > module show boost/1_61_0
cixi:~ > show boost
-------------------------------------------------------------------
/local/gensoft2/devmodules/boost/1_61_0:

module-whatis    Set environnement for boost (1_61_0) 
module-whatis    topic_3316 
prepend-path     LIBRARY_PATH /local/gensoft2/lib/boost/1_61_0/lib 
prepend-path     LD_RUN_PATH /local/gensoft2/lib/boost/1_61_0/lib 
prepend-path     -d   LDFLAGS -L/local/gensoft2/lib/boost/1_61_0/lib 
prepend-path     CMAKE_LIBRARY_PATH /local/gensoft2/lib/boost/1_61_0/lib 
prepend-path     C_INCLUDE_PATH /local/gensoft2/lib/boost/1_61_0/include 
prepend-path     CPLUS_INCLUDE_PATH /local/gensoft2/lib/boost/1_61_0/include 
prepend-path     CMAKE_INCLUDE_PATH /local/gensoft2/lib/boost/1_61_0/include 
prepend-path     -d   CPPFLAGS -I/local/gensoft2/lib/boost/1_61_0/include 
setenv           BOOST_ROOT /local/gensoft2/lib/boost/1_61_0 
-------------------------------------------------------------------

loading boost and compiling versus this one provide binaries with accurate boost rpath
eg ufasta, part of masurca-3.1.3 (this one was compiled versus boost-1_61_1 and gcc-4.9.0

/local/gensoft2/exe/masurca/3.1.3/bin/ufasta:
        linux-vdso.so.1 =>  (0x00007ffc461dc000)
        libboost_regex.so.1.61.0 => /local/gensoft2/lib/boost/1_61_0/lib/libboost_regex.so.1.61.0 (0x00007f50efdef000)
        libbz2.so.1 => /lib64/libbz2.so.1 (0x00000034fb800000)
        libz.so.1 => /lib64/libz.so.1 (0x00000034f6800000)
        librt.so.1 => /lib64/librt.so.1 (0x00000034f6000000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00000034f5800000)
        libstdc++.so.6 => /local/gensoft2/exe/gcc/4.9.0/lib64/libstdc++.so.6 (0x00007f50efacc000)
        libm.so.6 => /lib64/libm.so.6 (0x00000034f6400000)
        libgcc_s.so.1 => /local/gensoft2/exe/gcc/4.9.0/lib64/libgcc_s.so.1 (0x00007f50ef8b5000)
        libc.so.6 => /lib64/libc.so.6 (0x00000034f5400000)
        libicudata.so.42 => /usr/lib64/libicudata.so.42 (0x00007f50ee76a000)
        libicui18n.so.42 => /usr/lib64/libicui18n.so.42 (0x00007f50ee3d3000)
        libicuuc.so.42 => /usr/lib64/libicuuc.so.42 (0x00007f50ee081000)
        /lib64/ld-linux-x86-64.so.2 (0x00000034f5000000)

NB there is a "corner case" using this approach, when some rpath instructions are set in upstream compilation mechanism. as ld in this case ignores LD_RUN_PATH and LD_LIBRARY_PATH (man ld explain it pretty well)

edit: typo

@boegel
Copy link
Member Author

boegel commented Nov 23, 2016

@EricDeveaud let's follow up on the LD_RUN_PATH suggestion in easybuilders/easybuild#282

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants