Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5487954
Squash and rebase for 5.0.x branch
ocaisa Mar 10, 2025
67b5a38
Add missing import
ocaisa Mar 10, 2025
4c02482
No need to create the wrapper directory or modify permissions
ocaisa Mar 13, 2025
6684446
Fix updated test
ocaisa Mar 13, 2025
79495df
Add rpath_wrappers_dir build option
ocaisa Mar 14, 2025
f1482cd
Unused import
ocaisa Mar 14, 2025
f88ab4f
Fix tests
ocaisa Mar 14, 2025
8cc4dc3
Merge branch 'easybuilders:develop' into export_rpath_wrappers
ocaisa Apr 1, 2025
224e83a
Merge branch 'easybuilders:develop' into export_rpath_wrappers
ocaisa Apr 2, 2025
dd4d4db
Add warning if wrappers are being overwritten
ocaisa Apr 23, 2025
539bb56
Merge branch 'export_rpath_wrappers' of github.com:ocaisa/easybuild-f…
ocaisa Apr 23, 2025
ef951d9
Add warning if wrappers are being overwritten
ocaisa Apr 23, 2025
64a4b19
remove --rpath-wrappers-dir configuration option
boegel Apr 23, 2025
841b82b
copy rpath_args.py script alongside RPATH wrapper scripts, and use it
boegel Apr 23, 2025
e9e7870
avoid that test_toolchain_prepare_rpath_external produces output
boegel Apr 23, 2025
c863497
make sure that parent directory for RPATH wrappers exists before copy…
boegel Apr 23, 2025
4606da6
use relative path to rpath_args.py script when creating RPATH wrapper…
boegel Apr 23, 2025
62e0a28
only copy rpath_args.py script and use relative path to it when custo…
boegel Apr 23, 2025
f6f5e93
fix excessively long comments
boegel Apr 23, 2025
71cc6eb
Merge pull request #47 from boegel/export_rpath_wrappers
ocaisa Apr 23, 2025
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
13 changes: 9 additions & 4 deletions easybuild/tools/toolchain/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,8 @@ def is_rpath_wrapper(path):
# need to use binary mode to read the file, since it may be an actual compiler command (which is a binary file)
return b'rpath_args.py $CMD' in read_file(path, mode='rb')

def prepare_rpath_wrappers(self, rpath_filter_dirs=None, rpath_include_dirs=None):
def prepare_rpath_wrappers(self, rpath_filter_dirs=None, rpath_include_dirs=None, wrappers_dir=None,
add_to_path=True, disable_wrapper_log=False):
"""
Put RPATH wrapper script in place for compiler and linker commands

Expand All @@ -998,7 +999,10 @@ def prepare_rpath_wrappers(self, rpath_filter_dirs=None, rpath_include_dirs=None
rpath_filter_dirs.append(lib_stubs_pattern)

# directory where all wrappers will be placed
wrappers_dir = os.path.join(tempfile.mkdtemp(), RPATH_WRAPPERS_SUBDIR)
if wrappers_dir is None:
wrappers_dir = os.path.join(tempfile.mkdtemp(), RPATH_WRAPPERS_SUBDIR)
else:
wrappers_dir = os.path.join(wrappers_dir, RPATH_WRAPPERS_SUBDIR)

# must also wrap compilers commands, required e.g. for Clang ('gcc' on OS X)?
c_comps, fortran_comps = self.compilers()
Expand Down Expand Up @@ -1045,7 +1049,7 @@ def prepare_rpath_wrappers(self, rpath_filter_dirs=None, rpath_include_dirs=None
raise EasyBuildError("Refusing the create a fork bomb, which(%s) == %s", cmd, orig_cmd)

# enable debug mode in wrapper script by specifying location for log file
if build_option('debug'):
if build_option('debug') and not disable_wrapper_log:
rpath_wrapper_log = os.path.join(tempfile.gettempdir(), 'rpath_wrapper_%s.log' % cmd)
else:
rpath_wrapper_log = '/dev/null'
Expand All @@ -1064,7 +1068,8 @@ def prepare_rpath_wrappers(self, rpath_filter_dirs=None, rpath_include_dirs=None
adjust_permissions(cmd_wrapper, stat.S_IXUSR)

# prepend location to this wrapper to $PATH
setvar('PATH', '%s:%s' % (wrapper_dir, os.getenv('PATH')))
if add_to_path:
setvar('PATH', '%s:%s' % (wrapper_dir, os.getenv('PATH')))

self.log.info("RPATH wrapper script for %s: %s (log: %s)", orig_cmd, which(cmd), rpath_wrapper_log)
else:
Expand Down
26 changes: 25 additions & 1 deletion easybuild/tools/toolchain/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@
* Kenneth Hoste (Ghent University)
"""
import copy
import os
import re
import sys

import easybuild.tools.toolchain
from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.toolchain.toolchain import Toolchain
from easybuild.tools.environment import setvar
from easybuild.tools.toolchain.toolchain import Toolchain, RPATH_WRAPPERS_SUBDIR
from easybuild.tools.utilities import get_subclasses, import_available_modules, nub


Expand Down Expand Up @@ -151,3 +153,25 @@ def get_toolchain(tc, tcopts, mns=None, tcdeps=None, modtool=None):
tc_inst.set_options(tcopts)

return tc_inst


def export_rpath_wrappers(targetdir, toolchain_name, toolchain_version, rpath_filter_dirs=None,
rpath_include_dirs=None):
tc = get_toolchain({'name': toolchain_name, 'version': toolchain_version}, {})

# Temporarily filter any existing RPATH wrappers from the PATH
orig_paths = os.getenv('PATH')
filtered_paths = [path for path in orig_paths.split(':') if RPATH_WRAPPERS_SUBDIR not in path]
setvar('PATH', ':'.join(filtered_paths))

tc.prepare_rpath_wrappers(
rpath_filter_dirs=rpath_filter_dirs,
rpath_include_dirs=rpath_include_dirs,
wrappers_dir=targetdir,
add_to_path=False,
disable_wrapper_log=True,
)
_log.debug("Installed RPATH wrappers in command specific subdirectories of %s" % (str(targetdir)))

# Restore the PATH
setvar('PATH', orig_paths)
60 changes: 58 additions & 2 deletions test/framework/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
from easybuild.tools.run import run_shell_cmd
from easybuild.tools.systemtools import get_shared_lib_ext
from easybuild.tools.toolchain.mpi import get_mpi_cmd_template
from easybuild.tools.toolchain.toolchain import env_vars_external_module
from easybuild.tools.toolchain.utilities import get_toolchain, search_toolchain
from easybuild.tools.toolchain.toolchain import env_vars_external_module, RPATH_WRAPPERS_SUBDIR
from easybuild.tools.toolchain.utilities import get_toolchain, search_toolchain, export_rpath_wrappers
from easybuild.toolchains.compiler.clang import Clang

easybuild.tools.toolchain.compiler.systemtools.get_compiler_family = lambda: st.POWER
Expand Down Expand Up @@ -3137,6 +3137,62 @@ def test_toolchain_prepare_rpath(self):
self.assertTrue(os.path.samefile(res[1], fake_gxx))
self.assertFalse(any(os.path.samefile(x, fake_gxx) for x in res[2:]))

def test_export_rpath(self):
"""Test tools.toolchain.export_rpath_wrappers()"""

# put fake 'g++' command in place that just echos its arguments
fake_gxx = os.path.join(self.test_prefix, 'fake', 'g++')
write_file(fake_gxx, '#!/bin/bash\necho "$@"')
adjust_permissions(fake_gxx, stat.S_IXUSR)
os.environ['PATH'] = '%s:%s' % (os.path.join(self.test_prefix, 'fake'), os.getenv('PATH', ''))

# enable --rpath for a toolchain so we test against it
init_config(build_options={'rpath': True, 'silent': True})
tc = self.get_toolchain('gompi', version='2018a')
tc.set_options({'rpath': True})
# allow the underlying toolchain to be in a prepared state (which may include rpath wrapping)
tc.prepare()

# export the wrappers to a target location
target_wrapper_dir = os.path.join(self.test_prefix, 'target')
export_rpath_wrappers(targetdir=target_wrapper_dir, toolchain_name='gompi', toolchain_version='2018a',
rpath_filter_dirs=['/filter_path'], rpath_include_dirs=['/include_path'])

# check that wrapper was created
target_wrapper = os.path.join(target_wrapper_dir, RPATH_WRAPPERS_SUBDIR, 'gxx_wrapper', 'g++')
self.assertTrue(os.path.exists(target_wrapper))
# Make sure it is a wrapper
self.assertTrue(b'rpath_args.py $CMD' in read_file(target_wrapper, mode='rb'))
# Make sure it wraps our fake 'g++'
self.assertTrue(fake_gxx.encode(encoding="utf-8") in read_file(target_wrapper, mode='rb'))
# Make sure the wrapper is not in PATH (we export only)
self.assertFalse(any(os.path.samefile(x, target_wrapper) for x in which('g++', retain_all=True)))

# check whether fake g++ was wrapped and that arguments are what they should be
# no -rpath for /path because of rpath filter
mkdir(os.path.join(self.test_prefix, 'foo'), parents=True)
cmd = ' '.join([
target_wrapper,
'${USER}.c',
'-L%s/foo' % self.test_prefix,
'-L/filter_path',
"'$FOO'",
'-DX="\\"\\""',
])
res = run_shell_cmd(cmd, hidden=True)
self.assertEqual(res.exit_code, 0)
expected = ' '.join([
'-Wl,-rpath=/include_path',
'-Wl,--disable-new-dtags',
'-Wl,-rpath=%s/foo' % self.test_prefix,
'%(user)s.c',
'-L%s/foo' % self.test_prefix,
'-L/filter_path',
'$FOO',
'-DX=""',
])
self.assertEqual(res.output.strip(), expected % {'user': os.getenv('USER')})

def test_prepare_openmpi_tmpdir(self):
"""Test handling of long $TMPDIR path for OpenMPI 2.x"""

Expand Down
Loading