Skip to content

Commit ca9b1d4

Browse files
committed
fix handling of quotes in rpath wrapper
In some software the compiler must be invoked with an argument containing a literal single quote which requires escaping in the call. E.g.: `gcc -DFOO=\'value\'` However the current rpath wrapper would remove those single quotes causing errors during compilation or erronous behavior of the compiled binary. Fix by replacing the `eval` of the `rpath_args.py` output by `readarray -t`. An array asignment could be used (`CMD_ARGS=( $(rpath_args.py ...) )`) but that would break up arguments containing spaces. Quotes from the output are kept literally, so quoting to avoid this is not possible. `readarray -t` works and is widely available. Also some minor fixes related to quoting in the bash script template.
1 parent 5661bf5 commit ca9b1d4

File tree

3 files changed

+63
-47
lines changed

3 files changed

+63
-47
lines changed

easybuild/scripts/rpath_args.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@
2525
##
2626
"""
2727
Utility script used by RPATH wrapper script;
28-
output is statements that define the following environment variables
29-
* $CMD_ARGS: new list of command line arguments to pass
30-
* $RPATH_ARGS: command line option to specify list of paths to RPATH
28+
output is a list of arguments to be passed to the wrapped command, one per line
3129
3230
author: Kenneth Hoste (HPC-UGent)
31+
author: Alexander Grund (TU Dresden)
3332
"""
3433
import os
3534
import re
@@ -161,8 +160,12 @@ def is_new_existing_path(new_path, paths):
161160
# add -rpath flags in front
162161
cmd_args = cmd_args_rpath + cmd_args
163162

164-
# wrap all arguments into single quotes to avoid further bash expansion
165-
cmd_args = ["'%s'" % a.replace("'", "''") for a in cmd_args]
166163

167-
# output: statement to define $CMD_ARGS and $RPATH_ARGS
168-
print("CMD_ARGS=(%s)" % ' '.join(cmd_args))
164+
def quote(arg):
165+
"""Quote the argument for use in a shell to avoid further expansion"""
166+
# Wrap in single quotes and replace every single quote by '"'"'
167+
# This terminates the single-quoted string, inserts a literal single quote and starts a new single-quotest-string
168+
return "'" + arg.replace("'", "'\"'\"'") + "'"
169+
170+
171+
print('\n'.join(cmd_args))

easybuild/scripts/rpath_wrapper_template.sh.in

+9-14
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,21 @@
3030
# before actually calling the original compiler/linker command.
3131
#
3232
# author: Kenneth Hoste (HPC-UGent)
33+
# author: Alexander Grund (TU Dresden)
3334

3435
set -e
3536

3637
# logging function
3738
function log {
3839
# escape percent signs, since this is a template script
3940
# that will templated using Python string templating
40-
echo "($$) [$(date "+%%Y-%%m-%%d %%H:%%M:%%S")] $1" >> %(rpath_wrapper_log)s
41+
echo "($$) [$(date "+%%Y-%%m-%%d %%H:%%M:%%S")] $1" >> '%(rpath_wrapper_log)s'
4142
}
4243

4344
# command name
44-
CMD=`basename $0`
45+
CMD=$(basename $0)
4546

46-
log "found CMD: $CMD | original command: %(orig_cmd)s | orig args: '$(echo \"$@\")'"
47+
log "found CMD: $CMD | original command: %(orig_cmd)s | orig args: $*)"
4748

4849
# rpath_args.py script spits out statement that defines $CMD_ARGS
4950
# options for 'python' command (see https://docs.python.org/3/using/cmdline.html#miscellaneous-options)
@@ -52,18 +53,12 @@ log "found CMD: $CMD | original command: %(orig_cmd)s | orig args: '$(echo \"$@\
5253
# * -s: don’t add the user site-packages directory to sys.path;
5354
# * -S: disable the import of the module site and the site-dependent manipulations of sys.path that it entails;
5455
# (once we only support Python 3, we can (also) use -I (isolated mode)
55-
log "%(python)s -E -O -s -S %(rpath_args_py)s $CMD '%(rpath_filter)s' '%(rpath_include)s' $(echo \"$@\")'"
56-
rpath_args_out=$(%(python)s -E -O -s -S %(rpath_args_py)s $CMD '%(rpath_filter)s' '%(rpath_include)s' "$@")
57-
58-
log "rpath_args_out:
59-
$rpath_args_out"
60-
61-
# define $CMD_ARGS by evaluating output of rpath_args.py script
62-
eval $rpath_args_out
56+
log "%(python)s -E -O -s -S %(rpath_args_py)s $CMD '%(rpath_filter)s' '%(rpath_include)s' $*"
57+
readarray -t CMD_ARGS < <( %(python)s -E -O -s -S %(rpath_args_py)s $CMD '%(rpath_filter)s' '%(rpath_include)s' "$@" )
6358

6459
# exclude location of this wrapper from $PATH to avoid other potential wrappers calling this wrapper
65-
export PATH=$(echo $PATH | tr ':' '\n' | grep -v "^%(wrapper_dir)s$" | tr '\n' ':')
60+
export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v "^%(wrapper_dir)s$" | tr '\n' ':')
6661

6762
# call original command with modified list of command line arguments
68-
log "running '%(orig_cmd)s $(echo ${CMD_ARGS[@]})'"
69-
%(orig_cmd)s "${CMD_ARGS[@]}"
63+
log "running: %(orig_cmd)s $(echo "${CMD_ARGS[@]}")"
64+
'%(orig_cmd)s' "${CMD_ARGS[@]}"

test/framework/toolchain.py

+44-26
Original file line numberDiff line numberDiff line change
@@ -2744,9 +2744,12 @@ def test_rpath_args_script(self):
27442744
def test_toolchain_prepare_rpath(self):
27452745
"""Test toolchain.prepare under --rpath"""
27462746

2747+
# Code for a bash script that prints each passed argument on a new line
2748+
BASH_SCRIPT_PRINT_ARGS = '#!/bin/bash\nfor arg in "$@"; do echo "$arg"; done'
2749+
27472750
# put fake 'g++' command in place that just echos its arguments
27482751
fake_gxx = os.path.join(self.test_prefix, 'fake', 'g++')
2749-
write_file(fake_gxx, '#!/bin/bash\necho "$@"')
2752+
write_file(fake_gxx, BASH_SCRIPT_PRINT_ARGS)
27502753
adjust_permissions(fake_gxx, stat.S_IXUSR)
27512754
os.environ['PATH'] = '%s:%s' % (os.path.join(self.test_prefix, 'fake'), os.getenv('PATH', ''))
27522755

@@ -2789,7 +2792,7 @@ def test_toolchain_prepare_rpath(self):
27892792
# Check that we can create a wrapper for a toolchain for which self.compilers() returns 'None' for the Fortran
27902793
# compilers (i.e. Clang)
27912794
fake_clang = os.path.join(self.test_prefix, 'fake', 'clang')
2792-
write_file(fake_clang, '#!/bin/bash\necho "$@"')
2795+
write_file(fake_clang, BASH_SCRIPT_PRINT_ARGS)
27932796
adjust_permissions(fake_clang, stat.S_IXUSR)
27942797
tc_clang = Clang(name='Clang', version='1')
27952798
tc_clang.prepare_rpath_wrappers()
@@ -2852,18 +2855,25 @@ def test_toolchain_prepare_rpath(self):
28522855
'-L%s/foo' % self.test_prefix,
28532856
'-L/bar',
28542857
"'$FOO'",
2855-
'-DX="\\"\\""',
2858+
# C/C++ preprocessor value including a quotes
2859+
r'-DX1="\"\""',
2860+
r'-DX2="\"I\""',
2861+
r"-DY1=\'\'",
2862+
r"-DY2=\'J\'",
28562863
])
28572864
out, ec = run_cmd(cmd)
28582865
self.assertEqual(ec, 0)
2859-
expected = ' '.join([
2866+
expected = '\n'.join([
28602867
'-Wl,--disable-new-dtags',
28612868
'-Wl,-rpath=%s/foo' % self.test_prefix,
28622869
'%(user)s.c',
28632870
'-L%s/foo' % self.test_prefix,
28642871
'-L/bar',
28652872
'$FOO',
2866-
'-DX=""',
2873+
'-DX1=""',
2874+
'-DX2="I"',
2875+
"-DY1=''",
2876+
"-DY2='J'",
28672877
])
28682878
self.assertEqual(out.strip(), expected % {'user': os.getenv('USER')})
28692879

@@ -2888,35 +2898,43 @@ def test_toolchain_prepare_rpath(self):
28882898
for path in paths:
28892899
mkdir(path, parents=True)
28902900
args = ['-L%s' % x for x in paths]
2901+
path_with_spaces = os.path.join(self.test_prefix, 'prefix/with spaces')
2902+
mkdir(path_with_spaces)
2903+
args.append('-L"%s"' % path_with_spaces)
28912904

28922905
cmd = "g++ ${USER}.c %s" % ' '.join(args)
28932906
out, ec = run_cmd(cmd, simple=False)
28942907
self.assertEqual(ec, 0)
28952908

2896-
expected = ' '.join([
2909+
expected = '\n'.join([
28972910
'-Wl,--disable-new-dtags',
2898-
'-Wl,-rpath=%s/tmp/foo/' % self.test_prefix,
2899-
'-Wl,-rpath=%s/prefix/software/stubs/1.2.3/lib' % self.test_prefix,
2900-
'-Wl,-rpath=%s/prefix/software/foobar/4.5/notreallystubs' % self.test_prefix,
2901-
'-Wl,-rpath=%s/prefix/software/zlib/1.2.11/lib' % self.test_prefix,
2902-
'-Wl,-rpath=%s/prefix/software/foobar/4.5/stubsbutnotreally' % self.test_prefix,
2911+
'-Wl,-rpath=%(libdir)s/tmp/foo/',
2912+
'-Wl,-rpath=%(libdir)s/prefix/software/stubs/1.2.3/lib',
2913+
'-Wl,-rpath=%(libdir)s/prefix/software/foobar/4.5/notreallystubs',
2914+
'-Wl,-rpath=%(libdir)s/prefix/software/zlib/1.2.11/lib',
2915+
'-Wl,-rpath=%(libdir)s/prefix/software/foobar/4.5/stubsbutnotreally',
2916+
'-Wl,-rpath=%(path_with_spaces)s',
29032917
'%(user)s.c',
2904-
'-L%s/prefix/software/CUDA/1.2.3/lib/stubs/' % self.test_prefix,
2905-
'-L%s/prefix/software/CUDA/1.2.3/stubs/lib/' % self.test_prefix,
2906-
'-L%s/tmp/foo/' % self.test_prefix,
2907-
'-L%s/prefix/software/stubs/1.2.3/lib' % self.test_prefix,
2908-
'-L%s/prefix/software/CUDA/1.2.3/lib/stubs' % self.test_prefix,
2909-
'-L%s/prefix/software/CUDA/1.2.3/stubs/lib' % self.test_prefix,
2910-
'-L%s/prefix/software/CUDA/1.2.3/lib64/stubs/' % self.test_prefix,
2911-
'-L%s/prefix/software/CUDA/1.2.3/stubs/lib64/' % self.test_prefix,
2912-
'-L%s/prefix/software/foobar/4.5/notreallystubs' % self.test_prefix,
2913-
'-L%s/prefix/software/CUDA/1.2.3/lib64/stubs' % self.test_prefix,
2914-
'-L%s/prefix/software/CUDA/1.2.3/stubs/lib64' % self.test_prefix,
2915-
'-L%s/prefix/software/zlib/1.2.11/lib' % self.test_prefix,
2916-
'-L%s/prefix/software/bleh/0/lib/stubs' % self.test_prefix,
2917-
'-L%s/prefix/software/foobar/4.5/stubsbutnotreally' % self.test_prefix,
2918+
'-L%(libdir)s/prefix/software/CUDA/1.2.3/lib/stubs/',
2919+
'-L%(libdir)s/prefix/software/CUDA/1.2.3/stubs/lib/',
2920+
'-L%(libdir)s/tmp/foo/',
2921+
'-L%(libdir)s/prefix/software/stubs/1.2.3/lib',
2922+
'-L%(libdir)s/prefix/software/CUDA/1.2.3/lib/stubs',
2923+
'-L%(libdir)s/prefix/software/CUDA/1.2.3/stubs/lib',
2924+
'-L%(libdir)s/prefix/software/CUDA/1.2.3/lib64/stubs/',
2925+
'-L%(libdir)s/prefix/software/CUDA/1.2.3/stubs/lib64/',
2926+
'-L%(libdir)s/prefix/software/foobar/4.5/notreallystubs',
2927+
'-L%(libdir)s/prefix/software/CUDA/1.2.3/lib64/stubs',
2928+
'-L%(libdir)s/prefix/software/CUDA/1.2.3/stubs/lib64',
2929+
'-L%(libdir)s/prefix/software/zlib/1.2.11/lib',
2930+
'-L%(libdir)s/prefix/software/bleh/0/lib/stubs',
2931+
'-L%(libdir)s/prefix/software/foobar/4.5/stubsbutnotreally',
2932+
'-L%(path_with_spaces)s',
29182933
])
2919-
self.assertEqual(out.strip(), expected % {'user': os.getenv('USER')})
2934+
self.assertEqual(out.strip(), expected % {'libdir': self.test_prefix,
2935+
'user': os.getenv('USER'),
2936+
'path_with_spaces': path_with_spaces,
2937+
})
29202938

29212939
# calling prepare() again should *not* result in wrapping the existing RPATH wrappers
29222940
# this can happen when building extensions

0 commit comments

Comments
 (0)