Skip to content
Snippets Groups Projects
Commit 3d9c6196 authored by Paul McCarthy's avatar Paul McCarthy :mountain_bicyclist:
Browse files

Merge branch 'enh/create_wrapper' into 'master'

Enh/create wrapper

See merge request !1
parents e44b46d5 0cef2b16
No related branches found
No related tags found
1 merge request!1Enh/create wrapper
Pipeline #6292 passed
#!/usr/bin/env python
#
# test_create_remove_wrapper.py - Test the createFSLWrapper.sh and
# removeFSLWrapper.sh scripts. Requires Python 3.7.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import os.path as op
import subprocess as sp
import textwrap as tw
import os
import shlex
import shutil
import tempfile
import contextlib
from unittest import mock
BASE_DIR = op.abspath(op.join(op.dirname(__file__), '..'))
CREATE_WRAPPER = op.join(BASE_DIR, 'share', 'fsl', 'sbin', 'createFSLWrapper')
REMOVE_WRAPPER = op.join(BASE_DIR, 'share', 'fsl', 'sbin', 'removeFSLWrapper')
def run(cmd, **kwargs):
return sp.run(shlex.split(cmd), check=True, **kwargs)
@contextlib.contextmanager
def temp_fsldir():
testdir = tempfile.mkdtemp()
prevdir = os.getcwd()
fsldir = op.join(testdir, 'fsl')
wrapperdir = op.join(testdir, 'fsl', 'share', 'fsl', 'bin')
try:
os.chdir(testdir)
os.mkdir(fsldir)
with mock.patch.dict(os.environ, {
'FSLDIR' : fsldir,
'PREFIX' : fsldir,
'FSL_CREATE_WRAPPER_SCRIPTS' : '1'}):
yield fsldir, wrapperdir
finally:
os.chdir(prevdir)
shutil.rmtree(testdir)
def touch(path):
dirname = op.dirname(path)
if not op.exists(dirname):
os.makedirs(dirname)
with open(path, 'wt') as f:
f.write('.')
def test_env_vars_not_set():
"""Test that wrapper scripts are not created if the
FSL_CREATE_WRAPPER_SCRIPTS, FSLDIR, or PREFIX environment variables
are not set.
"""
with temp_fsldir() as (fsldir, wrapperdir):
touch(op.join(fsldir, 'bin', 'test_script'))
env = os.environ.copy()
env.pop('FSL_CREATE_WRAPPER_SCRIPTS')
run(f'{CREATE_WRAPPER} test_script', env=env)
assert not op.exists(op.join(wrapperdir, 'test_script1'))
env = os.environ.copy()
env.pop('FSLDIR')
run(f'{CREATE_WRAPPER} test_script', env=env)
assert not op.exists(op.join(wrapperdir, 'test_script1'))
env = os.environ.copy()
env.pop('PREFIX')
run(f'{CREATE_WRAPPER} test_script', env=env)
assert not op.exists(op.join(wrapperdir, 'test_script1'))
# FSLDIR invalid
env = os.environ.copy()
env['FSLDIR'] = '/some/non-existent/path'
run(f'{CREATE_WRAPPER} test_script', env=env)
assert not op.exists(op.join(wrapperdir, 'test_script1'))
# FSLDIR != PREFIX
env = os.environ.copy()
env['FSLDIR'] = op.join(env['PREFIX'], 'other_fsl')
run(f'{CREATE_WRAPPER} test_script', env=env)
assert not op.exists(op.join(wrapperdir, 'test_script1'))
def test_create_python_wrapper():
"""Test creation of a wrapper script for a python executable"""
with temp_fsldir() as (fsldir, wrapperdir):
script_path = op.join(fsldir, 'bin', 'test_script')
wrapper_path = op.join(wrapperdir, 'test_script')
touch(script_path)
with open(script_path, 'wt') as f:
f.write('#!/usr/bin/env python\n')
f.write('print("hello")\n')
expect = tw.dedent(f"""
#!/usr/bin/env bash
{fsldir}/bin/python -I {script_path} "$@"
""").strip()
run(f'{CREATE_WRAPPER} test_script')
assert op.exists(wrapper_path)
with open(wrapper_path, 'rt') as f:
got = f.read().strip()
assert got == expect
def test_create_other_wrapper():
"""Test creation of a wrapper script for a non-python executable."""
with temp_fsldir() as (fsldir, wrapperdir):
script_path = op.join(fsldir, 'bin', 'test_script')
wrapper_path = op.join(wrapperdir, 'test_script')
touch(script_path)
with open(op.join(fsldir, 'bin', 'test_script'), 'wt') as f:
f.write('#!/usr/bin/env bash\n')
f.write('echo "hello"\n')
expect = tw.dedent(f"""
#!/usr/bin/env bash
{script_path} "$@"
""").strip()
run(f'{CREATE_WRAPPER} test_script')
assert op.exists(wrapper_path)
with open(wrapper_path, 'rt') as f:
got = f.read().strip()
assert got == expect
def test_permissions_preserved():
"""Test that wrapper script has same permissions as wrapped script."""
with temp_fsldir() as (fsldir, wrapperdir):
perms = [0o777, 0o755, 0o644, 0o600, 0o755, 0o700]
script_path = op.join(fsldir, 'bin', 'test_script')
wrapper_path = op.join(wrapperdir, 'test_script')
touch(script_path)
for perm in perms:
os.chmod(script_path, perm)
run(f'{CREATE_WRAPPER} test_script')
stat = os.stat(wrapper_path)
assert (stat.st_mode & 0o777) == perm
def test_create_remove_wrappers():
"""Tests normal usage. """
with temp_fsldir() as (fsldir, wrapperdir):
touch(op.join(fsldir, 'bin', 'test_script1'))
touch(op.join(fsldir, 'bin', 'test_script2'))
run(f'{CREATE_WRAPPER} test_script1 test_script2')
assert op.exists(op.join(wrapperdir, 'test_script1'))
assert op.exists(op.join(wrapperdir, 'test_script2'))
run(f'{REMOVE_WRAPPER} test_script1 test_script2')
assert not op.exists(op.join(wrapperdir, 'test_script1'))
assert not op.exists(op.join(wrapperdir, 'test_script2'))
include:
- project: fsl/fsl-ci-rules
file: .gitlab-ci.yml
stages:
- test
- fsl-ci-pre
- fsl-ci-build
- fsl-ci-deploy
test:
stage: test
image: python:3.7
tags:
- docker
script:
- pip install pytest
- pytest -v .ci
\ No newline at end of file
# FSL base project changelog
## 2011.1 (Wednesday 2nd December 2020)
- Added `creeateFSLWrapper` and `removeFSLWrapper`, utility commands, which
will be used to create isolated wrapper scripts of all FSL executables in a
separate sub-directory, so they can be added to the user `$PATH` without
other executables in `$PREFIX/bin/` causing conflicts (!1).
## 2011.0 (Monday 2nd November 2020)
- Initial release of the FSL `base` project - an amalagamation of `fsl/etc`
and `fsl/config`, which has been adjusted to compile shared libraries with
dynamic linking, and tweaked for use as the base of a `conda`-managed FSL
installation.
......@@ -9,3 +9,10 @@ This project contains the "base" of an FSL installation. It includes:
Prior to FSL 6.0.X, the contents of this project were provided by the
[etc](https://git.fmrib.ox.ac.uk/fsl/etc) and
[config](https://git.fmrib.ox.ac.uk/fsl/config) projects.
## Tests
The `.ci` directory contains a collection of tests which validate the
behaviour of various aspects of the `base` project. All tests are written
in Python, and are executed with `pytest`.
#!/usr/bin/env bash
#
# Create wrapper scripts in $FSLDIR/share/fsl/bin/ which invoke commands that
# are installed in $FSLDIR/bin/.
#
# This script is used to create isolated versions of all executables provided
# by FSL projects, so they can be added to the user $PATH without any other
# executables that are installed into the FSL conda environment (for example,
# python, pip, tcl, etc).
#
# This script should only be invoked when FSL is being installed via the
# fslinstaller script - it is not intended to be used when individual FSL
# projects are explicitly installed into a custom conda environment.
#
# The fslinstaller script should ensure that the FSLDIR and
# FSL_CREATE_WRAPPER_SCRIPTS variables are set appropriately before creating
# the FSL conda environment.
#
# This script is intended to be called by the post-link.sh script of the conda
# recipe for each FSL project that provides executable commands.
#
# Wrapper scripts will only be created if the following conditions are met:
#
# - The $FSLDIR and $PREFIX environment variables are set, and
# are equivalent
# - The $FSL_CREATE_WRAPPER_SCRIPTS environment variable is set and is not
# empty.
#
# Wrapper scripts, and not sym-links, are used to avoid a couple of potential
# problems:
#
# - We need python executables to exclusively use the libraries installed in
# the FSL conda environment. Users may have other Python environments
# activated, and/or libraries installed into a local site-packages
# directory. So we need to invoke the python interpreter in isolated mode,
# with the -I flag:
#
# https://docs.python.org/3/using/cmdline.html#id2
#
# - There is no guarantee that a shell supports shebang lines longer than 127
# characters. Depending on where FSL is installed, it may not be possible
# to have a shebang line pointing to $FSLDIR/bin/python which does not
# exceed 127 characters.
# Names of all executables for which wrapper
# scripts are to be created are passed as
# arguments
targets="$@"
# Only create wrappers if the FSL_CREATE_WRAPPER_SCRIPTS
# environment variable is set
if [ -z "$FSL_CREATE_WRAPPER_SCRIPTS" ]; then
exit 0
fi
# Only create wrappers if FSLDIR exists
if [ ! -d "$FSLDIR" ]; then
exit 0
fi
# and if FSLDIR == PREFIX
if [ "$FSLDIR" != "$PREFIX" ]; then
exit 0
fi
for script in $targets; do
sourceScript="${PREFIX}/bin/$script"
targetScript="${FSLDIR}/share/fsl/bin/$script"
if [ ! -f "$sourceScript" ]; then
continue
fi
# create share/fsl/bin/ if it doesn't
# already exist
if [ ! -e "$FSLDIR/share/fsl/bin" ]; then
mkdir -p "$FSLDIR/share/fsl/bin"
fi
# remove target script if it already exists
if [ -e "$targetScript" ]; then
rm "$targetScript"
fi
# Figure out whether this is a python
# executable or some other type of
# executable. We search for these strings
# in source file headers to ID them as
# python scripts. We need to check both
# match "#!/usr/bin/env python" and
# "#!<fsldir>/bin/python...", because
# conda might generate one or the other
# in different scenarios.
id1=$(head -c 1024 "$sourceScript" | grep "#!/usr/bin/env python")
id2=$(head -c 1024 "$sourceScript" | grep "#!${FSLDIR%/}/bin/python")
# Non-python executable - use a
# pass-through script
if [ -z "$id1" ] && [ -z "$id2" ]; then
echo "#!/usr/bin/env bash" > "$targetScript"
echo "$FSLDIR/bin/$script "'"$@"' >> "$targetScript"
else
# Python executable - run it via
# $FSLDIR/bin/python in isolated mode
echo "#!/usr/bin/env bash" > "$targetScript"
echo "$FSLDIR/bin/python -I $sourceScript "'"$@"' >> "$targetScript"
fi
# Preserve file permissions
chmod --reference="$sourceScript" "$targetScript"
done
#!/usr/bin/env bash
#
# Remove wrapper script/links in $FSLDIR/share/fsl/bin/ which invoke commands
# that are installed in $FSLDIR/bin/. See createFSLWrapper for more
# information.
#
# This script is intended to be called by the pre-unlink.sh script of the
# conda recipe for each FSL project that provides executable commands.
# Names of all executables for which wrapper
# scripts are to be created are passed as
# arguments
targets="$@"
# Note that we don't check the FSL_CREATE_WRAPPER_SCRIPTS environment variable
# here. Wrapper scripts in $FSLDIR/share/fsl/bin/ will exist only if a
# FSL conda packages was installed in an environment where thenb
# FSL_CREATE_WRAPPER_SCRIPTS variable was set, so this script will simply
# delete any wrapper scripts that exist.
# Only remove wrappers if FSLDIR exists
if [ ! -d "$FSLDIR" ]; then
exit 0
fi
# and if FSLDIR == PREFIX
if [ "$FSLDIR" != "$PREFIX" ]; then
exit 0
fi
# Only remove if scripts are in subdirectory of FSLDIR
for script in $targets; do
targetScript="${FSLDIR}/share/fsl/bin/$script"
if [ -f "$targetScript" ]; then
rm "$targetScript"
fi
done
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment