summarylogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.SRCINFO46
-rw-r--r--001-use_distribution_environment.patch154
-rw-r--r--002-fix_fsl_exec_empty_errorCode.patch12
-rw-r--r--003-fix_missing_LIB_PROB.patch35
-rw-r--r--004-fix_missing_LIB_PROB.patch35
-rw-r--r--004-fix_mist_discard.patch11
-rw-r--r--005-fix_cuda_thrust_include.patch76
-rw-r--r--006-compile_ptx2_without_std-c++11.patch10
-rwxr-xr-xPKGBUILD113
-rw-r--r--buildSettings.mk139
-rwxr-xr-xexternallibs.mk77
-rw-r--r--fsl_exec.patch11
-rw-r--r--fsl_sub544
-rw-r--r--fslinstaller.py2892
-rwxr-xr-ximcp31
-rwxr-xr-ximglob31
-rwxr-xr-ximmv31
-rwxr-xr-xsystemvars.mk46
18 files changed, 3868 insertions, 426 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 91089d7c0033..5d58695fb03c 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,51 +1,13 @@
pkgbase = fsl
pkgdesc = A comprehensive library of analysis tools for FMRI, MRI and DTI brain imaging data
- pkgver = 6.0.1
+ pkgver = 6.0.7.7
pkgrel = 1
url = http://www.fmrib.ox.ac.uk/fsl/
arch = x86_64
license = custom
- makedepends = boost
- makedepends = fftw
- depends = gd
- depends = libxml2
- depends = libxml++2.6
- depends = gsl
- depends = libpng
- depends = nlopt
- depends = newmat
- depends = tcl
- depends = tk
- depends = zlib
depends = python
- depends = glu
- depends = boost-libs
- depends = vtk
- depends = sqlite
- depends = python3
- depends = fslpy
- depends = bc
- optdepends = cuda
- source = http://www.fmrib.ox.ac.uk/fsldownloads/fsl-6.0.1-sources.tar.gz
- source = http://www.fmrib.ox.ac.uk/fsldownloads/fsl-6.0.1-feeds.tar.gz
- source = externallibs.mk
- source = systemvars.mk
- source = imcp
- source = imglob
- source = immv
- source = 001-use_distribution_environment.patch
- source = 002-fix_meldata_usage_of_ifstream.patch
- source = 003-fix_fsl_exec_empty_errorCode.patch
- sha256sums = ccab9709239340299b0ca034cb00d6ce0170b9e0d075b3adb55c556feacfb2da
- sha256sums = 91aa756d5a052702cc68e41bcc9a64ba7c7f8853feb215d5a44eeb710c4a0fd0
- sha256sums = e3345af9d3a1bca157c3a5700c63c4d0e01da3cec525f8ffb8f1a04b048aeff1
- sha256sums = 326c73cf0fb07ef9436ec31dda00f1e77488949152aa908b50aa4059701b2984
- sha256sums = c61f185fbe7e297c4518e96377aa5ff4852f90eda0dbb9ae8edc5e24735e14ad
- sha256sums = 7a1039cdc38b4d728f14efce3b0fda0cadc7bfcd3432556c3f3113985bf2720a
- sha256sums = b6f61a6d5672b6684f19150f6e21ded1bd04ec6415dcf07a32291e4002bfa5d8
- sha256sums = b59921d9b76c07da6c775d63d5fe99ca5069a15827aa7a3d44c2e5eb6f3638d6
- sha256sums = 13d4cf35343e7a73bc2534c94b1b0d4db41c338d374e6982091e4cf7a421d420
- sha256sums = 64b4ccefa63a3cf920b185dd52e94b918c24f2cedaebcec8efb767bd80a6418a
+ options = !strip
+ source = fslinstaller.py
+ sha256sums = 3db63c9f53edc909b2264bd364c241d72945862726c305025694aae44e544b0c
pkgname = fsl
-
diff --git a/001-use_distribution_environment.patch b/001-use_distribution_environment.patch
index ef84b6e115d8..65bc781bfee2 100644
--- a/001-use_distribution_environment.patch
+++ b/001-use_distribution_environment.patch
@@ -37,38 +37,34 @@
export FSLTCLSH FSLWISH
---- a/fsl/extras/build 2016-11-15 15:30:21.000000000 +0100
-+++ b/fsl/extras/build 2018-11-25 14:10:48.382885194 +0100
-@@ -96,21 +96,20 @@
- BUILDICONV=1
+--- a/fsl/extras/build 2019-09-29 14:50:57.966635464 +0200
++++ b/fsl/extras/build 2019-09-29 14:53:48.906589517 +0200
+@@ -96,21 +96,12 @@
+ BUILDICONV=1
fi
fi
-PROJECTS="tcl tk"
-+PROJECTS=""
- if [ ${BUILDZLIB} -eq 1 ]; then
+-if [ ${BUILDZLIB} -eq 1 ]; then
- PROJECTS="${PROJECTS} zlib"
-+ PROJECTS="${PROJECTS}"
- fi
+-fi
-PROJECTS="${PROJECTS} libpng"
-+PROJECTS="${PROJECTS}"
- if [ ${BUILDICONV} -eq 1 ]; then
+-if [ ${BUILDICONV} -eq 1 ]; then
- PROJECTS="${PROJECTS} libiconv"
-+ PROJECTS="${PROJECTS}"
- fi
+-fi
-PROJECTS="${PROJECTS} libgd libgdc libprob libcprob newmat cprob newran fftw"
--PROJECTS="${PROJECTS} boost libxml2-2.9.2 libxml++-2.34.0 libsqlite libnlopt ../include/armawrap/dummy_newmat"
+-PROJECTS="${PROJECTS} boost libxml2-2.9.2 libxmlpp libsqlite libnlopt ../include/armawrap/dummy_newmat"
+PROJECTS="${PROJECTS} libgdc libprob libcprob cprob newran ../include/armawrap/dummy_newmat"
for projname in $PROJECTS; do
if [ -d $FSLESRCDIR/$projname ] ; then
- buildIt $FSLESRCDIR $projname 1
+ buildIt $FSLESRCDIR $projname 1
elif [ "$projname" = "boost" -a -d ${FSLEXTINC}/boost/boost ]; then
- buildIt $FSLEXTINC boost 0
+ buildIt $FSLEXTINC 0
fi
done
---- a/fsl/src/mist-clean/Makefile 2018-10-17 13:00:28.000000000 +0200
-+++ b/fsl/src/mist-clean/Makefile 2018-11-25 11:26:42.643047048 +0100
+--- a/fsl/src/mist/Makefile 2018-10-17 13:00:28.000000000 +0200
++++ b/fsl/src/mist/Makefile 2018-11-25 11:26:42.643047048 +0100
@@ -2,15 +2,15 @@
NLOPT_INC = ${FSLEXTINC}
@@ -104,129 +100,3 @@
+ ${LIBRT}
HDRS = common/gibbsshapemodel.h common/mrfshapemodel.h common/mvnshapemodel.h common/plotting.h common/profilefilters.h common/profilemixtures.h common/profilemodel.h common/profilepriors.h common/serialisation.h common/shape.h common/shapemodel.h common/stats.h common/transformation.h mist/builddate.h
-
---- a/fsl/config/_FSLMACHTYPE/externallibs.mk 2018-10-10 13:12:48.000000000 +0200
-+++ b/fsl/config/_FSLMACHTYPE/externallibs.mk 2018-11-25 11:26:48.363038307 +0100
-@@ -7,26 +7,27 @@
- FSLEXTBIN=${FSLDIR}/extras/bin
-
- # GD library
--LIB_GD = ${FSLEXTLIB}
--INC_GD = ${FSLEXTINC}
-+LIB_GD = /usr/lib
-+INC_GD = /usr/include
-
- # GDC library
- LIB_GDC = ${FSLEXTLIB}
- INC_GDC = ${FSLEXTINC}/libgdc
-
- # LIBXML2 library
--INC_XML2 = ${FSLEXTINC}/libxml2
-+INC_XML2 = /usr/include/libxml2
-
- # LIBXML++ library
--INC_XML++ = ${FSLEXTINC}/libxml++-2.6
--INC_XML++CONF = ${FSLEXTLIB}/libxml++-2.6/include
-+INC_XML++ = /usr/include/libxml++-2.6
-+INC_XML++CONF = /usr/lib/libxml++-2.6/include
-+
- # GSL library
--LIB_GSL = ${FSLEXTLIB}
--INC_GSL = ${FSLEXTINC}/gsl
-+LIB_GSL = /usr/lib
-+INC_GSL = /usr/include/gsl
-
- # PNG library
--LIB_PNG = ${FSLEXTLIB}
--INC_PNG = ${FSLEXTINC}
-+LIB_PNG = /usr/lib
-+INC_PNG = /usr/include/libpng1.6
-
- # PROB library
- LIB_PROB = ${FSLEXTLIB}
-@@ -38,7 +39,9 @@
-
- # NEWMAT library
- #LIB_NEWMAT = ${FSLEXTLIB} -llapack -lblas or just -lopenblas
--LIB_NEWMAT = ${FSLEXTLIB} -lopenblas
-+#LIB_NEWMAT = /usr/lib/newmat
-+#INC_NEWMAT = /usr/include/newmat
-+LIB_NEWMAT = ${FSLEXTLIB} -llapack -lblas
- INC_NEWMAT = ${FSLEXTINC}/armawrap/armawrap -DARMA_USE_LAPACK -DARMA_USE_BLAS -DARMA_64BIT_WORD
-
- # NEWRAN library
-@@ -46,29 +49,29 @@
- INC_NEWRAN = ${FSLEXTINC}/newran
-
- # ZLIB library
--LIB_ZLIB = /lib64
-+LIB_ZLIB = /usr/lib
- INC_ZLIB = /usr/include
-
- # BOOST library
--BOOSTDIR = ${FSLEXTINC}/boost
--LIB_BOOST = ${BOOSTDIR}
--INC_BOOST = ${BOOSTDIR}
-+#BOOSTDIR = ${FSLEXTINC}/boost
-+LIB_BOOST = /usr/lib
-+INC_BOOST = /usr/include/boost
-
- # QT library
--QTDIR = /usr/lib/qt3
--LIB_QT = ${QTDIR}/lib
--INC_QT = ${QTDIR}/include
-+#QTDIR = /usr/lib/qt5
-+LIB_QT = /usr/lib
-+INC_QT = ${QTDIR}/include/qt
-
- # QWT library
--QWTDIR = /usr/local/qwt
--LIB_QWT = ${QWTDIR}/lib
--INC_QWT = ${QWTDIR}/include
-+#QWTDIR = /usr/local/qwt
-+LIB_QWT = /usr/lib
-+INC_QWT = /usr/include/qwt
-
- # FFTW3 library
--LIB_FFTW3 = ${FSLEXTLIB}
--INC_FFTW3 = ${FSLEXTINC}/fftw3
-+LIB_FFTW3 = /usr/lib
-+INC_FFTW3 = /usr/include
-
- # VTK library
--VTKDIR_INC = /home/fs0/cowboy/var/caper_linux_64-gcc4.4/VTK7/include/vtk-7.0
--VTKDIR_LIB = /home/fs0/cowboy/var/caper_linux_64-gcc4.4/VTK7/lib
--VTKSUFFIX = -7.0
-\ No newline at end of file
-+VTKDIR_INC = /usr/include/vtk
-+VTKDIR_LIB = /usr/lib
-+VTKSUFFIX =
---- a/fsl/config/_FSLMACHTYPE/systemvars.mk 2017-09-04 14:09:26.000000000 +0200
-+++ b/fsl/config/_FSLMACHTYPE/systemvars.mk 2018-11-25 11:26:48.363038307 +0100
-@@ -8,7 +8,7 @@
- CP = /bin/cp
- MV = /bin/mv
- INSTALL = install -p
--TCLSH = ${FSLDIR}/bin/fsltclsh
-+TCLSH = /usr/bin/tclsh
- RANLIB = echo
-
- FSLML = ${FSLDIR}/bin/fslml
-@@ -30,14 +30,14 @@
-
- DEPENDFLAGS = -MM
-
--OPTFLAGS = -g -O3 -fexpensive-optimizations ${ARCHFLAGS}
-+OPTFLAGS = -march=native -g -O3 -fexpensive-optimizations ${ARCHFLAGS}
- MACHDBGFLAGS = -g
--GNU_ANSI_FLAGS = -Wall -ansi -pedantic -Wno-long-long
-+GNU_ANSI_FLAGS = -Wall -ansi -pedantic -std=c++11 -Wno-long-long
- SGI_ANSI_FLAGS = -ansi -fullwarn
- ANSI_FLAGS = ${GNU_ANSI_FLAGS}
-
- # CUDA development environment
--CUDA_INSTALLATION = /opt/cuda-7.5
-+CUDA_INSTALLATION = /opt/cuda
- GENCODE_FLAGS = $(shell ${FSLDIR}/config/common/supportedGencodes.sh ${CUDA_INSTALLATION})
- LIB_CUDA = ${CUDA_INSTALLATION}/lib64
- INC_CUDA = ${CUDA_INSTALLATION}/include
diff --git a/002-fix_fsl_exec_empty_errorCode.patch b/002-fix_fsl_exec_empty_errorCode.patch
new file mode 100644
index 000000000000..5e8cb0269cd2
--- /dev/null
+++ b/002-fix_fsl_exec_empty_errorCode.patch
@@ -0,0 +1,12 @@
+--- a/fsl/src/misc_tcl/fsl_exec.tcl 2018-10-23 18:49:44.000000000 +0200
++++ b/fsl/src/misc_tcl/fsl_exec.tcl 2018-11-25 18:52:30.636934316 +0100
+@@ -175,6 +175,9 @@
+ set logout ""
+ }
+
++ # make sure errorCode is set
++ if { ! [ info exists ::errorCode ] } { set ::errorCode "NONE" }
++
+ # run and log the actual command
+ if { $do_logout } {
+ fsl:echo $logout "\n$thecommand"
diff --git a/003-fix_missing_LIB_PROB.patch b/003-fix_missing_LIB_PROB.patch
new file mode 100644
index 000000000000..abd3dbb4b631
--- /dev/null
+++ b/003-fix_missing_LIB_PROB.patch
@@ -0,0 +1,35 @@
+--- a/fsl/src/eddy/Makefile 2019-09-29 21:41:53.118138982 +0200
++++ b/fsl/src/eddy/Makefile 2019-09-29 21:25:19.118338652 +0200
+@@ -62,7 +62,7 @@
+ endif
+ EXENAME=eddy${TMPNAME_1}${TMPNAME_2}
+ endif
+-USRLDFLAGS=-L${LIB_NEWMAT} -Wl,-rpath,/opt/fmrib/fsl/lib -rdynamic
++USRLDFLAGS=-L${LIB_NEWMAT} -L${LIB_PROB} -Wl,-rpath,/opt/fmrib/fsl/lib -rdynamic
+ USRINCFLAGS=-I. -I${INC_NEWMAT} -I${INC_PROB} -I${INC_BOOST} -I${INC_CUDA} -I$(CBF_DIR) -I$(INC_BASISFIELD)
+
+ OBJS=eddy.o BiasFieldEstimatorImpl.o MoveBySuscCF.o PostEddyAlignShellsFunctions.o EddyCommandLineOptions.o ECModels.o KMatrix.o HyParEstimator.o ECScanClasses.o EddyUtils.o EddyHelperClasses.o DiffusionGP.o b0Predictor.o
+
+--- a/fsl/src/asl_mfree/Makefile 2019-09-29 22:52:54.221367251 +0200
++++ b/fsl/src/asl_mfree/Makefile 2019-09-29 22:53:23.577360889 +0200
+@@ -3,7 +3,7 @@
+ PROJNAME = asl_mfree
+
+ USRINCFLAGS = -I${INC_NEWMAT} -I${INC_ZLIB}
+-USRLDFLAGS = -L${LIB_NEWMAT} -L${LIB_ZLIB}
++USRLDFLAGS = -L${LIB_NEWMAT} -L${LIB_ZLIB} -L${LIB_PROB}
+
+ FSLVERSION= $(shell cat ${FSLDIR}/etc/fslversion | head -c 1)
+ ifeq ($(FSLVERSION), 5)
+
+--- a/fsl/src/oxford_asl/Makefile 2019-09-29 22:52:27.621373018 +0200
++++ b/fsl/src/oxford_asl/Makefile 2019-09-29 22:53:41.237357074 +0200
+@@ -3,7 +3,7 @@
+ PROJNAME = oxford_asl
+
+ USRINCFLAGS = -I${INC_NEWMAT} -I${INC_ZLIB}
+-USRLDFLAGS = -L${LIB_NEWMAT} -L${LIB_ZLIB}
++USRLDFLAGS = -L${LIB_NEWMAT} -L${LIB_ZLIB} -L${LIB_PROB}
+
+ FSLVERSION= $(shell cat ${FSLDIR}/etc/fslversion | head -c 1)
+ ifeq ($(FSLVERSION), 5)
diff --git a/004-fix_missing_LIB_PROB.patch b/004-fix_missing_LIB_PROB.patch
new file mode 100644
index 000000000000..abd3dbb4b631
--- /dev/null
+++ b/004-fix_missing_LIB_PROB.patch
@@ -0,0 +1,35 @@
+--- a/fsl/src/eddy/Makefile 2019-09-29 21:41:53.118138982 +0200
++++ b/fsl/src/eddy/Makefile 2019-09-29 21:25:19.118338652 +0200
+@@ -62,7 +62,7 @@
+ endif
+ EXENAME=eddy${TMPNAME_1}${TMPNAME_2}
+ endif
+-USRLDFLAGS=-L${LIB_NEWMAT} -Wl,-rpath,/opt/fmrib/fsl/lib -rdynamic
++USRLDFLAGS=-L${LIB_NEWMAT} -L${LIB_PROB} -Wl,-rpath,/opt/fmrib/fsl/lib -rdynamic
+ USRINCFLAGS=-I. -I${INC_NEWMAT} -I${INC_PROB} -I${INC_BOOST} -I${INC_CUDA} -I$(CBF_DIR) -I$(INC_BASISFIELD)
+
+ OBJS=eddy.o BiasFieldEstimatorImpl.o MoveBySuscCF.o PostEddyAlignShellsFunctions.o EddyCommandLineOptions.o ECModels.o KMatrix.o HyParEstimator.o ECScanClasses.o EddyUtils.o EddyHelperClasses.o DiffusionGP.o b0Predictor.o
+
+--- a/fsl/src/asl_mfree/Makefile 2019-09-29 22:52:54.221367251 +0200
++++ b/fsl/src/asl_mfree/Makefile 2019-09-29 22:53:23.577360889 +0200
+@@ -3,7 +3,7 @@
+ PROJNAME = asl_mfree
+
+ USRINCFLAGS = -I${INC_NEWMAT} -I${INC_ZLIB}
+-USRLDFLAGS = -L${LIB_NEWMAT} -L${LIB_ZLIB}
++USRLDFLAGS = -L${LIB_NEWMAT} -L${LIB_ZLIB} -L${LIB_PROB}
+
+ FSLVERSION= $(shell cat ${FSLDIR}/etc/fslversion | head -c 1)
+ ifeq ($(FSLVERSION), 5)
+
+--- a/fsl/src/oxford_asl/Makefile 2019-09-29 22:52:27.621373018 +0200
++++ b/fsl/src/oxford_asl/Makefile 2019-09-29 22:53:41.237357074 +0200
+@@ -3,7 +3,7 @@
+ PROJNAME = oxford_asl
+
+ USRINCFLAGS = -I${INC_NEWMAT} -I${INC_ZLIB}
+-USRLDFLAGS = -L${LIB_NEWMAT} -L${LIB_ZLIB}
++USRLDFLAGS = -L${LIB_NEWMAT} -L${LIB_ZLIB} -L${LIB_PROB}
+
+ FSLVERSION= $(shell cat ${FSLDIR}/etc/fslversion | head -c 1)
+ ifeq ($(FSLVERSION), 5)
diff --git a/004-fix_mist_discard.patch b/004-fix_mist_discard.patch
new file mode 100644
index 000000000000..9ed361fb3c2a
--- /dev/null
+++ b/004-fix_mist_discard.patch
@@ -0,0 +1,11 @@
+--- a/fsl/src/mist/common/shape.cpp 2021-07-15 10:44:27.000000000 +0000
++++ b/fsl/src/mist/common/shape.cpp 2021-11-24 14:12:38.684538565 +0000
+@@ -354,7 +354,7 @@
+ for (vtkIdType i = 0; i < cells->GetNumberOfIds(); i++)
+ {
+ vtkIdType npoints;
+- vtkIdType *points;
++ const vtkIdType *points;
+
+ m_polyData->GetCellPoints(cells->GetId(i), npoints, points);
+
diff --git a/005-fix_cuda_thrust_include.patch b/005-fix_cuda_thrust_include.patch
new file mode 100644
index 000000000000..1870755c2f0c
--- /dev/null
+++ b/005-fix_cuda_thrust_include.patch
@@ -0,0 +1,76 @@
+# Author: Caspar van Leeuwen (SURF)
+# FSL includes thrust headers by doing #include <device_vector.h>
+# and then include -I$CUDA_ROOT/include/thrust
+# This results in errors like
+# /sw/arch/Debian10/EB_production/2019/software/CUDA/10.1.243/include/cuda_runtime_api.h(7397):
+# error: identifier "UINT_MAX" is undefined
+# This is normally defined in the standard C header 'limits.h'.
+# However, by doing -I$CUDA_ROOT/include/thrust, the limits.h from Thrust is picked up instead!
+# According to the Thrust docs, includes should be done as #include <thrust/device_vector.h>
+# This way, -I$CUDA_ROOT/include is sufficient, and the limits.h from thrust
+# no longer erroneously gets picked up.
+diff -Nru a/fsl/src/fdt/CUDA/diffmodels.cuh fsl/src/fdt/CUDA/diffmodels.cuh
+--- a/fsl/src/fdt/CUDA/diffmodels.cuh 2020-06-09 20:41:17.117128671 +0200
++++ b/fsl/src/fdt/CUDA/diffmodels.cuh 2020-06-09 20:42:44.045211731 +0200
+@@ -66,7 +66,7 @@
+ University, to negotiate a licence. Contact details are:
+ <a href="/cgi-bin/wa-jisc.exe?LOGON=A3%3Dind2006%26L%3DFSL%26E%3Dbase64%26P%3D3480627%26B%3D------%253D_Part_434001_1485233425.1591892113317%26T%3Dtext%252Fx-patch%3B%2520name%3D%2522FSL-6.0.2_thrust.patch%2522%26N%3DFSL-6.0.2_thrust.patch%26attachment%3Dq%26XSS%3D3" target="_parent" >[log in to unmask]</a> quoting Reference Project 9564, FSL.*/
+
+-#include <device_vector.h>
++#include <thrust/device_vector.h>
+
+ void fit_PVM_single( //INPUT
+ const vector<ColumnVector> datam_vec,
+diff -Nru a/fsl/src/fdt/CUDA/runmcmc.cu fsl/src/fdt/CUDA/runmcmc.cu
+--- a/fsl/src/fdt/CUDA/runmcmc.cu 2020-06-09 20:41:17.101128655 +0200
++++ b/fsl/src/fdt/CUDA/runmcmc.cu 2020-06-09 20:43:13.649240030 +0200
+@@ -71,8 +71,8 @@
+ #include "runmcmc_kernels.cu"
+ #include "sync_check.h"
+
+-#include <host_vector.h>
+-#include <device_vector.h>
++#include <thrust/host_vector.h>
++#include <thrust/device_vector.h>
+
+ #include <time.h>
+ #include <sys/time.h>
+diff -Nru a/fsl/src/fdt/CUDA/xfibres_gpu.cu fsl/src/fdt/CUDA/xfibres_gpu.cu
+--- a/fsl/src/fdt/CUDA/xfibres_gpu.cu 2020-06-09 20:41:17.117128671 +0200
++++ b/fsl/src/fdt/CUDA/xfibres_gpu.cu 2020-06-09 20:44:00.621284957 +0200
+@@ -76,8 +76,8 @@
+ #include "samples.h"
+ #include "options.h"
+
+-#include <host_vector.h>
+-#include <device_vector.h>
++#include <thrust/host_vector.h>
++#include <thrust/device_vector.h>
+
+ #include <time.h>
+ #include <sys/time.h>
+diff -Nru a/fsl/src/fdt/CUDA/xfibres_gpu.cuh fsl/src/fdt/CUDA/xfibres_gpu.cuh
+--- a/fsl/src/fdt/CUDA/xfibres_gpu.cuh 2020-06-09 20:41:17.113128667 +0200
++++ b/fsl/src/fdt/CUDA/xfibres_gpu.cuh 2020-06-09 20:43:38.469263769 +0200
+@@ -67,8 +67,8 @@
+ <a href="/cgi-bin/wa-jisc.exe?LOGON=A3%3Dind2006%26L%3DFSL%26E%3Dbase64%26P%3D3480627%26B%3D------%253D_Part_434001_1485233425.1591892113317%26T%3Dtext%252Fx-patch%3B%2520name%3D%2522FSL-6.0.2_thrust.patch%2522%26N%3DFSL-6.0.2_thrust.patch%26attachment%3Dq%26XSS%3D3" target="_parent" >[log in to unmask]</a> quoting Reference Project 9564, FSL.*/
+
+ #include "newimage/newimageall.h"
+-#include <host_vector.h>
+-#include <device_vector.h>
++#include <thrust/host_vector.h>
++#include <thrust/device_vector.h>
+
+ #include "fibre_gpu.h"
+ #include <curand_kernel.h>
+--- a/fsl/src/fdt/Makefile 2020-08-25 21:32:15.316740340 +0200
++++ b/fsl/src/fdt/Makefile 2020-08-25 21:34:28.163670755 +0200
+@@ -171,7 +171,7 @@
+ ${CXX} ${CXXFLAGS} ${LDFLAGS} -o $@ ${EDDYCOMBINEOBJS} ${DLIBS}
+
+ ${LIBBEDPOSTX_CUDA}:
+- ${NVCC} --shared --compiler-options '-fPIC' -o CUDA/libbedpostx_cuda.so CUDA/init_gpu.cu CUDA/samples.cu CUDA/diffmodels.cu CUDA/runmcmc.cu CUDA/xfibres_gpu.cu -O3 ${GENCODE_FLAGS} -lcudart -lcuda -lcurand -I. -L${LIB_CUDA} -L${LIB_CUDA}/stubs -ICUDA/options -I${INC_NEWMAT} -I${FSLDIR}/include -I${INC_BOOST} -I${INC_CUDA} -I${INC_CUDA}/thrust -maxrregcount=64
++ ${NVCC} --shared --compiler-options '-fPIC' -o CUDA/libbedpostx_cuda.so CUDA/init_gpu.cu CUDA/samples.cu CUDA/diffmodels.cu CUDA/runmcmc.cu CUDA/xfibres_gpu.cu -O3 ${GENCODE_FLAGS} -lcudart -lcuda -lcurand -I. -L${LIB_CUDA} -L${LIB_CUDA}/stubs -ICUDA/options -I${INC_NEWMAT} -I${FSLDIR}/include -I${INC_BOOST} -I${INC_CUDA} -I${INC_CUDA} -maxrregcount=64
+ @if [ ! -d ${FSLDEVDIR}/lib/ ] ; then ${MKDIR} ${FSLDEVDIR}/lib ; fi
+ ${CP} -rf CUDA/libbedpostx_cuda.so ${FSLDEVDIR}/lib
+
diff --git a/006-compile_ptx2_without_std-c++11.patch b/006-compile_ptx2_without_std-c++11.patch
new file mode 100644
index 000000000000..2067c5d4df5e
--- /dev/null
+++ b/006-compile_ptx2_without_std-c++11.patch
@@ -0,0 +1,10 @@
+--- a/fsl/src/ptx2/Makefile 2020-01-24 11:47:42.000000000 +0100
++++ b/fsl/src/ptx2/Makefile 2020-08-25 23:34:22.215595885 +0200
+@@ -24,6 +24,7 @@
+ USRLDFLAGS = -L${LIB_NEWMAT} -L${LIB_NEWRAN} -L${LIB_CPROB} -L${LIB_PROB} -L${LIB_ZLIB}
+
+ DLIBS = -lnewmeshclass -lwarpfns -lbasisfield -lfslsurface -lfslvtkio -lmeshclass -lnewimage -lutils -lmiscmaths -lnewmat -lnewran -lNewNifti -lgiftiio -lexpat -lfirst_lib -lznz -lcprob -lutils -lprob -lm -lz
++GNU_ANSI_FLAGS = -Wall -ansi -pedantic -Wno-long-long
+
+ CCOPS=ccops
+ PTX=probtrackx2
diff --git a/PKGBUILD b/PKGBUILD
index fbaa3af33a87..eccdf3a74f22 100755
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -2,114 +2,35 @@
# Contributor: fishburn <frankthefishburn@gmail.com>
pkgname=fsl
-pkgver=6.0.1
+pkgver=6.0.7.7
pkgrel=1
pkgdesc="A comprehensive library of analysis tools for FMRI, MRI and DTI brain imaging data"
arch=("x86_64")
url="http://www.fmrib.ox.ac.uk/fsl/"
-license=(custom)
-depends=(gd libxml2 libxml++2.6 gsl libpng nlopt newmat tcl tk zlib python glu boost-libs vtk sqlite python3 fslpy bc)
-makedepends=(boost fftw)
-optdepends=(cuda)
-source=("http://www.fmrib.ox.ac.uk/fsldownloads/fsl-${pkgver}-sources.tar.gz"
- "http://www.fmrib.ox.ac.uk/fsldownloads/fsl-${pkgver}-feeds.tar.gz"
- "externallibs.mk"
- "systemvars.mk"
- "imcp"
- "imglob"
- "immv"
- "001-use_distribution_environment.patch"
- "002-fix_meldata_usage_of_ifstream.patch"
- "003-fix_fsl_exec_empty_errorCode.patch")
+license=('custom')
+depends=('python')
+source=("fslinstaller.py")
+options=('!strip') # Added as it took hours to do this without substantial benefit
-sha256sums=('ccab9709239340299b0ca034cb00d6ce0170b9e0d075b3adb55c556feacfb2da'
- '91aa756d5a052702cc68e41bcc9a64ba7c7f8853feb215d5a44eeb710c4a0fd0'
- 'e3345af9d3a1bca157c3a5700c63c4d0e01da3cec525f8ffb8f1a04b048aeff1'
- '326c73cf0fb07ef9436ec31dda00f1e77488949152aa908b50aa4059701b2984'
- 'c61f185fbe7e297c4518e96377aa5ff4852f90eda0dbb9ae8edc5e24735e14ad'
- '7a1039cdc38b4d728f14efce3b0fda0cadc7bfcd3432556c3f3113985bf2720a'
- 'b6f61a6d5672b6684f19150f6e21ded1bd04ec6415dcf07a32291e4002bfa5d8'
- 'b59921d9b76c07da6c775d63d5fe99ca5069a15827aa7a3d44c2e5eb6f3638d6'
- '13d4cf35343e7a73bc2534c94b1b0d4db41c338d374e6982091e4cf7a421d420'
- '64b4ccefa63a3cf920b185dd52e94b918c24f2cedaebcec8efb767bd80a6418a')
-
-prepare() {
- cd "${srcdir}"
- export FSLDIR="${srcdir}/fsl"
- . "${FSLDIR}/etc/fslconf/fsl.sh"
- export FSLMACHTYPE=$(${FSLDIR}/etc/fslconf/fslmachtype.sh)
- mkdir "${FSLDIR}/config/${FSLMACHTYPE}"
- # Use config linux_64-gcc4.8 as template
- cp "${srcdir}"/{externallibs.mk,systemvars.mk} "${FSLDIR}/config/${FSLMACHTYPE}"
-
- # Apply patches
- patch -Np1 -i "${srcdir}/001-use_distribution_environment.patch"
- patch -Np1 -i "${srcdir}/002-fix_meldata_usage_of_ifstream.patch"
- # From https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=fsl;e8fa48c1.1501
- patch -Np1 -i "${srcdir}/003-fix_fsl_exec_empty_errorCode.patch"
-
- # Insert makepkg build flags into configuration
- sed -i '0,/${AccumulatedIncFlags}/{s^${AccumulatedIncFlags}^& '"${CFLAGS}"'^}' "${srcdir}/fsl/config/common/vars.mk"
- sed -i '0,/${AccumulatedIncFlags}/{s^${AccumulatedIncFlags}^& '"${CPPFLAGS}"'^}' "${srcdir}/fsl/config/common/vars.mk"
- sed -i '1,/${AccumulatedIncFlags}/!{s^${AccumulatedIncFlags}^& '"${CXXFLAGS}"'^}' "${srcdir}/fsl/config/common/vars.mk"
- sed -i '1,/${AccumulatedIncFlags}/!{s^${AccumulatedIncFlags}^& '"${CPPFLAGS}"'^}' "${srcdir}/fsl/config/common/vars.mk"
- sed -i 's^LDFLAGS = .*$^& '"${LDFLAGS}"'^g' "${srcdir}/fsl/config/common/vars.mk"
-}
+sha256sums=('3db63c9f53edc909b2264bd364c241d72945862726c305025694aae44e544b0c')
build() {
- export FSLDIR="${srcdir}/fsl"
- cd "${FSLDIR}"
- ./build
-
- # Install missing binaries, which are no longer shipped (depends on fslpy)
- /usr/bin/install -m 755 "${srcdir}"/{imcp,imglob,immv} "${srcdir}/fsl/bin"
-}
-
-check() {
- export FSLDIR="${srcdir}/fsl"
- export FEEDSDIR="${srcdir}/feeds"
- . "${FSLDIR}/etc/fslconf/fsl.sh"
- cd "${FEEDSDIR}"
- time ./RUN all
+ export TMPFSLDIR="${srcdir}/fsl"
+ mkdir -p "${TMPFSLDIR}"
+ /usr/bin/python3 "${srcdir}"/fslinstaller.py -n -o -d "${TMPFSLDIR}" -V "${pkgver}"
}
package() {
- rm -rf "${srcdir}/fsl/src"
- rm -rf "${srcdir}/fsl/extras/src"
- rm -rf "${srcdir}/fsl/extras/include"
- mkdir -p "${pkgdir}/opt/fsl"
-
- cp -r "${srcdir}/fsl/bin" "${pkgdir}/opt/fsl/"
- cp -r "${srcdir}/fsl/data" "${pkgdir}/opt/fsl/"
- cp -r "${srcdir}/fsl/doc" "${pkgdir}/opt/fsl/"
- cp -r "${srcdir}/fsl/etc" "${pkgdir}/opt/fsl/"
- cp -r "${srcdir}/fsl/extras" "${pkgdir}/opt/fsl/"
- cp -r "${srcdir}/fsl/lib" "${pkgdir}/opt/fsl/"
- cp -r "${srcdir}/fsl/refdoc" "${pkgdir}/opt/fsl/"
- cp -r "${srcdir}/fsl/tcl" "${pkgdir}/opt/fsl/"
- cp -r "${srcdir}/feeds" "${pkgdir}/opt/fsl/"
-
+ # Replace paths and move files in place
+ find "${TMPFSLDIR}" -type f -exec grep -Iq . {} \; -exec sed -i "s|${srcdir}|/opt|g" {} +
+ mkdir "${pkgdir}"/opt
+ mv "${TMPFSLDIR}" "${pkgdir}"/opt
+ # Copy license
+ mkdir -p "${pkgdir}"/usr/share/licenses/fsl
+ cp "${pkgdir}"/opt/fsl/LICENCE.FSL "${pkgdir}"/usr/share/licenses/fsl/LICENSE
+ # Setup shell environment
mkdir -p "${pkgdir}/etc/profile.d"
echo 'FSLDIR=/opt/fsl' > "${pkgdir}/etc/profile.d/fsl.sh"
echo '. ${FSLDIR}/etc/fslconf/fsl.sh' >> "${pkgdir}/etc/profile.d/fsl.sh"
echo 'export FSLDIR' >> "${pkgdir}/etc/profile.d/fsl.sh"
- echo 'export PATH=$PATH:${FSLDIR}/bin' >> "${pkgdir}/etc/profile.d/fsl.sh"
-
- mkdir -p "${pkgdir}/usr/share/licenses/fsl"
- grep -v \< "${srcdir}/fsl/doc/fsl/licence.html" | cat -s > "${pkgdir}/usr/share/licenses/fsl/LICENSE"
-
- # Fix permissions
- find "${pkgdir}" -type f -exec chmod 644 {} \;
- find "${pkgdir}" -type d -exec chmod 755 {} \;
- find "${pkgdir}/opt/fsl/bin" -exec chmod 755 {} \;
- find "${pkgdir}/opt/fsl/etc/fslconf" -exec chmod 755 {} \;
- chmod 755 "${pkgdir}/etc/profile.d/fsl.sh"
-
- mkdir -p "${pkgdir}/opt/fsl/feeds/results"
- chmod -R 777 "${pkgdir}/opt/fsl/feeds/results"
- chmod 755 "${pkgdir}/opt/fsl/feeds/RUN"
-
- # Clean up
- find "${pkgdir}" -empty -delete
- find "${pkgdir}" -type f -exec sed -i 's^/usr/local/fsl^/opt/fsl^g' "{}" \;
}
diff --git a/buildSettings.mk b/buildSettings.mk
new file mode 100644
index 000000000000..4521b1063169
--- /dev/null
+++ b/buildSettings.mk
@@ -0,0 +1,139 @@
+# This master make file is based on the "old"
+# systemvars.mk and externallibs.mk files from:
+# apple-darwin13-llvm6.0
+# linux_64-gcc4.4
+# linux_64-gcc4.8
+
+# get system type (Darwin, Linux)
+SYSTYPE := $(shell uname -s)
+#####################################################################
+#
+# System Vars (common)
+#
+#####################################################################
+SHELL = /bin/sh
+RM = /bin/rm
+CP = /bin/cp
+MV = /bin/mv
+CHMOD = /bin/chmod
+MKDIR = /bin/mkdir
+INSTALL = install -p
+TCLSH = /usr/bin/tclsh
+DEPENDFLAGS = -MM
+MACHDBGFLAGS = -g
+#####################################################################
+#
+# External libs (common)
+#
+#####################################################################
+FSLEXTLIB=${FSLDIR}/extras/lib
+FSLEXTINC=${FSLDIR}/extras/include
+FSLEXTBIN=${FSLDIR}/extras/bin
+# CEPHES library
+LIB_CEPHES = ${FSLEXTLIB}
+INC_CEPHES = ${FSLEXTINC}/cephes
+# GD library
+LIB_GD = /usr/lib
+INC_GD = /usr/include
+# GDC library
+LIB_GDC = ${FSLEXTLIB}
+INC_GDC = ${FSLEXTINC}/libgdc
+# GSL library
+LIB_GSL = /usr/lib
+INC_GSL = /usr/include/gsl
+# PNG library
+LIB_PNG = /usr/lib
+INC_PNG = /usr/include/libpng16
+# PROB library; added -I${FSLEXTINC} for 6.0.5
+LIB_PROB = ${FSLEXTLIB}
+INC_PROB = ${FSLEXTINC}/cprob -I${FSLEXTINC}
+# CPROB library
+LIB_CPROB = ${FSLEXTLIB}
+INC_CPROB = ${FSLEXTINC}/
+# NEWRAN library
+LIB_NEWRAN = ${FSLEXTLIB}
+INC_NEWRAN = ${FSLEXTINC}/newran
+# BOOST library
+BOOSTDIR = ${FSLEXTINC}/boost
+LIB_BOOST = /usr/lib
+INC_BOOST = /usr/include/boost
+# QWT library
+QWTDIR = /usr
+INC_QWT = ${QWTDIR}/include/qwt
+LIB_QWT = ${QWTDIR}/lib
+# FFTW3 library
+LIB_FFTW3 = /usr/lib
+INC_FFTW3 = /usr/include
+# LIBXML2 library
+INC_XML2 = /usr/include/libxml2
+# LIBXML++ library
+INC_XML++ = /usr/include/libxml++-2.6
+INC_XML++CONF = /usr/lib/libxml++-2.6/include
+# NEWMAT library/armadillo
+INC_NEWMAT = ${FSLEXTINC}/armawrap/armawrap -DARMA_USE_LAPACK -DARMA_USE_BLAS -DARMA_64BIT_WORD
+#####################################################################
+#
+# Linux specific sys vars and ext libs
+# Makefile auto-detects gcc version for Linux
+#
+#####################################################################
+ifeq ($(SYSTYPE), Linux)
+############### System Vars #####################################
+CC = gcc
+CXX = g++
+CXX11 = g++
+CSTATICFLAGS = -static
+CXXSTATICFLAGS = -static
+ARCHFLAGS = -m64
+ARCHLDFLAGS = -Wl,-rpath,'$$ORIGIN/../lib'
+PARALLELFLAGS = -fopenmp
+OPTFLAGS = -g -O3 -fexpensive-optimizations ${ARCHFLAGS}
+GNU_ANSI_FLAGS = -Wall -ansi -pedantic -Wno-long-long -std=c++11
+SGI_ANSI_FLAGS = -ansi -fullwarn
+ANSI_FLAGS = ${GNU_ANSI_FLAGS}
+RANLIB = echo
+FSLML = ${FSLDIR}/bin/fslml
+# CUDA development environment
+CUDAVER := $(or $(CUDAVER),9.1)
+#$(info $$CUDAVER is [${CUDAVER}])
+CUDA_INSTALLATION = /opt/cuda
+GENCODE_FLAGS = $(shell ${FSLDIR}/config/common/supportedGencodes.sh ${CUDA_INSTALLATION})
+LIB_CUDA = ${CUDA_INSTALLATION}/lib64
+INC_CUDA = ${CUDA_INSTALLATION}/include
+NVCC = ${CUDA_INSTALLATION}/bin/nvcc
+############### External Libs #####################################
+# ZLIB library
+LIB_ZLIB = /usr/lib
+INC_ZLIB = /usr/include
+# QT library
+QTDIR = /usr/
+LIB_QT = ${QTDIR}/lib
+INC_QT = ${QTDIR}/include/qt
+# VTK library
+VTKDIR_INC = /usr/include/vtk
+VTKDIR_LIB = /usr/lib
+VTKSUFFIX =
+# openblas
+LIB_NEWMAT = /usr/lib -llapack -lopenblas
+# get and then parse gcc version to run context specific builds
+#GCCVER := $(shell gcc -dumpversion)
+#GCCARR = $(subst ., ,$(GCCVER))
+#$(info $(GCCVER))
+#GCCMAJ = $(word 1, $(GCCARR))
+#GCCMIN = $(word 2, $(GCCARR))
+#GCCPAT = $(word 3, $(GCCARR))
+#$(info GCC MAJ VER $(GCCMAJ))
+#$(info GCC MIN VER $(GCCMIN))
+#$(info GCC PAT VER $(GCCPAT))
+
+#Project specific variables
+EDDYBUILDPARAMETERS = "cuda=1 CUDAVER=9.1" "cuda=1 CUDAVER=9.1" "cpu=1"
+fdt_MASTERBUILD = COMPILE_GPU = 0
+ptx2_MASTERBUILD = COMPILE_GPU = 0
+define newline
+
+
+endef
+#PTX2_MASTER_COMMANDS = COMPILE_GPU = 1$(newline)FOO=2
+endif # if Linux
+
diff --git a/externallibs.mk b/externallibs.mk
deleted file mode 100755
index e1c5e44798c7..000000000000
--- a/externallibs.mk
+++ /dev/null
@@ -1,77 +0,0 @@
-# $Id: externallibs.mk,v 1.3 2018/10/10 11:12:48 mwebster Exp $
-
-# External Library and Include Paths
-
-FSLEXTLIB=${FSLDIR}/extras/lib
-FSLEXTINC=${FSLDIR}/extras/include
-FSLEXTBIN=${FSLDIR}/extras/bin
-
-# GD library
-LIB_GD = /usr/lib
-INC_GD = /usr/include
-
-# GDC library
-LIB_GDC = ${FSLEXTLIB}
-INC_GDC = ${FSLEXTINC}/libgdc
-
-# LIBXML2 library
-INC_XML2 = /usr/include/libxml2
-
-# LIBXML++ library
-INC_XML++ = /usr/include/libxml++-2.6
-INC_XML++CONF = /usr/lib/libxml++-2.6/include
-
-# GSL library
-LIB_GSL = /usr/lib
-INC_GSL = /usr/include/gsl
-
-# PNG library
-LIB_PNG = /usr/lib
-INC_PNG = /usr/include/libpng1.6
-
-# PROB library
-LIB_PROB = ${FSLEXTLIB}
-INC_PROB = ${FSLEXTINC}/libprob
-
-# CPROB library
-LIB_CPROB = ${FSLEXTLIB}
-INC_CPROB = ${FSLEXTINC}/libcprob
-
-# NEWMAT library
-#LIB_NEWMAT = ${FSLEXTLIB} -llapack -lblas or just -lopenblas
-#LIB_NEWMAT = /usr/lib/newmat
-#INC_NEWMAT = /usr/include/newmat
-LIB_NEWMAT = ${FSLEXTLIB} -llapack -lblas
-INC_NEWMAT = ${FSLEXTINC}/armawrap/armawrap -DARMA_USE_LAPACK -DARMA_USE_BLAS -DARMA_64BIT_WORD
-
-# NEWRAN library
-LIB_NEWRAN = ${FSLEXTLIB}
-INC_NEWRAN = ${FSLEXTINC}/newran
-
-# ZLIB library
-LIB_ZLIB = /usr/lib
-INC_ZLIB = /usr/include
-
-# BOOST library
-#BOOSTDIR = ${FSLEXTINC}/boost
-LIB_BOOST = /usr/lib
-INC_BOOST = /usr/include/boost
-
-# QT library
-#QTDIR = /usr/lib/qt5
-LIB_QT = /usr/lib
-INC_QT = ${QTDIR}/include/qt
-
-# QWT library
-#QWTDIR = /usr/local/qwt
-LIB_QWT = /usr/lib
-INC_QWT = /usr/include/qwt
-
-# FFTW3 library
-LIB_FFTW3 = /usr/lib
-INC_FFTW3 = /usr/include
-
-# VTK library
-VTKDIR_INC = /usr/include/vtk
-VTKDIR_LIB = /usr/lib
-VTKSUFFIX =
diff --git a/fsl_exec.patch b/fsl_exec.patch
deleted file mode 100644
index 3a5628c37044..000000000000
--- a/fsl_exec.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- fsl/src/misc_tcl/fsl_exec.tcl.orig 2017-06-12 17:54:14.000000000 -0400
-+++ fsl/src/misc_tcl/fsl_exec.tcl 2017-06-12 18:45:07.176756012 -0400
-@@ -175,6 +175,7 @@ proc fsl:exec { thecommand args } {
- set logout ""
- }
-
-+ if { ! [ info exists ::errorCode ] } { set ::errorCode "NONE" }
- # run and log the actual command
- if { $do_logout } {
- fsl:echo $logout "\n$thecommand"
-
diff --git a/fsl_sub b/fsl_sub
new file mode 100644
index 000000000000..cf74a2795289
--- /dev/null
+++ b/fsl_sub
@@ -0,0 +1,544 @@
+#!/bin/bash
+
+# Copyright (C) 2007-2017 University of Oxford
+# Authors: Dave Flitney, Stephen Smith, Matthew Webster and Duncan Mortimer
+
+# Part of FSL - FMRIB's Software Library
+# http://www.fmrib.ox.ac.uk/fsl
+# fsl@fmrib.ox.ac.uk
+#
+# Developed at FMRIB (Oxford Centre for Functional Magnetic Resonance
+# Imaging of the Brain), Department of Clinical Neurology, Oxford
+# University, Oxford, UK
+#
+#
+# LICENCE
+#
+# FMRIB Software Library, Release 6.0 (c) 2018, The University of
+# Oxford (the "Software")
+#
+# The Software remains the property of the Oxford University Innovation
+# ("the University").
+#
+# The Software is distributed "AS IS" under this Licence solely for
+# non-commercial use in the hope that it will be useful, but in order
+# that the University as a charitable foundation protects its assets for
+# the benefit of its educational and research purposes, the University
+# makes clear that no condition is made or to be implied, nor is any
+# warranty given or to be implied, as to the accuracy of the Software,
+# or that it will be suitable for any particular purpose or for use
+# under any specific conditions. Furthermore, the University disclaims
+# all responsibility for the use which is made of the Software. It
+# further disclaims any liability for the outcomes arising from using
+# the Software.
+#
+# The Licensee agrees to indemnify the University and hold the
+# University harmless from and against any and all claims, damages and
+# liabilities asserted by third parties (including claims for
+# negligence) which arise directly or indirectly from the use of the
+# Software or the sale of any products based on the Software.
+#
+# No part of the Software may be reproduced, modified, transmitted or
+# transferred in any form or by any means, electronic or mechanical,
+# without the express permission of the University. The permission of
+# the University is not required if the said reproduction, modification,
+# transmission or transference is done without financial return, the
+# conditions of this Licence are imposed upon the receiver of the
+# product, and all original and amended source code is included in any
+# transmitted product. You may be held legally responsible for any
+# copyright infringement that is caused or encouraged by your failure to
+# abide by these terms and conditions.
+#
+# You are not permitted under this Licence to use this Software
+# commercially. Use for which any financial return is received shall be
+# defined as commercial use, and includes (1) integration of all or part
+# of the source code or the Software into a product for sale or license
+# by or on behalf of Licensee to third parties or (2) use of the
+# Software or any derivative of it for research with the final aim of
+# developing software products for sale or license to a third party or
+# (3) use of the Software or any derivative of it for research with the
+# final aim of developing non-software products for sale or license to a
+# third party, or (4) use of the Software to provide any service to an
+# external organisation for which payment is received. If you are
+# interested in using the Software commercially, please contact Oxford
+# University Innovation ("OUI"), the technology transfer company of the
+# University, to negotiate a licence. Contact details are:
+# fsl@innovation.ox.ac.uk quoting Reference Project 9564, FSL.
+export LC_ALL=C
+export LC_ALL=C
+
+###########################################################################
+# Edit this file in order to setup FSL to use your local compute
+# cluster.
+###########################################################################
+set +o errexit
+
+###########################################################################
+# The following section determines what to do when fsl_sub is called
+# by an FSL program. If SGE_ROOT is set it will attempt to pass the
+# commands onto the cluster, otherwise it will run the commands
+# itself. There are two values for the METHOD variable, "SGE" and
+# "NONE". Note that a user can unset SGE_ROOT if they don't want the
+# cluster to be used.
+###########################################################################
+METHOD=SGE
+unset module
+if [[ "x$SGE_ROOT" = "x" ]] ; then
+ METHOD=NONE
+else
+ QCONF=$(which qconf)
+ if [[ "x$QCONF" = "x" ]]; then
+ METHOD=NONE
+ echo "Warning: SGE_ROOT environment variable is set but Grid Engine software not found, will run locally" >&2
+ fi
+fi
+
+# stop submitted scripts from submitting jobs themselves
+if [[ "X$FSLSUBALREADYRUN" = "Xtrue" ]] ; then
+ METHOD=NONE
+ echo "Warning: job on queue attempted to submit parallel jobs - running jobs serially instead" >&2
+fi
+
+if [[ "X$METHOD" = "XNONE" ]]; then
+ QCONF=echo
+fi
+FSLSUBALREADYRUN=true
+export FSLSUBALREADYRUN
+
+###########################################################################
+# The following auto-decides what cluster queue to use. The calling
+# FSL program will probably use the -T option when calling fsl_sub,
+# which tells fsl_sub how long (in minutes) the process is expected to
+# take (in the case of the -t option, how long each line in the
+# supplied file is expected to take). You need to setup the following
+# list to map ranges of timings into your cluster queues - it doesn't
+# matter how many you setup, that's up to you.
+###########################################################################
+
+map_qname ()
+{
+ if [[ "$1" -le 20 ]] ; then
+ queue=veryshort.q
+ elif [[ "$1" -le 120 ]] ; then
+ queue=short.q
+ elif [[ "$1" -le 1440 ]] ; then
+ queue=long.q
+ else
+ queue=verylong.q
+ fi
+ queueCmd=" -q $queue "
+
+ #echo "Estimated time was $1 mins: queue name is $queue"
+}
+
+###########################################################################
+# Don't change the following (but keep scrolling down!)
+###########################################################################
+
+if [[ ! -z "${POSIXLY_CORRECT}" ]]; then
+ OLD_POSIXLY_CORRECT=${POSIXLY_CORRECT}
+fi
+
+POSIXLY_CORRECT=1
+export POSIXLY_CORRECT
+command=$(basename "$0")
+
+usage ()
+{
+ cat <<EOF
+
+$command V1.1 - wrapper for job control system such as SGE
+
+Usage: $command [options] <command>
+
+$command gzip *.img *.hdr
+$command -q short.q gzip *.img *.hdr
+$command -a darwin regscript rawdata outputdir ...
+
+ -T <minutes> Estimated job length in minutes, used to auto-set queue name
+ -q <queuename> Possible values for <queuename> are "verylong.q", "long.q"
+ and "short.q". See below for details
+ Default is "long.q".
+ -a <arch-name> Architecture [e.g., darwin or lx24-amd64]
+ -p <job-priority> Lower priority [0:-1024] default = 0
+ -M <email-address> Who to email, default = $(whoami)@$(hostname -f | cut -d . -f 2-)
+ -j <jid> Place a hold on this task until job jid has completed
+ -t <filename> Specify a task file of commands to execute in parallel
+ -N <jobname> Specify jobname as it will appear on queue
+ -R <RAM> Max total RAM to use for job (integer in MB)
+ -l <logdirname> Where to output logfiles
+ -m <mailoptions> Change the SGE mail options, see qsub for details
+ -z <output> If <output> image or file already exists, do nothing and exit
+ -F Use flags embedded in scripts to set SGE queuing options
+ -s <pename>,<threads> Submit a multi-threaded task - requires a PE (<pename>) to be
+ configured for the requested queues.
+ <threads> specifies the number of threads to run
+ -v Verbose mode.
+
+Queues:
+
+There are several batch queues configured on the cluster, each with defined CPU
+time limits. All queues, except bigmem.q, have a 8GB memory limit.
+
+veryshort.q:This queue is for jobs which last under 30mins.
+short.q: This queue is for jobs which last up to 4h.
+long.q: This queue is for jobs which last less than 24h. Jobs run with a
+ nice value of 10.
+verylong.q: This queue is for jobs which will take longer than 24h CPU time.
+ There is one slot per node, and jobs on this queue have a nice value
+ of 15.
+bigmem.q: This queue is like the verylong.q but has no memory limits.
+
+EOF
+
+ exit 1
+}
+
+nargs=$#
+if [[ "$nargs" -eq 0 ]] ; then
+ usage
+fi
+
+#if the newer whitespace-safe getopt format is available, use it
+if [[ $(getopt -T >/dev/null 2>&1; echo $?) == 4 ]]; then
+ eval set -- $(getopt -s bash T:q:a:p:M:j:t:z:N:R:Fvm:l:s: "$@")
+ result=$?
+else
+ set -- $(getopt T:q:a:p:M:j:t:z:N:R:Fvm:l:s: "$@")
+ result=$?
+fi
+if [[ "$result" != 0 ]] ; then
+ echo "What? Your arguments make no sense!"
+fi
+
+if [[ "$nargs" -eq 0 ]] || [[ $result != 0 ]] ; then
+ usage
+fi
+
+if [[ -z "${OLD_POSIXLY_CORRECT}" ]]; then
+ unset POSIXLY_CORRECT
+else
+ POSIXLY_CORRECT=${OLD_POSIXLY_CORRECT}
+ export POSIXLY_CORRECT
+fi
+
+###########################################################################
+# If you have a Parallel Environment configured for OpenMP tasks then
+# the variable omp_pe should be set to the name you have defined for that
+# PE. The script will work out which queues have that PE setup on them.
+# Note, we support openmp tasks even when Grid Engine is not in use.
+###########################################################################
+
+omp_pe='openmp'
+
+
+###########################################################################
+# If you wish to disable processor affinities under Grid Engine then
+# comment the following line.
+# This instructs Grid Engine to bind the task to the number of job slots
+# allocated to the job (or PE)
+###########################################################################
+proc_affinities="-binding linear:slots"
+
+
+###########################################################################
+# The following sets up the default queue name, which you may want to
+# change. It also sets up the basic emailing control.
+###########################################################################
+
+queue=long.q
+queueCmd=" -q long.q "
+mailto=$(whoami)@$(hostname -f | cut -d . -f 2-)
+MailOpts="a"
+
+
+###########################################################################
+# In the following, you might want to change the behaviour of some
+# flags so that they prepare the right arguments for the actual
+# cluster queue submission program, in our case "qsub".
+#
+# -a sets is the cluster submission flag for controlling the required
+# hardware architecture (normally not set by the calling program)
+#
+# -p set the priority of the job - ignore this if your cluster
+# environment doesn't have priority control in this way.
+#
+# -j tells the cluster not to start this job until cluster job ID $jid
+# has completed. You will need this feature.
+#
+# -t will pass on to the cluster software the name of a text file
+# containing a set of commands to run in parallel; one command per
+# line.
+#
+# -N option determines what the command will be called when you list
+# running processes.
+#
+# -l tells the cluster what to call the standard output and standard
+# -error logfiles for the submitted program.
+###########################################################################
+
+if [[ -z "$FSLSUBVERBOSE" ]] ; then
+ verbose=0
+else
+ verbose=$FSLSUBVERBOSE;
+ echo "METHOD=$METHOD : args=$*" >&2
+fi
+
+scriptmode=0
+
+while [[ "$1" != -- ]] ; do
+ case "$1" in
+ -z)
+ if [[ -e "$2" || $("${FSLDIR}/bin/imtest" "$2") = 1 ]] ; then
+ exit 0
+ fi
+ shift;;
+ -T)
+ map_qname "$2"
+ shift;;
+ -q)
+ queue="$2"
+ queueCmd=" -q $queue "
+ "$QCONF" -sq "$queue" >/dev/null 2>&1
+ if [[ $? -eq 1 ]]; then
+ echo "Invalid queue specified!"
+ exit 127
+ fi
+ shift;;
+ -a)
+ acceptable_arch=no
+ available_archs=$(qhost | tail -n +4 | awk '{print $2}' | sort | uniq)
+ for a in $available_archs; do
+ if [[ "$2" = "$a" ]] ; then
+ acceptable_arch="yes"
+ fi
+ done
+ if [[ "$acceptable_arch" = "yes" ]]; then
+ sge_arch="-l arch=$2"
+ else
+ echo "Sorry arch of $2 is not supported on this SGE configuration!"
+ echo "Should be one of: $available_archs"
+ exit 127
+ fi
+ shift;;
+ -p)
+ # Not implmented
+ shift;;
+ -M)
+ mailto=$2
+ shift;;
+ -j)
+ jid=$2
+ sge_hold="-hold_jid $jid"
+ shift;;
+ -t)
+ taskfile=$2
+ if [[ -f "$taskfile" ]] ; then
+ tasks=$(wc -l "$taskfile" | awk '{print $1}')
+ if [[ "$tasks" -ne 0 ]]; then
+ sge_tasks="-t 1-$tasks"
+ else
+ echo "Task file ${taskfile} is empty"
+ echo "Should be a text file listing all the commands to run!"
+ exit -1
+ fi
+ else
+ echo "Task file (${taskfile}) does not exist"
+ exit -1
+ fi
+ shift;;
+ -N)
+ JobName=$2;
+ shift;;
+ -R)
+ RAM="-l mem_free=${2}M"
+ shift;;
+ -m)
+ MailOpts=$2;
+ shift;;
+ -l)
+ LogOpts="-o $2 -e $2";
+ LogDir="${2}/";
+ if [[ ! -e "${2}" ]]; then
+ mkdir -p "$2"
+ else
+ echo "${2}" | grep '/dev/null' >/dev/null 2>&1
+ if [[ $? -eq 1 ]] && [[ -f "${2}" ]]; then
+ echo "Log destination is a file (should be a folder)"
+ exit -1
+ fi
+ fi
+ shift;;
+ -F)
+ scriptmode=1;
+ ;;
+ -v)
+ verbose=1
+ ;;
+ -s)
+ pe_string=$2;
+ peName=$(echo "$pe_string" | cut -d',' -f 1)
+ peThreads=$(echo "$pe_string" | cut -d',' -f 2)
+ shift;;
+ esac
+ shift # next flag
+done
+shift
+
+###########################################################################
+# Don't change the following (but keep scrolling down!)
+###########################################################################
+commandline=("$@")
+command="${commandline[0]}"
+
+if [[ -z "$taskfile" ]] && [[ -z "$command" ]]; then
+ echo "Either supply a command to run or a parallel task file"
+ exit -1
+fi
+
+if [[ -z "$taskfile" ]] && [[ ! -x "$command" ]]; then
+ which "$command" >/dev/null 2>&1
+ if [[ $? -ne 0 ]]; then
+ echo "The command you have requested cannot be found or is not executable"
+ exit -1
+ fi
+fi
+
+if [[ "x$JobName" = x ]] ; then
+ if [[ "x$taskfile" != x ]] ; then
+ JobName=$(basename "$taskfile")
+ else
+ JobName=$(basename "$command")
+ fi
+fi
+
+if [[ -n "$tasks" ]] && [[ -n "${commandline[*]}" ]] ; then
+ echo "Spurious input after parsing command line: \"${commandline[*]}\"!"
+ echo "You appear to have specified both a task file and a command to run"
+ exit -1
+fi
+
+if [[ -n "$peName" ]]; then
+ # If the PE name is 'openmp' then limit the number of threads to those specified
+ if [[ "X$peName" = "X$omp_pe" ]]; then
+ OMP_NUM_THREADS=$peThreads
+ export OMP_NUM_THREADS
+ fi
+fi
+
+case "$METHOD" in
+
+###########################################################################
+# The following is the main call to the cluster, using the "qsub" SGE
+# program. If $tasks has not been set then qsub is running a single
+# command, otherwise qsub is processing a text file of parallel
+# commands.
+###########################################################################
+
+ SGE)
+ ###########################################################################
+ # Test Parallel environment options
+ ###########################################################################
+ if [[ -n "$peName" ]]; then
+ # Is this a configured PE?
+
+ "$QCONF" -sp "$peName" >/dev/null 2>&1
+
+ if [[ $? -eq 1 ]]; then
+ echo "${commandline[*]}"
+ echo "$peName is not a valid PE"
+ exit -1
+ fi
+
+ # Get a list of queues configured for this PE and confirm that the queue
+ # we have submitted to has that PE set up.
+ qstat -g c -pe "$peName" >/dev/null 2>&1
+ if [[ $? -eq 1 ]]; then
+ echo "No parallel environments configured!"
+ exit -1
+ fi
+
+ qstat -g c -pe "$peName" | sed '1,2d' | awk '{ print $1 }' | grep "^$queue" >/dev/null 2>&1
+
+ if [[ $? -eq 1 ]]; then
+ echo "${commandline[*]}"
+ echo "PE $peName is not configured on $queue"
+ exit -1
+ fi
+
+ # The -w e option will result in the job failing if there are insufficient slots
+ # on any of the cluster nodes
+ pe_options="-pe $peName $peThreads -w e"
+ fi
+
+ if [[ -z "$tasks" ]] ; then
+ if [[ "$scriptmode" -ne 1 ]] ; then
+ sge_command=(qsub -V -cwd -shell n -b y -r y $queueCmd $proc_affinities $pe_options -M $mailto -N "$JobName" -m $MailOpts $LogOpts $sge_arch $RAM $sge_hold)
+ else
+ sge_command=(qsub $proc_affinities $LogOpts $sge_arch $sge_hold)
+ fi
+ if [[ "$verbose" -eq 1 ]] ; then
+ echo "sge_command: ${sge_command[*]}" >&2
+ echo "executing: ${commandline[*]}" >&2
+ fi
+ exec "${sge_command[@]}" "${commandline[@]}" | awk '{print $3}'
+ else
+ sge_command=(qsub -V -cwd $queueCmd $proc_affinities $pe_options -M $mailto -N "$JobName" -m $MailOpts $LogOpts $sge_arch $RAM $sge_hold $sge_tasks)
+ if [[ "$verbose" -eq 1 ]] ; then
+ echo "sge_command: ${sge_command[*]}" >&2
+ echo "control file: $taskfile" >&2
+ fi
+ exec "${sge_command[@]}" <<EOF | awk '{print $3}' | awk -F. '{print $1}'
+#!/bin/sh
+
+#$ -S /bin/sh
+
+command=\`sed -n -e "\${SGE_TASK_ID}p" $taskfile\`
+
+exec /bin/sh -c "\$command"
+EOF
+ fi
+ ;;
+
+###########################################################################
+# Don't change the following - this runs the commands directly if a
+# cluster is not being used.
+###########################################################################
+
+ NONE)
+ if [[ "x$tasks" = "x" ]] ; then
+ if [[ "$verbose" -eq 1 ]] ; then
+ echo executing: "${commandline[*]}" >&2
+ fi
+
+ "${commandline[@]}" > "${LogDir}${JobName}.o$$" 2> "${LogDir}${JobName}.e$$"
+ ERR=$?
+ if [[ "$ERR" -ne 0 ]] ; then
+ cat "${LogDir}${JobName}.e$$" >&2
+ exit $ERR
+ fi
+ else
+ if [[ "$verbose" -eq 1 ]] ; then
+ echo "Running commands in: $taskfile" >&2
+ fi
+
+ n=1
+ while [[ "$n" -le "$tasks" ]] ; do
+ line=$(sed -n -e ''${n}'p' "$taskfile")
+ if [[ "$verbose" -eq 1 ]] ; then
+ echo "executing: $line" >&2
+ fi
+ /bin/sh <<EOF2 > "${LogDir}${JobName}.o$$.$n" 2> "${LogDir}${JobName}.e$$.$n"
+$line
+EOF2
+ n=$((n+1))
+ done
+ fi
+ echo $$
+ ;;
+
+esac
+
+###########################################################################
+# Done.
+###########################################################################
diff --git a/fslinstaller.py b/fslinstaller.py
new file mode 100644
index 000000000000..a5d01121f392
--- /dev/null
+++ b/fslinstaller.py
@@ -0,0 +1,2892 @@
+#!/usr/bin/env python
+#
+# SHBASECOPYRIGHT
+#
+# FSL installer script.
+#
+"""This is the FSL installation script, which can be used to install FSL.
+
+This script must:
+
+ - be able to be executed with Python 2.7 or newer.
+
+ - be able to be executed in a "vanilla" Python environment, with no third
+ party dependencies.
+
+ - be self-contained, with no dependencies on any other modules (apart from
+ the Python standard library).
+
+ - be importable as a Python module - this script contains functions and
+ classes that may be used by other scripts.
+"""
+
+
+from __future__ import print_function, division, unicode_literals
+
+import functools as ft
+import os.path as op
+import subprocess as sp
+import textwrap as tw
+import argparse
+import contextlib
+import datetime
+import fnmatch
+import getpass
+import glob
+import hashlib
+import json
+import logging
+import os
+import platform
+import pwd
+import readline
+import shlex
+import shutil
+import ssl
+import sys
+import tempfile
+import threading
+import time
+import traceback
+
+try:
+ import urllib.request as urlrequest
+except ImportError:
+ import urllib
+ import urllib2 as urlrequest
+ urlrequest.pathname2url = urllib.pathname2url
+
+
+try: import urllib.parse as urlparse
+except ImportError: import urlparse
+
+try: import queue
+except ImportError: import Queue as queue
+
+
+PYVER = sys.version_info[:2]
+
+
+log = logging.getLogger(__name__)
+
+
+# this sometimes gets set to fslinstaller.pyc, so rstrip c
+__absfile__ = op.abspath(__file__).rstrip('c')
+
+
+__version__ = '3.9.0'
+"""Installer script version number. This must be updated
+whenever a new version of the installer script is released.
+"""
+
+
+DEFAULT_INSTALLATION_DIRECTORY = op.join(op.expanduser('~'), 'fsl')
+"""Default FSL installation directory. """
+
+
+DEFAULT_ROOT_INSTALLATION_DIRECTORY = '/usr/local/fsl/'
+"""Default FSL installation directory when the installer is run as root. """
+
+
+FSL_RELEASE_MANIFEST = 'https://fsl.fmrib.ox.ac.uk/fsldownloads/' \
+ 'fslconda/releases/manifest.json'
+"""URL to download the FSL installer manifest file from. The installer
+manifest file is a JSON file which contains information about available FSL
+versions.
+
+See the download_manifest function, and an example manifest file
+in test/data/manifest.json, for more details.
+
+A custom manifest URL can be specified with the -a/--manifest command-line
+option.
+"""
+
+
+FSL_DEV_RELEASES = 'https://fsl.fmrib.ox.ac.uk/fsldownloads/' \
+ 'fslconda/releases/devreleases.txt'
+"""URL to the devreleases.txt file, which contains a list of available
+internal/development FSL releases. See the download_dev_releases function
+for more details.
+"""
+
+
+# List of modifiers which can be used to change how
+# a message is printed by the printmsg function.
+INFO = 1
+IMPORTANT = 2
+QUESTION = 3
+PROMPT = 4
+WARNING = 5
+ERROR = 6
+EMPH = 7
+EMPHASIS = 7
+UNDERLINE = 8
+RESET = 9
+ANSICODES = {
+ INFO : '\033[37m', # Light grey
+ IMPORTANT : '\033[92m', # Green
+ QUESTION : '\033[36m\033[4m', # Blue+underline
+ PROMPT : '\033[36m\033[1m', # Bright blue+bold
+ WARNING : '\033[93m', # Yellow
+ ERROR : '\033[91m', # Red
+ EMPHASIS : '\033[1m', # White+bold
+ UNDERLINE : '\033[4m', # Underline
+ RESET : '\033[0m', # Used internally
+}
+
+def get_terminal_width(fallback=None):
+ """Return the number of columns in the current terminal, or fallback
+ if it cannot be determined.
+ """
+ # os.get_terminal_size added in python
+ # 3.3, so we try it but fall back to
+ # COLUMNS, or tput as a last resort.
+ try:
+ return shutil.get_terminal_size()[0]
+ except Exception:
+ pass
+
+ try:
+ return int(os.environ['COLUMNS'])
+ except Exception:
+ pass
+
+ try:
+ result = Process.check_output('tput cols', log_output=False)
+ return int(result.strip())
+ except Exception:
+ return fallback
+
+
+def printmsg(*args, **kwargs):
+ """Prints a sequence of strings formatted with ANSI codes. Expects
+ positional arguments to be of the form::
+
+ printable, ANSICODE, printable, ANSICODE, ...
+
+ :arg log: Must be specified as a keyword argument. If True (default),
+ the message is logged.
+
+ :arg fill: Must be specified as a keyword argument. If True (default),
+ the message is wrapped to the terminal width.
+
+ All other keyword arguments are passed through to the print function.
+ """
+
+ args = list(args)
+ blockids = [i for i in range(len(args)) if (args[i] not in ANSICODES)]
+ logmsg = kwargs.pop('log', True)
+ fill = kwargs.pop('fill', True)
+
+ coded = ''
+ uncoded = ''
+
+ for i, idx in enumerate(blockids):
+ if i == len(blockids) - 1:
+ slc = slice(idx + 1, None)
+ else:
+ slc = slice(idx + 1, blockids[i + 1])
+
+ msg = args[idx]
+ msgcodes = args[slc]
+ msgcodes = [ANSICODES[c] for c in msgcodes]
+ msgcodes = ''.join(msgcodes)
+ uncoded += msg
+ coded += '{}{}{}'.format(msgcodes, msg, ANSICODES[RESET])
+
+ if len(blockids) > 0:
+
+ if fill:
+ width = get_terminal_width(70)
+ coded = tw.fill(coded, width, replace_whitespace=False)
+
+ print(coded, **kwargs)
+
+ if logmsg:
+ log.debug(uncoded)
+
+ sys.stdout.flush()
+
+
+def prompt(promptmsg, *msgtypes, **kwargs):
+ """Prompts the user for some input. msgtypes and kwargs are passed
+ through to the printmsg function.
+ """
+ printmsg(promptmsg, *msgtypes, end='', log=False, **kwargs)
+
+ if PYVER[0] == 2: response = raw_input(' ').strip()
+ else: response = input( ' ').strip()
+
+ log.debug('%s: %s', promptmsg, response)
+
+ return response
+
+
+def post_request(url, data):
+ """Send JSON data to a URL via a HTTP POST request. """
+
+ data = json.dumps(data).encode('utf-8')
+ headers = {}
+ headers['Content-Type'] = 'application/json'
+ resp = None
+
+ try:
+ req = urlrequest.Request(url,
+ headers=headers,
+ data=data)
+ resp = urlrequest.urlopen(req)
+ finally:
+ if resp:
+ resp.close()
+
+
+def identify_platform():
+ """Figures out what platform we are running on. Returns a platform
+ identifier string - one of:
+
+ - "linux-64" (Linux, x86_64)
+ - "macos-64" (macOS, x86_64)
+ - "macos-M1" (macOS, M1)
+
+ Note that these identifiers are for FSL releases, and are not the
+ same as the platform identifiers used by conda.
+ """
+
+ platforms = {
+ ('linux', 'x86_64') : 'linux-64',
+ ('darwin', 'x86_64') : 'macos-64',
+ ('darwin', 'arm64') : 'macos-M1',
+ }
+
+ system = platform.system().lower()
+ cpu = platform.machine()
+ key = (system, cpu)
+
+ if key not in platforms:
+ supported = ', '.join(['[{}, {}]' for s, c in platforms])
+ raise Exception('This platform [{}, {}] is unrecognised or '
+ 'unsupported! Supported platforms: {}'.format(
+ system, cpu, supported))
+
+ return platforms[key]
+
+
+def timestamp():
+ """Return a string containing the local time, with time zone offset.
+ """
+ now = datetime.datetime.now()
+ offset = (now - datetime.datetime.utcnow())
+ offset = round(offset.total_seconds())
+ hours = int(offset / 3600)
+ minutes = int((offset % 3600) / 60)
+ now = now.strftime('%Y-%m-%dT%H:%M:%S')
+ return '{}{:+03d}:{:02d}'.format(now, hours, minutes)
+
+
+def check_need_admin(dirname):
+ """Returns True if dirname needs administrator privileges to write to,
+ False otherwise.
+ """
+ # os.supports_effective_ids added in
+ # python 3.3, so can't be used here
+ return not os.access(dirname, os.W_OK | os.X_OK)
+
+
+def get_admin_password(action='install FSL'):
+ """Prompt the user for their administrator password. An Exception is raised
+ if an incorrect password is entered three times.a
+
+ :arg action: String which describes what the password is needed for, i.e.:
+ "Your administrator password is needed to {action}"
+ :returns: the validated administrator password
+ """
+
+ def validate_admin_password(password):
+ proc = Process.sudo_popen(['true'], password, stdin=sp.PIPE)
+ proc.communicate()
+ return proc.returncode == 0
+
+ msg = 'Your administrator password is needed to {}'.format(action)
+
+ for attempt in range(3):
+ if attempt == 0: msg = '{}:'.format(msg)
+ else: msg = '{} [attempt {} of 3]:'.format(msg, attempt + 1)
+ printmsg(msg, IMPORTANT, end='')
+ password = getpass.getpass('')
+ valid = validate_admin_password(password)
+
+ if valid:
+ printmsg('Password accepted', INFO)
+ break
+ else:
+ printmsg('Incorrect password', WARNING)
+
+ if not valid:
+ raise Exception('Incorrect password')
+
+ return password
+
+
+def isstr(s):
+ """Returns True if s is a string, False otherwise. Works on python 2.7
+ and >=3.3.
+ """
+ try: return isinstance(s, basestring)
+ except Exception: return isinstance(s, str)
+
+
+def match_any(s, patterns):
+ """Test if the string s matches any of the fnmatch-style patterns.
+ Returns the matched pattern, or None.
+ """
+ for pat in patterns:
+ if fnmatch.fnmatch(s, pat):
+ return pat
+ return None
+
+
+@contextlib.contextmanager
+def tempdir(override_dir=None, change_into=True, delete=True):
+ """Returns a context manager which creates, changes into, and returns a
+ temporary directory, and then deletes it on exit (unless delete is False).
+
+ If override_dir is not None, instead of creating and changing into a
+ temporary directory, this function just changes into override_dir.
+ """
+
+ if override_dir is None: tmpdir = tempfile.mkdtemp()
+ else: tmpdir = override_dir
+
+ prevdir = os.getcwd()
+
+ try:
+ if change_into:
+ os.chdir(tmpdir)
+ yield tmpdir
+
+ finally:
+ if change_into:
+ os.chdir(prevdir)
+ if delete and override_dir is None:
+ shutil.rmtree(tmpdir)
+
+
+def warn_on_error(*msgargs, **msgkwargs):
+ """Decorator which tries to run a function, and prints a message if it
+ fails. The arguments after the function are passed to the printmsg
+ function, e.g.:
+
+ @warn_on_error('Function failed!', WARNING)
+ def function(a, b):
+ ...
+
+ function('a', 'b')
+
+ :arg toscreen: Defaults to True. Print the warning to the screen.
+ :arg tolog: Defaults to True. Print the warning to the log file.
+ """
+
+ toscreen = msgkwargs.pop('toscreen', True)
+ tolog = msgkwargs.pop('tolog', True)
+
+ def decorator(function):
+ def wrapper(*args, **kwargs):
+ try:
+ function(*args, **kwargs)
+ except Exception as e:
+ if toscreen: printmsg(*msgargs, **msgkwargs)
+ if tolog: log.debug('%s', e, exc_info=True)
+ return wrapper
+ return decorator
+
+
+@contextlib.contextmanager
+def tempfilename(permissions=None, delete=True):
+ """Returns a context manager which creates a temporary file, yields its
+ name, then deletes the file on exit.
+ """
+
+ fname = None
+
+ try:
+ tmpf = tempfile.NamedTemporaryFile(delete=False)
+ fname = tmpf.name
+
+ tmpf.close()
+
+ if permissions:
+ os.chmod(fname, permissions)
+
+ yield fname
+
+ finally:
+ if delete and fname and op.exists(fname):
+ os.remove(fname)
+
+
+def sha256(filename, check_against=None, blocksize=1048576):
+ """Calculate the SHA256 checksum of the given file. If check_against
+ is provided, it is compared against the calculated checksum, and an
+ error is raised if they are not the same.
+ """
+
+ hashobj = hashlib.sha256()
+
+ with open(filename, 'rb') as f:
+ while True:
+ block = f.read(blocksize)
+ if len(block) == 0:
+ break
+ hashobj.update(block)
+
+ checksum = hashobj.hexdigest()
+
+ if check_against is not None:
+ if checksum != check_against:
+ raise Exception('File {} does not match expected checksum '
+ '({})'.format(filename, check_against))
+
+ return checksum
+
+
+def clean_environ():
+ """Return a dict containing a set of sanitised environment variables.
+
+ All FSL and conda related variables are removed.
+ """
+ env = os.environ.copy()
+ for v in list(env.keys()):
+ if any(('FSL' in v, 'CONDA' in v, 'MAMBA' in v, 'PYTHON' in v)):
+ env.pop(v)
+ return env
+
+
+def install_environ(fsldir, username=None, password=None):
+ """Returns a dict containing some environment variables that should
+ be added to the shell environment when the FSL conda environment is
+ being installed.
+ """
+ env = {}
+ # post-link scripts call $FSLDIR/share/fsl/sbin/createFSLWrapper
+ # (part of fsl/base), which will only do its thing if the following
+ # env vars are set
+ env['FSL_CREATE_WRAPPER_SCRIPTS'] = '1'
+ env['FSLDIR'] = fsldir
+
+ # Make sure HTTP proxy variables, if set,
+ # are available to the conda env command
+ for v in ['http_proxy', 'https_proxy',
+ 'HTTP_PROXY', 'HTTPS_PROXY']:
+ if v in os.environ:
+ env[v] = os.environ[v]
+
+ # Tell mamba not to abort if the download is taking time
+ # https://github.com/mamba-org/mamba/issues/1941
+ env['MAMBA_NO_LOW_SPEED_LIMIT'] = '1'
+
+ # FSL environments which source packages from the internal
+ # FSL conda channel will refer to the channel as:
+ #
+ # http://${FSLCONDA_USERNAME}:${FSLCONDA_PASSWORD}/abc.com/
+ #
+ # so we need to set those variables
+ if username: env['FSLCONDA_USERNAME'] = username
+ if password: env['FSLCONDA_PASSWORD'] = password
+
+ return env
+
+
+def download_file(url,
+ destination,
+ progress=None,
+ blocksize=131072,
+ ssl_verify=True):
+ """Download a file from url, saving it to destination. """
+
+ def default_progress(downloaded, total):
+ pass
+
+ if progress is None:
+ progress = default_progress
+
+ log.debug('Downloading %s ...', url)
+
+ # Path to local file
+ if op.exists(url):
+ url = 'file:' + urlrequest.pathname2url(op.abspath(url))
+
+ # We create and use an unconfigured SSL
+ # context to disable SSL verification.
+ # Otherwise pass None causes urlopen to
+ # use default behaviour.
+ kwargs = {}
+ if not ssl_verify:
+
+ # - The urlopen(context) argument is not available in py3.3
+ # - py3.4 does not have PROTOCOL_TLS
+ # - PROTOCOL_TLS deprecated in py3.10
+ if PYVER == (3, 3): pro = None
+ elif hasattr(ssl, 'PROTOCOL_TLS_CLIENT'): pro = ssl.PROTOCOL_TLS_CLIENT
+ elif hasattr(ssl, 'PROTOCOL_TLS'): pro = ssl.PROTOCOL_TLS
+ elif hasattr(ssl, 'PROTOCOL_TLSv1_2'): pro = ssl.PROTOCOL_TLSv1_2
+ elif hasattr(ssl, 'PROTOCOL_TLSv1_1'): pro = ssl.PROTOCOL_TLSv1_1
+ elif hasattr(ssl, 'PROTOCOL_TLSv1'): pro = ssl.PROTOCOL_TLSv1
+ else: pro = None
+
+ if pro is None:
+ printmsg('SSL verification cannot be skipped - if this is '
+ 'a problem, try running the installer with a newer '
+ 'version of Python.', INFO)
+ else:
+ printmsg('Skipping SSL verification - this '
+ 'is not recommended!', WARNING)
+
+ sslctx = ssl.SSLContext(pro)
+ sslctx.check_hostname = False
+ sslctx.verify_mode = ssl.CERT_NONE
+ kwargs['context'] = sslctx
+
+ req = None
+
+ try:
+ # py2: urlopen result cannot be
+ # used as a context manager
+ req = urlrequest.urlopen(url, **kwargs)
+ with open(destination, 'wb') as outf:
+
+ try: total = int(req.headers['content-length'])
+ except KeyError: total = None
+
+ downloaded = 0
+
+ progress(downloaded, total)
+ while True:
+ block = req.read(blocksize)
+ if len(block) == 0:
+ break
+ downloaded += len(block)
+ outf.write(block)
+ progress(downloaded, total)
+
+ finally:
+ if req:
+ req.close()
+
+
+def download_manifest(url, workdir=None, **kwargs):
+ """Downloads the installer manifest file, which contains information
+ about available FSL versions, and the most recent version number of the
+ installer (this script).
+
+ Keyword arguments are passed through to the download_file function.
+
+ The manifest file is a JSON file. Lines beginning with a double-forward
+ slash are ignored.
+
+ This function modifies the manifest structure by adding a 'version'
+ attribute to all FSL build entries.
+ """
+
+ log.debug('Downloading FSL installer manifest from %s', url)
+
+ with tempdir(workdir):
+
+ try:
+ download_file(url, 'manifest.json', **kwargs)
+ except Exception as e:
+ log.debug('Error downloading FSL release manifest from %s',
+ url, exc_info=True)
+ raise Exception('Unable to download FSL release manifest '
+ 'from {} [{}]!'.format(url, str(e)))
+
+ with open('manifest.json') as f:
+ lines = f.readlines()
+
+ # Drop comments
+ lines = [l for l in lines if not l.lstrip().startswith('//')]
+
+ manifest = json.loads('\n'.join(lines))
+
+ # Add "version" to every build
+ for version, builds in manifest['versions'].items():
+ if version == 'latest':
+ continue
+ for build in builds:
+ build['version'] = version
+
+ return manifest
+
+
+def download_dev_releases(url, workdir=None, **kwargs):
+ """Downloads the FSL_DEV_RELEASES file. This file contains a list of
+ available development manifest URLS. Returns a list of tuples, one
+ for each development release, with each tuple containing:
+
+ - URL to the manifest file
+ - Version identifier
+ - Commit hash (on the fsl/conda/manifest repository)
+ - Branch name (on the fsl/conda/manifest repository)
+
+ The list is sorted by date, newest first.
+
+ Keyword arguments are passed through to the download_file function.
+ """
+
+ # parse a dev manifest file name, returning
+ # a sequence containing the tage, date, commit
+ # hash, and branch name. Dev manifest files
+ # are named like so:
+ #
+ # manifest-<tag>.<date>.<commit>.<branch>.json
+ #
+ # where <tag> is the tag of the most recent
+ # public FSL release, and everything else is
+ # self-explanatory.
+ def parse_devrelease_name(url):
+ name = urlparse.urlparse(url).path
+ name = op.basename(name)
+ name = name.lstrip('manifest-').rstrip('.json')
+
+ # The devrelease list may contain public
+ # releases too - sniff the commit, and if
+ # it doesn't look like a commit hash,
+ # assume that this file corresponds to a
+ # public release.
+ commit = name.rsplit('.', 2)[-2]
+
+ # public release or dev release
+ if len(commit) < 7: bits = [name, None, None]
+ else: bits = name.rsplit('.', 2)
+
+ return bits
+
+ # list of (url, version, commit, branch)
+ devreleases = []
+
+ with tempdir(workdir):
+
+ try:
+ download_file(url, 'devreleases.txt', **kwargs)
+ except Exception as e:
+ log.debug('Error downloading devreleases.txt from %s',
+ url, exc_info=True)
+ raise Exception('Unable to download development manifest '
+ 'list from {}!'.format(url))
+
+ with open('devreleases.txt', 'rt') as f:
+ urls = f.read().strip().split('\n')
+ urls = [l.strip() for l in urls]
+
+ for url in urls:
+ devreleases.append([url] + parse_devrelease_name(url))
+
+ # sort by version, newest first
+ return sorted(devreleases, key=lambda r: Version(r[1]), reverse=True)
+
+
+class Progress(object):
+ """Simple progress reporter. Displays one of the following:
+
+ - If both a value and total are provided, a progress bar is shown
+ - If only a value is provided, a cumulative count is shown
+ - If nothing is provided, a spinner is shown.
+
+ Use as a context manager, and call the update method to report progress,
+ e,g:
+
+ with Progress('%') as p:
+ for i in range(100):
+ p.update(i + 1, 100)
+ """
+
+ def __init__(self,
+ label='',
+ transform=None,
+ fmt='{:.1f}',
+ total=None,
+ width=None,
+ proglabel='progress',
+ progfile=None):
+ """Create a Progress reporter.
+
+ :arg label: Units (e.g. "MB", "%",)
+
+ :arg transform: Function to transform values (see e.g.
+ Progress.bytes_to_mb)
+
+ :arg fmt: Template string used to format value / total.
+
+ :arg total: Maximum value - overrides the total value passed to
+ the update method.
+
+ :arg width: Maximum width, if a progress bar is displayed. Default
+ is to automatically infer the terminal width (see
+ get_terminal_width).
+
+ :arg proglabel: Label to use when writing progress updates to progfile.
+
+ :arg progfile: File to write progress updates to. Each update is
+ written on a new line, and has the form:
+
+ <proglabel> <value>[ <total>]
+ """
+
+ if transform is None:
+ transform = Progress.default_transform
+
+ self.width = width
+ self.fmt = fmt.format
+ self.total = total
+ self.label = label
+ self.transform = transform
+ self.proglabel = proglabel
+ self.progfile = progfile
+
+ # used by the spin function
+ self.__last_spin = None
+
+ @staticmethod
+ def default_transform(val, total):
+ return val, total
+
+ @staticmethod
+ def bytes_to_mb(val, total):
+ if val is not None: val = val / 1048576
+ if total is not None: total = total / 1048576
+ return val, total
+
+ @staticmethod
+ def percent(val, total):
+ if val is None or total is None:
+ return val, total
+ return 100 * (val / total), 100
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args, **kwargs):
+ printmsg('', log=False, fill=False)
+
+ def write_progress(self, value, total):
+
+ if self.progfile is None:
+ return
+
+ if value is None: value = ''
+ if total is None: total = ''
+
+ with open(self.progfile, 'at') as f:
+ f.write('{} {} {}\n'.format(self.proglabel, value, total))
+
+ def update(self, value=None, total=None):
+
+ if total is None:
+ total = self.total
+
+ value, total = self.transform(value, total)
+
+ if value is None and total is None:
+ self.spin()
+ elif value is not None and total is None:
+ self.count(value)
+ elif value is not None and total is not None:
+ self.progress(value, total)
+
+ self.write_progress(value, total)
+
+ def spin(self):
+
+ symbols = ['|', '/', '-', '\\']
+
+ if self.__last_spin is not None: last = self.__last_spin
+ else: last = symbols[-1]
+
+ idx = symbols.index(last)
+ idx = (idx + 1) % len(symbols)
+ this = symbols[idx]
+
+ printmsg(this, end='\r', log=False, fill=False)
+ self.__last_spin = this
+
+ def count(self, value):
+
+ value = self.fmt(value)
+
+ if self.label is None: line = '{} ...'.format(value)
+ else: line = '{}{} ...'.format(value, self.label)
+
+ printmsg(line, end='\r', log=False, fill=False)
+
+ def progress(self, value, total):
+
+ value = min(value, total)
+
+ # arbitrary fallback of 50 columns if
+ # terminal width cannot be determined
+ if self.width is None: width = get_terminal_width(50)
+ else: width = self.width
+
+ fvalue = self.fmt(value)
+ ftotal = self.fmt(total)
+ suffix = '{} / {} {}'.format(fvalue, ftotal, self.label).rstrip()
+
+ # +5: - square brackets around bar
+ # - space between bar and tally
+ # - space+spin at the end
+ width = width - (len(suffix) + 5)
+ completed = int(round(width * (value / total)))
+ remaining = width - completed
+ progress = '[{}{}] {}'.format('#' * completed,
+ ' ' * remaining,
+ suffix)
+
+ printmsg(progress, end='', log=False, fill=False)
+ printmsg(' ', end='', log=False, fill=False)
+ self.spin()
+ printmsg(end='\r', log=False, fill=False)
+
+
+class Process(object):
+ """Container for a subprocess.Popen object, allowing non-blocking
+ line-based access to its standard output and error streams via separate
+ queues, while logging all outputs.
+
+ Don't create a Process directly - use one of the following static methods:
+ - Process.check_output
+ - Process.check_call
+ - Process.monitor_progress
+ """
+
+
+ def __init__(self,
+ cmd,
+ admin=False,
+ password=None,
+ log_output=True,
+ print_output=False,
+ append_env=None,
+ **kwargs):
+ """Run the specified command. Starts threads to capture stdout and
+ stderr.
+
+ :arg cmd: Command to run - passed through shlex.split, then
+ passed to subprocess.Popen
+
+ :arg admin: Run the command with administrative privileges
+
+ :arg password: Administrator password - can be None if admin is
+ False.
+
+ :arg log_output: If True, the command and all of its stdout/stderr
+ are logged.
+
+ :arg print_output: If True, the command and all of its stdout/stderr
+ are logged.
+
+ :arg append_env: Dictionary of additional environment to be set when
+ the command is run.
+
+ :arg kwargs: Passed to subprocess.Popen
+ """
+
+ self.cmd = cmd
+ self.stdoutq = queue.Queue()
+ self.stderrq = queue.Queue()
+
+ if log_output:
+ log.debug('Running %s [as admin: %s]', cmd, admin)
+
+ self.popen = Process.popen(cmd, admin, password,
+ append_env=append_env, **kwargs)
+
+ # threads for consuming stdout/stderr
+ self.stdout_thread = threading.Thread(
+ target=Process.forward_stream,
+ args=(self.popen.stdout, self.stdoutq, cmd,
+ 'stdout', log_output, print_output))
+ self.stderr_thread = threading.Thread(
+ target=Process.forward_stream,
+ args=(self.popen.stderr, self.stderrq, cmd,
+ 'stderr', log_output, print_output))
+
+ self.stdout_thread.daemon = True
+ self.stderr_thread.daemon = True
+ self.stdout_thread.start()
+ self.stderr_thread.start()
+
+
+ def wait(self):
+ """Waits for the process to terminate, then waits for the stdout
+ and stderr consumer threads to finish.
+ """
+ self.popen.wait()
+ self.stdout_thread.join()
+ self.stderr_thread.join()
+
+
+ @property
+ def returncode(self):
+ """Process return code. Returns None until the process has terminated,
+ and the stdout/stderr consumer threads have finished.
+ """
+ if self.popen.returncode is None: return None
+ if self.stdout_thread.is_alive(): return None
+ if self.stderr_thread.is_alive(): return None
+ return self.popen.returncode
+
+
+ @staticmethod
+ def check_output(cmd, *args, **kwargs):
+ """Behaves like subprocess.check_output. Runs the given command, then
+ waits until it finishes, and return its standard output. An error
+ is raised if the process returns a non-zero exit code, unless a keyword
+ argument `check=False` is specified.
+
+ :arg cmd: The command to run, as a string
+ """
+
+ check = kwargs.pop('check', True)
+ proc = Process(cmd, *args, **kwargs)
+
+ proc.wait()
+
+ if check and (proc.returncode != 0):
+ raise RuntimeError('This command returned an error: ' + cmd)
+
+ stdout = ''
+ while True:
+ try:
+ stdout += proc.stdoutq.get_nowait()
+ except queue.Empty:
+ break
+
+ return stdout
+
+
+ @staticmethod
+ def check_call(cmd, *args, **kwargs):
+ """Behaves like subprocess.check_call. Runs the given command, then
+ waits until it finishes. An error is raised if the process returns a
+ non-zero exit code, unless a keyword argument `check=False` is
+ specified.
+
+ :arg cmd: The command to run, as a string
+ """
+
+ check = kwargs.pop('check', True)
+ proc = Process(cmd, *args, **kwargs)
+
+ proc.wait()
+
+ if check and proc.returncode != 0:
+ raise RuntimeError('This command returned an error: ' + cmd)
+
+ return proc.returncode
+
+
+ @staticmethod
+ def monitor_progress(cmd, total=None, *args, **kwargs):
+ """Runs the given command(s), and shows a progress bar under the
+ assumption that cmd will produce "total" number of lines of output.
+
+ :arg cmd: The commmand to run as a string, or a sequence of
+ multiple commands.
+
+ :arg total: Total number of lines of standard output to expect.
+
+ :arg timeout: Refresh rate in seconds. Must be passed as a keyword
+ argument.
+
+ :arg progfunc: Function which returns a number indicating how far
+ the process has progressed. If provided, this
+ function is called, instead of standard output
+ lines being monitored. The function is passed a
+ reference to the Process object. Must be passed as a
+ keyword argument.
+
+ :arg progfile: File to write progress updates to.
+
+ :arg proglabel: Label to use when writing progress updates to progfile.
+ """
+
+ timeout = kwargs.pop('timeout', 0.5)
+ progfunc = kwargs.pop('progfunc', None)
+ proglabel = kwargs.pop('proglabel', None)
+ progfile = kwargs.pop('progfile', None)
+
+ if total is None: label = None
+ else: label = '%'
+
+ if progfunc is None:
+ nlines = [0]
+ def progfunc(proc):
+ try:
+ _ = proc.stdoutq.get_nowait()
+ nlines[0] = nlines[0] + 1
+ except queue.Empty:
+ pass
+ return nlines[0]
+
+ if isstr(cmd): cmds = [cmd]
+ else: cmds = cmd
+
+ with Progress(label=label,
+ fmt='{:.0f}',
+ transform=Progress.percent,
+ proglabel=proglabel,
+ progfile=progfile) as prog:
+
+ progcount = 0 if total else None
+
+ for cmd in cmds:
+
+ proc = Process(cmd, *args, **kwargs)
+ prog.update(progcount, total)
+
+ while proc.returncode is None:
+ time.sleep(timeout)
+ progcount = progfunc(proc) if total else None
+ prog.update(progcount, total)
+ proc.popen.poll()
+
+ # force progress bar to 100% when finished
+ if proc.returncode == 0:
+ prog.update(total, total)
+ else:
+ raise RuntimeError('This command returned '
+ 'an error: ' + cmd)
+
+
+ @staticmethod
+ def forward_stream(stream,
+ queue,
+ cmd,
+ streamname,
+ log_output,
+ print_output):
+ """Reads lines from stream and pushes them onto queue until popen
+ is finished. Logs every line.
+
+ :arg stream: stream to forward
+ :arg queue: queue.Queue to push lines onto
+ :arg cmd: string - the command that is running
+ :arg streamname: string - 'stdout' or 'stderr'
+ :arg log_output: If True, log all stdout/stderr.
+ :arg print_output: If True, print all stdout/stderr.
+ """
+
+ while True:
+ line = stream.readline().decode('utf-8')
+ if line == '':
+ break
+ queue.put(line)
+ if log_output:
+ log.debug(' [%s]: %s', streamname, line.rstrip())
+ if print_output:
+ print(' [{}]: {}'.format(streamname, line.rstrip()))
+
+
+ @staticmethod
+ def popen(cmd, admin=False, password=None, append_env=None, **kwargs):
+ """Runs the given command via subprocess.Popen, as administrator if
+ requested.
+
+ :arg cmd: The command to run, as a string
+
+ :arg admin: Whether to run with administrative privileges
+
+ :arg pssword: Administrator password. Only required if admin is
+ True.
+
+ :arg append_env: Dictionary of additional environment to be set when
+ the command is run.
+
+ :arg kwargs: Passed to subprocess.Popen. stdin, stdout, and stderr
+ will be silently clobbered
+
+ :returns: The subprocess.Popen object.
+ """
+
+ admin = admin and os.getuid() != 0
+
+ cmd = shlex.split(cmd)
+ kwargs['stdin'] = sp.PIPE
+ kwargs['stdout'] = sp.PIPE
+ kwargs['stderr'] = sp.PIPE
+
+ if admin:
+ proc = Process.sudo_popen(cmd, password, append_env, **kwargs)
+ else:
+ # if append_env has been specified,
+ # add it to the normal env option.
+ if append_env is not None:
+ env = kwargs.get('env', os.environ.copy())
+ env.update(append_env)
+ kwargs['env'] = env
+
+ proc = sp.Popen(cmd, **kwargs)
+
+ return proc
+
+
+ @staticmethod
+ def sudo_popen(cmd, password, append_env=None, **kwargs):
+ """Runs "sudo cmd" using subprocess.Popen. Used by Process.popen.
+ Assumes that kwargs contains stdin=sp.PIPE
+ """
+
+ # sudo will not necessarily propagate environment
+ # variables, and there is no guarantee that the
+ # sudo -E option will work. So here we create a
+ # wrapper shell script with "export VAR=VALUE"
+ # statements for all environment variables that
+ # are set.
+ if append_env is None:
+ append_env = {}
+
+ # Make the wrapper script delete itself
+ # after the command has been executed.
+ with tempfilename(0o755, delete=False) as wrapper:
+ with open(wrapper, 'wt') as f:
+ f.write('#!/usr/bin/env sh\n')
+ f.write('set -e\n')
+ f.write('thisfile=$0\n')
+ f.write('thisdir=$(cd $(dirname $0) && pwd)\n')
+ for k, v in append_env.items():
+ f.write('export {}="{}"\n'.format(k, v))
+ # shlex.join not available in py27
+ f.write(' '.join(cmd) + '\n')
+ f.write('cd ${thisdir} && rm ${thisfile}\n')
+
+ cmd = ['sudo', '-S', '-k', wrapper]
+ proc = sp.Popen(cmd, **kwargs)
+ proc.stdin.write('{}\n'.format(password).encode())
+ proc.stdin.flush()
+ return proc
+
+
+@ft.total_ordering
+class Version(object):
+ """Class to represent and compare version strings. Accepted version
+ strings are of the form W.X.Y.Z, where W, X, Y, and Z are all integers.
+ """
+ def __init__(self, verstr):
+ # Version identifiers for official FSL
+ # releases will have up to four
+ # components (X.Y.Z.W), but we accept
+ # any number of (integer) components,
+ # as internal releases may have more.
+ components = []
+
+ # ignore a leading "v", e.g. v1.2.3
+ verstr = verstr.lower()
+ if verstr.startswith('v'):
+ verstr = verstr[1:]
+
+ for comp in verstr.split('.'):
+ try: components.append(int(comp))
+ except Exception: break
+
+ self.components = components
+ self.verstr = verstr
+
+ def __str__(self):
+ return self.verstr
+
+ def __eq__(self, other):
+ for sn, on in zip(self.components, other.components):
+ if sn != on:
+ return False
+ return len(self.components) == len(other.components)
+
+ def __lt__(self, other):
+ for p1, p2 in zip(self.components, other.components):
+ if p1 < p2: return True
+ if p1 > p2: return False
+ return len(self.components) < len(other.components)
+
+
+class Context(object):
+ """Bag of information and settings created in the main function, and passed
+ around this script.
+
+ Several settings are lazily evaluated on first access, but once evaluated,
+ their values are immutable.
+ """
+
+ def __init__(self, args, destdir=None, action='install FSL'):
+ """Create the context with the argparse.Namespace object containing
+ parsed command-line arguments.
+
+ :arg args: argparse.Namespace containing command-line arguments
+ :arg destdir: Installation directory. If not provided, read from
+ args.dest, or read from the user,
+ :arg action: Passed to get_admin_password as a prompt.
+ """
+
+ if destdir is not None:
+ destdir = op.abspath(destdir)
+
+ self.args = args
+ self.shell = op.basename(os.environ.get('SHELL', 'sh')).lower()
+
+ # These attributes are updated on-demand via
+ # the property accessors defined below, or are
+ # all updated via the finalise_settings method.
+ self.__platform = None
+ self.__manifest = None
+ self.__devmanifest = None
+ self.__candidate_builds = None
+ self.__build = None
+ self.__destdir = destdir
+ self.__need_admin = None
+ self.__admin_password = None
+ self.__action = action
+
+ # If the destination directory already exists,
+ # and the user chooses to overwrite it, it is
+ # moved so that, if the installation fails, it
+ # can be restored. The new path is stored
+ # here - refer to overwrite_destdir.
+ self.old_destdir = None
+
+ # The download_fsl_environment function stores
+ # the path to the FSL conda environment file
+ # and list of conda channels
+ self.environment_file = None
+ self.environment_channels = None
+
+ # The config_logging function stores the path
+ # to the fslinstaller log file here.
+ self.logfile = None
+
+
+ def finalise_settings(self):
+ """Finalise values for all information and settings in the Context.
+ """
+ self.manifest
+ self.candidate_builds
+ self.platform
+ self.build
+ self.destdir
+ self.need_admin
+ self.admin_password
+
+
+ @property
+ def license_url(self):
+ """Return the FSL license URL from the manifest, or None if it is not
+ present.
+ """
+ return self.manifest['installer'].get('license_url')
+
+
+ @property
+ def registration_url(self):
+ """Return the FSL registration URL from the manifest, or None if it is
+ not present.
+ """
+ return self.manifest['installer'].get('registration_url')
+
+
+ @property
+ def platform(self):
+ """The platform we are running on, e.g. "linux-64", "macos-64",
+ "macos-M1". This identifier is used to determine which FSL build to
+ install.
+
+ Note that this function may report a different platform identifier than
+ the actual platform - specifically, if running on a M1 mac, and there
+ is no M1 FSL build for the requested FSL version, this function will
+ report "macos-64". This is because some older FSL releases do not have
+ M1 builds available.
+ """
+ if self.__platform is None:
+ plat = identify_platform()
+
+ # if M1, check that we have a suitable
+ # FSL build, falling back to x86 if not.
+ if plat == 'macos-M1':
+ candidates = self.candidate_builds
+ if not any(c['platform'] == 'macos-M1' for c in candidates):
+ plat = 'macos-64'
+
+ self.__platform = plat
+
+ return self.__platform
+
+
+ @property
+ def candidate_builds(self):
+ """Query the manifest and return a list of available builds for the
+ requested FSL release, for all platforms.
+ """
+ if self.__candidate_builds is not None:
+ return self.__candidate_builds
+
+ # defaults to "latest" if
+ # not specified by the user
+ fslversion = self.args.fslversion
+ if fslversion is None:
+ fslversion = 'latest'
+
+ if fslversion not in self.manifest['versions']:
+ available = ', '.join(self.manifest['versions'].keys())
+ raise Exception(
+ 'FSL version "{}" is not available - available '
+ 'versions: {}'.format(fslversion, available))
+
+ if fslversion == 'latest':
+ fslversion = self.manifest['versions']['latest']
+
+ self.__candidate_builds = list(self.manifest['versions'][fslversion])
+
+ return self.__candidate_builds
+
+
+ @property
+ def build(self):
+ """Returns a suitable FSL build (a dictionary entry from the FSL
+ installer manifest) for the target platform.
+
+ The returned dictionary has the following elements:
+ - 'version' FSL version.
+ - 'platform': Platform identifier (e.g. 'linux-64')
+ - 'environment': Environment file to download
+ - 'sha256': Checksum of environment file
+ - 'output': Number of lines of expected output, for reporting
+ progress
+ """
+
+ if self.__build is not None:
+ return self.__build
+
+ # Find refs to a suitable build for this
+ # platform. We assume that there is only
+ # one default build for each platform.
+ # in the list of builds for a given FSL
+ # version.
+ candidates = self.candidate_builds
+ build = None
+
+ for candidate in candidates:
+ if candidate['platform'] == self.platform:
+ build = candidate
+ break
+ else:
+ raise Exception(
+ 'Cannot find a version of FSL matching '
+ 'platform {}'.format(self.platform))
+
+ printmsg('FSL {} selected for installation'.format(build['version']))
+
+ self.__build = build
+ return build
+
+
+ @property
+ def destdir(self):
+ """Installation directory. If not specified at the command line, the
+ user is prompted to enter a directory.
+ """
+
+ if self.__destdir is not None:
+ return self.__destdir
+
+ fsldir = os.environ.get('FSLDIR', None)
+
+ if fsldir is not None: defdestdir = fsldir
+ elif os.getuid() != 0: defdestdir = DEFAULT_INSTALLATION_DIRECTORY
+ else: defdestdir = DEFAULT_ROOT_INSTALLATION_DIRECTORY
+
+ # The loop below validates the destination directory
+ # both when specified at commmand line or
+ # interactively. In either case, if invalid, the
+ # user is re-prompted to enter a new destination.
+ destdir = None
+ if self.args.dest is not None: response = self.args.dest
+ else: response = None
+
+ while destdir is None:
+
+ if response is None:
+ printmsg('\nWhere do you want to install FSL?',
+ IMPORTANT, EMPHASIS)
+ printmsg('Press enter to install to the default location '
+ '[{}]\n'.format(defdestdir), INFO)
+ response = prompt('FSL installation directory [{}]:'.format(
+ defdestdir), QUESTION, EMPHASIS)
+ response = response.rstrip(op.sep)
+
+ if response == '':
+ response = defdestdir
+
+ response = op.expanduser(op.expandvars(response))
+ response = op.abspath(response)
+ parentdir = op.dirname(response)
+ if op.exists(parentdir):
+ destdir = response
+ else:
+ printmsg('Destination directory {} does not '
+ 'exist!'.format(parentdir), ERROR)
+ response = None
+
+ self.__destdir = destdir
+ return self.__destdir
+
+
+ @property
+ def conda(self):
+ """Return a path to the ``conda`` or ``mamba`` executable. """
+ bindir = op.join(self.basedir, 'bin')
+ condabin = op.join(bindir, 'conda')
+ mambabin = op.join(bindir, 'mamba')
+
+ # If mamba is present, prefer it over conda, unless
+ # the user requestd otherwise via the --conda flag
+ if not self.args.conda: candidates = [mambabin, condabin]
+ else: candidates = [condabin, mambabin]
+
+ for c in candidates:
+ if op.exists(c):
+ return c
+
+ raise RuntimeError('Cannot find conda/mamba '
+ 'executable in {}'.format(bindir))
+
+
+ @property
+ def basedir(self):
+ """Return the path to the base conda installation. For normal
+ installations this is equivalent to destdir / $FSLDIR, but may be
+ different if the fslinstaller was instructed to use an existing
+ [mini]conda installation.
+ """
+
+ # Either the user gave a path to an existing
+ # miniconda installation, or $FSLDIR is the
+ # base miniconda installation
+ if (self.args.miniconda is not None) and op.isdir(self.args.miniconda):
+ return self.args.miniconda
+ else:
+ return self.destdir
+
+
+ @property
+ def use_existing_base(self):
+ """Return True if we have been instructed to use an existing
+ [mini]conda installation, as opposed to downloading/installing one.
+ """
+ return ((self.args.miniconda is not None) and
+ op.isdir(self.args.miniconda))
+
+
+ @property
+ def need_admin(self):
+ """Returns True if administrator privileges will be needed to install
+ FSL.
+ """
+ if self.__need_admin is not None:
+ return self.__need_admin
+ parentdir = op.dirname(self.destdir)
+ self.__need_admin = check_need_admin(parentdir)
+ return self.__need_admin
+
+
+ @property
+ def admin_password(self):
+ """Returns the user's administrator password, prompting them if needed.
+ """
+ # need_admin may be None or False,
+ # so don't rely on truthiness.
+ if not self.need_admin:
+ return None
+ if self.__admin_password is None:
+ self.__admin_password = get_admin_password(self.__action)
+ return self.__admin_password
+
+
+ @property
+ def manifest(self):
+ """Returns the FSL installer manifest as a dictionary. """
+
+ if self.__manifest is None:
+ if self.devmanifest is not None:
+ self.args.manifest = self.devmanifest
+
+ self.__manifest = download_manifest(
+ self.args.manifest,
+ self.args.workdir,
+ ssl_verify=(not self.args.skip_ssl_verify))
+ return self.__manifest
+
+
+ @property
+ def devmanifest(self):
+ """Returns a URL to a development manifest to use for installation.
+ This will only return a value if the --devrelease or --devlatest
+ options are active.
+
+ If this is the case, the FSL_DEV_RELEASES file is downloaded - this
+ file contains a list of available development manifest URLS. The
+ user is then prompted to choose which development manifest to use
+ for the installation, unless --devlatest is active, in which case
+ the newest manifest is selected.
+ """
+ if not self.args.devrelease:
+ return None
+ if self.__devmanifest == 'na':
+ return None
+ elif self.__devmanifest is not None:
+ return self.__devmanifest
+
+ devreleases = download_dev_releases(
+ FSL_DEV_RELEASES,
+ self.args.workdir,
+ ssl_verify=(not self.args.skip_ssl_verify))
+
+ if len(devreleases) == 0:
+ self.__devmanifest = 'na'
+ return None
+
+ self.__devmanifest = prompt_dev_release(devreleases,
+ self.args.devlatest)
+
+ return self.__devmanifest
+
+
+ def run(self, process_func, *args, **kwargs):
+ """Run a command via a static Process method. Handles sudo/
+ administrator authentication, and ensures that the shell
+ environment in which the command is executed is sanitised.
+
+ Can be used with Process.check_call, Process.check_output, and
+ Process.monitor_progress. For example:
+
+ ctx = Context(...)
+ ctx.run(Process.check_call, 'my_command')
+ ctx.run(Process.monitor_progress, 'my_command', total=100)
+ """
+
+ env = kwargs.pop('env', {})
+ append_env = kwargs.pop('append_env', {})
+ process_func = ft.partial(process_func, *args, **kwargs)
+
+ # Clear any environment variables that refer to
+ # existing FSL or conda installations, and ensure
+ # that some specific FSL environment variables
+ # are set while the command is running. See
+ # clean_environ and install_environ for more
+ # details, and see Process.sudo_popen regarding
+ # append_env.
+ env.update(clean_environ())
+ append_env.update(install_environ(self.destdir,
+ self.args.username,
+ self.args.password))
+ return process_func(admin=self.need_admin,
+ password=self.admin_password,
+ env=env,
+ append_env=append_env)
+
+
+def agree_to_license(ctx):
+ """Prompts the user to agree to the terms of the FSL license."""
+
+ msg = ['Installing FSL implies agreement with the terms of the FSL '
+ 'license - if you do not agree with these terms, you can '
+ 'cancel the installation by pressing CTRL+C.', IMPORTANT]
+
+ if ctx.license_url is not None:
+ msg = msg + [' You can view the license at ', IMPORTANT,
+ ctx.license_url, IMPORTANT, UNDERLINE]
+ printmsg(*msg)
+ printmsg('')
+
+
+def check_rosetta_status(ctx):
+ """Called from the main routine, before installation is attempted. If
+ running on a M1 macos machine, and a x86 version of FSL has been selected
+ for installation, checks whether rosetta emulation is enabled. If so,
+ does nothing further. Otherwise, prints a message and exits.
+ """
+
+ if not all((identify_platform() == 'macos-M1',
+ ctx.platform == 'macos-64')):
+ return
+
+ # Using the strategy discussed at
+ # https://forum.latenightsw.com/t/possible-for-a-script-\
+ # to-test-whether-rosetta-2-is-installed/3207/6
+ #
+ # The pkgutil command should return 0 if
+ # rosetta is installed, non-0 otherwise.
+ try:
+ Process.check_output('pkgutil --files com.apple.pkg.RosettaUpdateAuto')
+ except RuntimeError:
+ printmsg('Rosetta emulation does not appear to be enabled!\n', ERROR)
+ printmsg('Enable rosetta emulation, and then run this installer '
+ 'again. You can enable rosetta emulation by running this '
+ 'command:\n', INFO)
+ printmsg(' /usr/sbin/softwareupdate --install-rosetta '
+ '--agree-to-license\n', IMPORTANT)
+ printmsg('Aborting installation', ERROR)
+ sys.exit(1)
+ # pkgutil command not found - should
+ # never happen, but print a warning
+ # just in case
+ except Exception as e:
+ printmsg('An error occurred calling the pkgutil command - this '
+ 'may not be a problem, so I\'ll attempt to proceed '
+ 'with the installation. ({}'.format(e), WARNING)
+
+
+def list_available_versions(manifest):
+ """Lists available FSL versions. """
+ printmsg('Available FSL versions:', EMPHASIS)
+ for version in manifest['versions']:
+ if version == 'latest':
+ continue
+ printmsg(version, IMPORTANT, EMPHASIS)
+ for build in manifest['versions'][version]:
+ printmsg(' {}'.format(build['platform']), EMPHASIS, end=' ')
+ printmsg(build['environment'], INFO)
+
+
+def prompt_dev_release(devreleases, latest):
+ """Prompts the user to select a development release.
+
+ :arg devreleases: List of development releases, as returned by
+ download_dev_releases.
+ :arg latest: If True, the latest develeopment release is returned.
+ """
+
+ if len(devreleases) == 0:
+ return None
+
+ # automatically choose latest dev manifest?
+ if latest:
+ return devreleases[0][0]
+
+ # show the user a list, ask them which one they want
+ printmsg('Available development releases:', EMPHASIS)
+ for i, (url, tag, commit, branch) in enumerate(devreleases):
+ # dev release
+ if commit is not None:
+ printmsg(' [{}]: {} [{} commit {}]'.format(
+ i + 1, tag, branch, commit), IMPORTANT)
+ # public release
+ else:
+ printmsg(' [{}]: {}'.format(i + 1, tag), IMPORTANT)
+
+ while True:
+ selection = prompt('Which release would you like to '
+ 'install? [1]:', PROMPT)
+ if selection == '':
+ selection = '1'
+ try:
+ selection = int(selection) - 1
+ except Exception:
+ continue
+ if selection >= 0 and selection < len(devreleases):
+ break
+ return devreleases[selection][0]
+
+
+def download_fsl_environment(ctx):
+ """Downloads the environment specification file for the selected FSL
+ version.
+
+ Internal/development FSL versions may source packages from the internal
+ FSL conda channel, which requires a username+password to authenticate.
+
+ These are referred to in the environment file as ${FSLCONDA_USERNAME}
+ and ${FSLCONDA_PASSWORD}.
+
+ If the user has not provided a username+password on the command-line, they
+ are prompted for them.
+
+ The downloaded environment file may be modified - if the (hidden)
+ --exclude_package option has been used.
+ """
+
+ build = ctx.build
+ url = build['environment']
+ checksum = build.get('sha256', None)
+
+ printmsg('Downloading FSL environment specification '
+ 'from {}...'.format(url))
+ fname = url.split('/')[-1]
+ download_file(url, fname, ssl_verify=(not ctx.args.skip_ssl_verify))
+ ctx.environment_file = op.abspath(fname)
+ if (checksum is not None) and (not ctx.args.no_checksum):
+ sha256(fname, checksum)
+
+ # Environment files for internal/dev FSL versions
+ # will list the internal FSL conda channel with
+ # ${FSLCONDA_USERNAME} and ${FSLCONDA_PASSWORD}
+ # as placeholders for the username/password.
+ with open(fname, 'rt') as f:
+ need_auth = '${FSLCONDA_USERNAME}' in f.read()
+
+ # We need a username/password to access the internal
+ # FSL conda channel. Prompt the user if they haven't
+ # provided credentials.
+ if need_auth and (ctx.args.username is None):
+ printmsg('A username and password are required to install '
+ 'this version of FSL.', WARNING, EMPHASIS)
+ ctx.args.username = prompt('Username:').strip()
+ ctx.args.password = getpass.getpass('Password: ').strip()
+
+ # Conda expands environment variables within a
+ # .condarc file, but *not* within an environment.yml
+ # file. So to authenticate to our internal channel
+ # without storing credentials anywhere in plain text,
+ # we *move* the channel list from the environment.yml
+ # file into $FSLDIR/.condarc.
+ #
+ # Here we extract the channels from the environment
+ # file, and save them to ctx.environment_channels.
+ # The install_miniconda function will then add the
+ # channels to $FSLDIR/.condarc.
+ #
+ # We also remove any packages that the user has
+ # requested to exclude from the installation.
+ copy = '.' + op.basename(ctx.environment_file)
+ channels = []
+
+ shutil.move(ctx.environment_file, copy)
+ with open(copy, 'rt') as inf, \
+ open(ctx.environment_file, 'wt') as outf:
+
+ in_channels_section = False
+
+ for line in inf:
+
+ # start of channels list
+ if line.strip() == 'channels:':
+ in_channels_section = True
+ continue
+
+ if in_channels_section:
+ # end of channels list
+ if not line.strip().startswith('-'):
+ in_channels_section = False
+ else:
+ channels.append(line.split()[-1])
+ continue
+
+ # Include/exclude packages upon user request
+ pkgname = line.strip(' -').split()[0]
+ exclude = match_any(pkgname, ctx.args.exclude_package)
+ if exclude:
+ log.debug('Excluding package %s (matched '
+ '--exclude_package %s)', line, exclude)
+ else:
+ outf.write(line)
+
+ ctx.environment_channels = channels
+
+
+def download_miniconda(ctx):
+ """Downloads the miniconda/miniforge installer and saves it as
+ "miniconda.sh".
+
+ This function assumes that it is run within a temporary/scratch directory.
+ """
+
+ # The user has specified a path to an
+ # existing miniconda installation - we
+ # use that rather than downloading/
+ # installing a separate one.
+ if ctx.use_existing_base:
+ return
+
+ # user specified a URL/path to a
+ # miniconda installer
+ elif ctx.args.miniconda is not None:
+ url = ctx.args.miniconda
+ checksum = None
+
+ # Use miniconda installer specified in
+ # FSL release manifest
+ else:
+ metadata = ctx.manifest['miniconda'][ctx.platform]
+ url = metadata['url']
+ checksum = metadata['sha256']
+
+ # Download
+ printmsg('Downloading miniconda from {}...'.format(url))
+ with Progress('MB', transform=Progress.bytes_to_mb,
+ proglabel='download_miniconda',
+ progfile=ctx.args.progress_file) as prog:
+ download_file(url, 'miniconda.sh', prog.update,
+ ssl_verify=(not ctx.args.skip_ssl_verify))
+ if (not ctx.args.no_checksum) and (checksum is not None):
+ sha256('miniconda.sh', checksum)
+
+
+def install_miniconda(ctx):
+ """Downloads the miniconda/miniforge installer, and installs it to the
+ destination directory.
+
+ This function assumes that it is run within a temporary/scratch directory.
+ """
+
+ # We have been instructed to use an
+ # existing miniconda installation
+ if ctx.use_existing_base:
+ return
+
+ metadata = ctx.manifest['miniconda'][ctx.platform]
+ output = metadata.get('output', '').strip()
+
+ if output == '': output = None
+ else: output = int(output)
+
+ # The download_miniconda function saved
+ # the installer to <pwd>/miniconda.sh
+ printmsg('Installing miniconda at {}...'.format(ctx.basedir))
+ cmd = 'bash miniconda.sh -b -p {}'.format(ctx.basedir)
+ ctx.run(Process.monitor_progress, cmd, total=output,
+ proglabel='install_miniconda',
+ progfile=ctx.args.progress_file)
+
+ # Avoid WSL filesystem issue
+ # https://github.com/conda/conda/issues/9948
+ cmd = 'find {} -type f -exec touch {{}} +'.format(ctx.basedir)
+ ctx.run(Process.check_call, cmd)
+
+
+def generate_condarc(fsldir,
+ channels,
+ skip_ssl_verify=False,
+ pkgsdir=None):
+ """Called by install_miniconda. Generates content for a .condarc file to
+ be saved in $FSLDIR/.condarc. This file contains some default values, and
+ also enforces some settings so that they cannot be overridden by the
+ user. For example. the list of conda channels is configured so that it
+ cannot be overridden by a user's ~/.condarc file.
+
+ See the following web pages for more details:
+ - https://docs.conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html
+ - https://www.anaconda.com/blog/conda-configuration-engine-power-users
+ - https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-channels.html
+ """
+
+ # Create .condarc config file
+ condarc = tw.dedent("""
+ # FSL conda configuration file, auto-generated by the fslinstaller script.
+ #
+ # WARNING: This file may be automatically re-generated
+ # without warning, so it is recommended that any custom
+ # conda settings are stored elsewhere. Refer to the conda
+ # documentation for more guidance:
+ #
+ # https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html
+ # https://www.anaconda.com/blog/conda-configuration-engine-power-users
+ # https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-channels.html
+
+ # Try and make package downloads more robust
+ remote_read_timeout_secs: 240
+ remote_connect_timeout_secs: 20
+ remote_max_retries: 10
+ remote_backoff_factor: 5
+ safety_checks: warn
+
+ # Disable caching of remote channel repodata.
+ # This is a hack which is combined with the
+ # WSL1 filesystem hack in the fslinstaller
+ # script - because we have modified file
+ # timestamps, conda will assume that its
+ # channel repodata cache is up to date, and
+ # will not bother refreshing it in the
+ # commands that we run in the install_fsl
+ # function. When we remove the WSL1 hack from
+ # the fslinstaller, we can remove this config
+ # setting.
+ # https://github.com/conda/conda/issues/9948
+ local_repodata_ttl: 0
+
+ # Channel priority is important. In older versions
+ # of FSL we placed the FSL conda channel at the
+ # bottom (lowest priority) for legacy reasons (to
+ # ensure that conda-forge versions of e.g. VTK were
+ # preferred over legacy FSL conda versions).
+ #
+ # Use final/top/bottom marks to prevent the channel
+ # priority order being modified by user ~/.condarc
+ # configuration files.
+ channel_priority: strict #!final
+ """)
+
+ # Fix the conda package cache
+ # if a pkgsdir was provided
+ if pkgsdir is not None:
+ condarc += tw.dedent("""
+ # Fix the package cache at $FSLDIR/pkgs/
+ pkgs_dirs: #!final
+ - {} #!top #!bottom
+ """.format(pkgsdir))
+
+ if skip_ssl_verify:
+ printmsg('Configuring conda to skip SSL verification '
+ '- this is not recommended!', WARNING)
+ condarc += tw.dedent("""
+ # Disable SSL verification when accessing
+ # conda channels over https:// (the
+ # fslinstaller script was called with
+ # --skip_ssl_verify). NOT RECOMMENDED.
+ ssl_verify: false
+ """)
+
+ channels = list(channels)
+ if len(channels) > 0:
+ channels[0] += ' #!top'
+ channels[-1] += ' #!bottom'
+ condarc += '\nchannels: #!final\n'
+ for channel in channels:
+ condarc += ' - {}\n'.format(channel)
+
+ return condarc
+
+
+def get_install_fsl_progress_reporting_method(ctx):
+ """Figure out which reporting mechansim to use for reporting progress
+ whilst FSL is being installed. The mechanism that is used has changed
+ a few times.
+
+ Returns a tuple containing values to pass to the Progress.monitor_progress
+ function, either of which may be None if progress cannot be reported:
+
+ - an integer value to pass as the total
+ - a function to pass as the progfunc.
+ """
+
+ # We calculate installation progress in
+ # one of a few ways, as we have changed
+ # the mechanism a few times. The
+ # 'output/install' field in the manifest
+ # gives us information about how to
+ # report installation progress.
+ progparams = ctx.build.get('output', {}).get('install', None)
+
+ # The first method (version 1) involves
+ # progress reporting by monitoring number of
+ # lines of standard output produced by
+ # "conda env update". This is set to None,
+ # as it is the default behaviour of the
+ # Progress.monitor_progress function.
+ progress_v1 = None
+
+ # The remaining methods involve counting
+ # files and sizes in $FSLDIR/pkgs/
+
+ # The second method involves progress
+ # reporting by monitoring the number of
+ # package files created in $FSLDIR/pkgs/
+ # This coarsely reflects download
+ # progress - when conda downloads a
+ # package, it is saved into this directory.
+ #
+ # The third method involves progress
+ # reporting by monitoring the number of
+ # files created in $FSLDIR/pkgs/,
+ # $FSLDIR/bin/ and $FSLDIR/lib/. Tracking
+ # these three directories will cause the
+ # progress to reflect both download and
+ # installation
+ #
+ # The fourth method monitors download
+ # progress in a more fine-grained manner,
+ # by calculating the size of all .conda
+ # and .tar.bz2 files in $FSLDIR/pkgs/.
+ # This is combined with the number of
+ # files saved to $FSLDIR/bin/ and
+ # $FSLDIR/lib/
+ pkgdir = op.join(ctx.basedir, 'pkgs')
+ pkgdir = op.join(ctx.basedir, 'pkgs')
+ bindir = op.join(ctx.destdir, 'bin')
+ libdir = op.join(ctx.destdir, 'lib')
+
+ def matchany(name, *filters):
+ return any([fnmatch.fnmatch(name, f) for f in filters])
+
+ def contents(dirname, *filters):
+ if not op.exists(dirname):
+ return []
+ contents = os.listdir(dirname)
+ contents = [f for f in contents if matchany(f, *filters)]
+ return [op.join(dirname, f) for f in contents]
+
+ start_pkgs = contents(pkgdir, '*.conda', '*.bz2')
+ start_sizes = sum([op.getsize(p) for p in start_pkgs])
+ start_pkgs = len(start_pkgs)
+ start_bins = len(contents(bindir))
+ start_libs = len(contents(libdir))
+
+ def progress_v234(v, _):
+
+ pkgs = contents(pkgdir, '*.conda', '*.bz2')
+ bins = contents(bindir)
+ libs = contents(libdir)
+ sizes = [op.getsize(p) for p in pkgs]
+ pkgs = len(pkgs) - start_pkgs
+ bins = len(bins) - start_bins
+ libs = len(libs) - start_libs
+ sizes = sum(sizes) - start_sizes
+
+ if v == 2: return pkgs
+ elif v == 3: return pkgs + bins + libs
+ elif v == 4: return sizes + bins + libs
+ else: return None
+
+ progresses = {}
+ progresses[1] = progress_v1
+ progresses[2] = ft.partial(progress_v234, 2)
+ progresses[3] = ft.partial(progress_v234, 3)
+ progresses[4] = ft.partial(progress_v234, 4)
+
+ progval = None
+ progfunc = None
+
+ # The output field may be either a
+ # string, in which case we assume
+ # version 2, or a dictionary containing
+ # the progress reporting version, and
+ # an integer value.
+ if isstr(progparams):
+ progval = int(progparams)
+ progfunc = progresses[2]
+
+ # output field is a dict - versioned
+ # progress reporting
+ elif isinstance(progparams, dict):
+ progver = int(progparams['version'])
+ progfunc = progresses[progver]
+ progval = progparams['value']
+
+ # unsupported progress reporting version
+ if progver > 4:
+ progval = None
+ progfunc = None
+
+ # version 4: progval is a dict
+ # containing various quantities
+ if progver == 4:
+ progval = sum(progval.values())
+
+ # older versions: progval is an integer
+ elif progver:
+ progval = int(progval)
+
+ return progval, progfunc
+
+
+def install_fsl(ctx):
+ """Install FSL into ctx.destdir (which is assumed to be a miniconda
+ installation.
+
+ This function assumes that it is run within a temporary/scratch directory.
+ """
+
+ progval, progfunc = get_install_fsl_progress_reporting_method(ctx)
+
+ # Generate .condarc which contains some default/
+ # fixed conda settings. We create $FSLDIR in
+ # advance, and copy .condarc into it. Conda seems
+ # to be ok with the directory already existing,
+ # although I am concerned that this behaviour may
+ # not be guaranteed.
+ #
+ # If this is a typical FSL installation (a self-
+ # contained base miniconda environment) we fix
+ # the package cache directory to isolate it from
+ # other conda installations that may be on the
+ # system.
+ if ctx.destdir == ctx.basedir: pkgsdir = op.join(ctx.destdir, 'pkgs')
+ else: pkgsdir = None
+
+ condarc_contents = generate_condarc(ctx.destdir,
+ ctx.environment_channels,
+ ctx.args.skip_ssl_verify,
+ pkgsdir)
+ with open('.condarc', 'wt') as f:
+ f.write(condarc_contents)
+
+ cmds = ['mkdir -p {}'.format(ctx.destdir),
+ 'cp -f .condarc {}'.format(ctx.destdir)]
+ for cmd in cmds:
+ ctx.run(Process.check_call, cmd)
+
+ # Are we updating an existing
+ # env or creating a new env?
+ if ctx.destdir == ctx.basedir: cmd = 'update'
+ else: cmd = 'create'
+
+ # We install FSL simply by running conda
+ # env [update|create] -f env.yml.
+ cmd = (ctx.conda + ' env ' + cmd +
+ ' -p ' + ctx.destdir +
+ ' -f ' + ctx.environment_file)
+
+ # Make conda/mamba super verbose if the
+ # hidden --debug option was specified.
+ if ctx.args.debug:
+ cmd += ' -v -v -v'
+
+ printmsg('Installing FSL into {}...'.format(ctx.destdir))
+ ctx.run(Process.monitor_progress, cmd,
+ timeout=2, total=progval, progfunc=progfunc,
+ proglabel='install_fsl', progfile=ctx.args.progress_file)
+
+
+@warn_on_error('WARNING: The installation succeeded, but an error occurred '
+ 'while creating $FSLDIR/etc/fslversion! There may be more '
+ 'information in the log file.', WARNING, EMPHASIS)
+def finalise_installation(ctx):
+ """Performs some finalisation tasks. Includes:
+ - Saving the installed version to $FSLDIR/etc/fslversion
+ - Saving this installer script and the environment file to
+ $FSLDIR/etc/
+ """
+
+ with open('fslversion', 'wt') as f:
+ f.write(ctx.build['version'])
+
+ etcdir = op.join(ctx.destdir, 'etc')
+ cmds = [
+ 'cp fslversion {}' .format(etcdir),
+ 'cp {} {}' .format(ctx.environment_file, etcdir)]
+
+ for cmd in cmds:
+ ctx.run(Process.check_call, cmd)
+
+
+@warn_on_error('WARNING: The installation succeeded, but an error occurred '
+ 'while removing intermediate package files! There may be more '
+ 'information in the log file.', WARNING, EMPHASIS)
+def post_install_cleanup(ctx, tmpdir):
+ """Cleans up the FSL directory after installation. """
+
+ cmds = [ctx.conda + ' clean -y --all']
+
+ if tmpdir is not None:
+ cmds.append('rm -rf ' + tmpdir)
+
+ for cmd in cmds:
+ ctx.run(Process.check_call, cmd)
+
+
+@warn_on_error('WARNING: ', WARNING, EMPHASIS, toscreen=False)
+def register_installation(ctx):
+ """Gathers and sends some basic system details to the FSL registration
+ website.
+ """
+
+ if ctx.args.skip_registration:
+ return
+
+ regurl = ctx.registration_url
+ if regurl is None:
+ return
+
+ system = platform.system().lower()
+ uname = Process.check_output('uname -a', check=False)
+ osinfo = ''
+
+ # macOS
+ if system == 'darwin':
+ osinfo = Process.check_output('sw_vers', check=False)
+
+ # Linux
+ else:
+ for releasefile in glob.glob(op.join('/etc/*-release')):
+ with open(releasefile, 'rt') as f:
+ osinfo = f.read().strip()
+ break
+
+ # WSL
+ if 'microsoft' in uname.lower():
+ osinfo += '\n\n' + Process.check_output('wsl.exe -v', check=False)
+
+ info = {
+ 'timestamp' : timestamp(),
+ 'architecture' : platform.machine(),
+ 'os' : system,
+ 'os_info' : osinfo,
+ 'uname' : uname,
+ 'python_version' : platform.python_version(),
+ 'python_info' : sys.version,
+ 'fsl_version' : ctx.build['version'],
+ 'fsl_platform' : ctx.build['platform'],
+ }
+
+ printmsg('Registering installation with {}'.format(regurl))
+
+ post_request(regurl, data=info)
+
+
+def patch_file(filename, searchline, numlines, content):
+ """Used by configure_shell and configure_matlab. Adds to, modifies,
+ or creates the specified file.
+
+ If a line matching searchline is found in the file, numlines (starting
+ from searchline) are replaced with content.
+
+ Otherwise, content is appended to the end of the file.
+ """
+
+ content = content.split('\n')
+
+ if op.isfile(filename):
+ with open(filename) as f:
+ lines = [l.strip() for l in f.readlines()]
+ else:
+ lines = []
+
+ # replace block
+ try:
+ idx = lines.index(searchline)
+ lines = lines[:idx] + content + lines[idx + numlines:]
+
+ # append to end
+ except ValueError:
+ lines = lines + [''] + content + ['']
+
+ with open(filename, 'wt') as f:
+ f.write('\n'.join(lines))
+
+
+def configure_shell(shell, homedir, fsldir):
+ """Configures the user's shell environment (e.g. ~/.bash_profile).
+
+ :arg shell: User's shell (taken from the $SHELL environment variable
+ :arg homedir: User's home directory, presumed to contain shell profile
+ file(s).
+ :arg fsldir: FSL installation directory
+ """
+
+ bourne_shells = ['sh', 'bash', 'zsh', 'dash']
+ csh_shells = ['csh', 'tcsh']
+
+ # we edit the first file that exists in
+ # the list of candidate profile files.
+ # They are attached as an attribute of
+ # this function just for testing purposes
+ # (see after function definition)
+ shell_profiles = configure_shell.shell_profiles
+
+ # DO NOT CHANGE the format of these configurations -
+ # they are kept exactly as-is for compatibility with
+ # legacy FSL installations, i.e. so we can modify
+ # profiles with an existing configuration from older
+ # FSL versions
+ bourne_cfg = tw.dedent("""
+ # FSL Setup
+ FSLDIR={fsldir}
+ PATH=${{FSLDIR}}/share/fsl/bin:${{PATH}}
+ export FSLDIR PATH
+ . ${{FSLDIR}}/etc/fslconf/fsl.sh
+ """).format(fsldir=fsldir).strip()
+
+ csh_cfg = tw.dedent("""
+ # FSL Setup
+ setenv FSLDIR {fsldir}
+ setenv PATH ${{FSLDIR}}/share/fsl/bin:${{PATH}}
+ source ${{FSLDIR}}/etc/fslconf/fsl.csh
+ """).format(fsldir=fsldir).strip()
+
+ if shell not in bourne_shells + csh_shells:
+ printmsg('Shell {} not recognised - skipping environment '
+ 'setup'.format(shell), WARNING, EMPHASIS)
+ return
+
+ if shell in bourne_shells: cfg = bourne_cfg
+ else: cfg = csh_cfg
+
+ # find the profile file to edit
+ profile = None
+ candidates = [op.join(homedir, p)
+ for p in shell_profiles[shell]]
+ for candidate in candidates:
+ if op.isfile(candidate):
+ profile = candidate
+ break
+
+ # if no candidate profile files
+ # exist, fall back to the first one
+ if profile is None:
+ profile = candidates[0]
+
+ printmsg('Adding FSL configuration to {}'.format(profile))
+
+ patch_file(profile, '# FSL Setup', len(cfg.split('\n')), cfg)
+configure_shell.shell_profiles = {'sh' : ['.profile'],
+ 'ksh' : ['.profile'],
+ 'bash' : ['.bash_profile', '.profile'],
+ 'dash' : ['.bash_profile', '.profile'],
+ 'zsh' : ['.zprofile'],
+ 'csh' : ['.cshrc'],
+ 'tcsh' : ['.tcshrc']}
+
+
+def configure_matlab(homedir, fsldir):
+ """Creates/appends FSL configuration code to ~/Documents/MATLAB/startup.m.
+ """
+
+ # DO NOT CHANGE the format of this configuration -
+ # see in-line comments in configure_shell.
+ cfg = tw.dedent("""
+ % FSL Setup
+ setenv( 'FSLDIR', '{fsldir}' );
+ setenv('FSLOUTPUTTYPE', 'NIFTI_GZ');
+ fsldir = getenv('FSLDIR');
+ fsldirmpath = sprintf('%s/etc/matlab',fsldir);
+ path(path, fsldirmpath);
+ clear fsldir fsldirmpath;
+ """).format(fsldir=fsldir).strip()
+
+ matlab_dir = op.expanduser(op.join(homedir, 'Documents', 'MATLAB'))
+ startup_m = op.join(matlab_dir, 'startup.m')
+
+ if not op.exists(matlab_dir):
+ os.makedirs(matlab_dir)
+
+ printmsg('Adding FSL configuration to {}'.format(startup_m))
+
+ patch_file(startup_m, '% FSL Setup', len(cfg.split('\n')), cfg)
+
+
+def self_update(manifest, workdir, checksum, **kwargs):
+ """Checks to see if a newer version of the installer (this script) is
+ available and if so, downloads it to a temporary file, and runs it in
+ place of this script.
+ """
+
+ thisver = Version(__version__)
+ latestver = Version(manifest['installer']['version'])
+
+ if latestver <= thisver:
+ log.debug('Installer is up to date (this version: %s, '
+ 'latest version: %s)', thisver, latestver)
+ return
+
+ log.debug('New version of installer is available '
+ '(%s) - self-updating', latestver)
+
+ tmpf = tempfile.NamedTemporaryFile(
+ prefix='new_fslinstaller', delete=False, dir=workdir)
+ tmpf.close()
+ tmpf = tmpf.name
+
+ download_file(manifest['installer']['url'], tmpf, **kwargs)
+
+ if checksum:
+ try:
+ sha256(tmpf, manifest['installer']['sha256'])
+ except Exception as e:
+ printmsg('New installer file does not match expected '
+ 'checksum! Skipping update.', WARNING)
+ return
+
+ # Don't try and update again - if for some
+ # reason the online manifest reports a newer
+ # version than what is available, we would
+ # otherwise enter into an infinite loop.
+ cmd = [sys.executable, tmpf] + sys.argv[1:] + ['--no_self_update']
+ log.debug('Running new installer: %s', cmd)
+ os.execv(sys.executable, cmd)
+
+
+def overwrite_destdir(ctx):
+ """Called by main to handle when the destination directory already exists.
+ Asks the user if they want to overwrite it. If they do, or if the
+ --overwrite option was specified, the directory is moved, and then deleted
+ after the installation succeeds.
+
+ This function assumes that it is run within a temporary/scratch directory.
+ """
+
+ # there is no existing installation,
+ # so there is nothing to worry about
+ if not op.exists(ctx.destdir):
+ return
+
+ # We have been instructed to install
+ # into an existing [mini]conda environment
+ if ctx.use_existing_base and (ctx.basedir == ctx.destdir):
+ return
+
+ if not ctx.args.overwrite:
+ printmsg('\nDestination directory [{}] already exists!\n'
+ .format(ctx.destdir), WARNING, EMPHASIS)
+ response = prompt('Do you want to overwrite it [y/N]?',
+ QUESTION, EMPHASIS)
+ if response.lower() not in ('y', 'yes'):
+ printmsg('Aborting installation', ERROR, EMPHASIS)
+ sys.exit(1)
+
+ # generate a unique name for the old
+ # destination directory (to avoid
+ # collisions if using the same workdir
+ # repeatedly)
+ i = 0
+ while True:
+ ctx.old_destdir = op.abspath('old_destdir{}'.format(i))
+ i += 1
+ if not op.exists(ctx.old_destdir):
+ break
+
+ printmsg('Deleting directory {}'.format(ctx.destdir), IMPORTANT)
+ ctx.run(Process.check_call,
+ 'mv {} {}'.format(ctx.destdir, ctx.old_destdir))
+
+
+def parse_args(argv=None, include=None, parser=None):
+ """Parse command-line arguments, returns an argparse.Namespace object.
+
+ :arg argv: Command-line arguments.
+
+ :arg include: List of arguments to parse. May be used by other scripts
+ which re-use some of the routines defined in this script.
+ The resulting argparse.Namespace object will contain values
+ of None for all arguments that are not included.
+
+ :arg parser: `argparse.ArgumentParser` to configure. If not provided,
+ one is created.
+ """
+
+ if parser is None:
+ parser = argparse.ArgumentParser()
+
+ uid = os.getuid()
+
+ if uid != 0: destdir = DEFAULT_INSTALLATION_DIRECTORY
+ else: destdir = DEFAULT_ROOT_INSTALLATION_DIRECTORY
+
+ # on macOS, when Python is run with sudo,
+ # op.expanduser('~') will return the
+ # calling user's home directory, and not
+ # the root home directory. This doesn't
+ # really matter, as homedir is only used
+ # for modifying the shell/matlab profile,
+ # and this is automatically disabled via
+ # the --no_env option when run as root. But
+ # in case the user wants the root shell
+ # profile modified (via the hidden
+ # --root_env option), we use getpwuid to
+ # determine the appropriate home directory.
+ homedir = pwd.getpwuid(uid).pw_dir
+
+ username = os.environ.get('FSLCONDA_USERNAME', None)
+ password = os.environ.get('FSLCONDA_PASSWORD', None)
+
+ options = {
+ # regular options
+ 'version' : ('-v', {'action' : 'version',
+ 'version' : __version__}),
+ 'dest' : ('-d', {'metavar' : 'DESTDIR'}),
+ 'overwrite' : ('-o', {'action' : 'store_true'}),
+ 'listversions' : ('-l', {'action' : 'store_true'}),
+ 'no_env' : ('-n', {'action' : 'store_true'}),
+ 'no_shell' : ('-s', {'action' : 'store_true'}),
+ 'no_matlab' : ('-m', {'action' : 'store_true'}),
+ 'skip_registration' : ('-r', {'action' : 'store_true'}),
+ 'fslversion' : ('-V', {'default' : 'latest'}),
+
+ # hidden options
+ 'debug' : (None, {'action' : 'store_true'}),
+ 'logfile' : (None, {}),
+ 'username' : (None, {'default' : username}),
+ 'password' : (None, {'default' : password}),
+ 'no_checksum' : (None, {'action' : 'store_true'}),
+ 'skip_ssl_verify' : (None, {'action' : 'store_true'}),
+ 'workdir' : (None, {}),
+ 'homedir' : (None, {'default' : homedir}),
+ 'devrelease' : (None, {'action' : 'store_true'}),
+ 'devlatest' : (None, {'action' : 'store_true'}),
+ 'manifest' : (None, {}),
+ 'miniconda' : (None, {}),
+ 'conda' : (None, {'action' : 'store_true'}),
+ 'no_self_update' : (None, {'action' : 'store_true'}),
+ 'exclude_package' : (None, {'action' : 'append'}),
+ 'root_env' : (None, {'action' : 'store_true'}),
+ 'progress_file' : (None, {}),
+ }
+
+ if include is None:
+ include = list(options.keys())
+
+ helps = {
+ 'version' : 'Print installer version number and exit.',
+ 'listversions' : 'List available FSL versions and exit.',
+ 'dest' : 'Install FSL into this folder (default: '
+ '{}).'.format(destdir),
+ 'overwrite' : 'Delete existing destination directory if it '
+ 'exists, without asking.',
+ 'no_env' : 'Do not modify your shell or MATLAB configuration '
+ '(implies --no_shell and --no_matlab). When '
+ 'running the installer script as the root user, '
+ 'the root shell profile is never modified.',
+ 'no_shell' : 'Do not modify your shell configuration.',
+ 'no_matlab' : 'Do not modify your MATLAB configuration.',
+ 'skip_registration' : 'Do not register this installation with the '
+ 'FSL development team.',
+ 'fslversion' : 'Install this specific version of FSL.',
+
+ # Enable verbose output when calling
+ # mamba/conda.
+ 'debug' : argparse.SUPPRESS,
+
+ # Direct the installer log to this file
+ # (default: file in $TMPDIR)
+ 'logfile' : argparse.SUPPRESS,
+
+ # Username / password for accessing
+ # internal FSL conda channel, if an
+ # internal/development release is being
+ # installed. If not set, will be read from
+ # FSLCONDA_USERNAME/FSLCONDA_PASSWORD
+ # environment variables.
+ 'username' : argparse.SUPPRESS,
+ 'password' : argparse.SUPPRESS,
+
+ # Do not automatically update the installer script,
+ 'no_self_update' : argparse.SUPPRESS,
+
+ # Install a development release. This
+ # option will cause the installer to
+ # download the devrelreases.txt file,
+ # which contains a list of available
+ # internal/development manifests. The
+ # user will be prompted to choose one,
+ # which will be propagated on to the
+ # --manifest option. If --devlatest
+ # is used, the most recent developmet
+ # release is automatically selected.
+ 'devrelease' : argparse.SUPPRESS,
+ 'devlatest' : argparse.SUPPRESS,
+
+ # Path/URL to alternative FSL release
+ # manifest.
+ 'manifest' : argparse.SUPPRESS,
+
+ # Install miniconda from this path/URL,
+ # instead of the one specified in the
+ # FSL release manifest.
+ #
+ # For example - to download/install a
+ # conda base environment and install FSL
+ # packages into the base environment,
+ # pass a URL or path to a miniconda
+ # installer:
+ #
+ # fslinstaller.py --miniconda https://path/to/miniconda.sh
+ # fslinstaller.py --miniconda ~/Downloads/miniconda.sh
+ #
+ # Alternatively, pass the directory of
+ # an existing [mini]conda installation
+ # to use that - for example, if a conda
+ # base environment has already been
+ # created at ~/fsl/, to install FSL into
+ # that base environment:
+ #
+ # fslinstaller.py --miniconda ~/fsl/
+ #
+ # Or to use an existing [mini]conda
+ # installation, and create FSL as a
+ # child environment, just set the
+ # destination directory to a
+ # different path, e.g.:
+ #
+ # fslinstaller.py --miniconda ~/miniconda3/ -d ~/fsl/
+ 'miniconda' : argparse.SUPPRESS,
+
+ # Use conda and not mamba
+ 'conda' : argparse.SUPPRESS,
+
+ # Disable SHA256 checksum validation
+ # of downloaded files
+ 'no_checksum' : argparse.SUPPRESS,
+
+ # Store temp files in this directory
+ # rather than in a temporary directory
+ 'workdir' : argparse.SUPPRESS,
+
+ # Treat this directory as user's home
+ # directory, for the purposes of shell
+ # configuration. Must already exist.
+ 'homedir' : argparse.SUPPRESS,
+
+ # Configure conda to skip SSL verification.
+ # Not recommended.
+ 'skip_ssl_verify' : argparse.SUPPRESS,
+
+ # Do not install packages matching this
+ # fnmatch-style wildcard pattern. Can
+ # be used multiple times.
+ 'exclude_package' : argparse.SUPPRESS,
+
+ # If the installer is run as root, the
+ # --no_env flag is automatically enabled
+ # UNLESS this flag is also provided.
+ 'root_env' : argparse.SUPPRESS,
+
+ # File to send progress information to.
+ 'progress_file' : argparse.SUPPRESS,
+ }
+
+ # parse args
+ for option in include:
+ shortflag, kwargs = options[option]
+ flags = ['--{}'.format(option)]
+ if shortflag is not None:
+ flags.insert(0, shortflag)
+ parser.add_argument(*flags, help=helps[option], **kwargs)
+
+ # parse_known_args so that newly added
+ # args are ignored by older versions,
+ # but will be parsed after self_update
+ args = parser.parse_known_args(argv)[0]
+
+ if getattr(args, 'fslversion', 'latest') != 'latest':
+ if Version(args.fslversion) < Version('6.0.6'):
+ printmsg(
+ 'This script can only be used to install FSL 6.0.6 or newer. '
+ 'Visit https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FslInstallation '
+ 'for information on installing older versions.', ERROR,
+ EMPHASIS)
+ sys.exit(1)
+
+ # add placeholder values for excluded args
+ for option in options.keys():
+ if option not in include:
+ setattr(args, option, None)
+
+ # alternate home dir (for debugging)
+ if args.homedir is not None:
+ args.homedir = op.abspath(args.homedir)
+ if not op.isdir(args.homedir):
+ printmsg('Home directory {} does not exist!'.format(args.homedir),
+ ERROR, EMPHASIS)
+ sys.exit(1)
+
+ # --no-env is automatically enabled
+ # when installer is run as root
+ if os.getuid() == 0 and not (args.root_env):
+ args.no_env = True
+
+ # don't modify shell profile
+ if args.no_env:
+ args.no_shell = True
+ args.no_matlab = True
+
+ # use workdir rather than a tempdir
+ if args.workdir is not None:
+ args.workdir = op.abspath(args.workdir)
+ if not op.exists(args.workdir):
+ os.mkdir(args.workdir)
+
+ # manifest takes priority over devrelease/devlatest
+ if args.manifest is not None:
+ args.devrelease = False
+ args.devlatest = False
+
+ if args.manifest is None:
+ args.manifest = FSL_RELEASE_MANIFEST
+
+ if args.devlatest:
+ args.devrelease = True
+
+ if args.exclude_package is None:
+ args.exclude_package = []
+
+ if args.logfile is not None:
+ args.logfile = op.abspath(args.logfile)
+ if args.progress_file is not None:
+ args.progress_file = op.abspath(args.progress_file)
+
+ # accept local path for manifest and environment
+ if args.manifest is not None:
+ args.manifest = op.expanduser(args.manifest)
+ if op.exists(args.manifest):
+ args.manifest = op.abspath(args.manifest)
+
+ # accept local path for miniconda installer, or
+ # path to existing miniconda installation
+ if args.miniconda is not None:
+ args.miniconda = op.expanduser(args.miniconda)
+ if op.exists(args.miniconda):
+ args.miniconda = op.abspath(args.miniconda)
+
+ return args
+
+
+def config_logging(prefix='fslinstaller_', logdir=None, logfile=None):
+ """Configures logging. If a logfile is not specified, log messages are
+ directed to $TMPDIR/fslinstaller_<unique_token>.log, or
+ logdir/fslinstaller_<unique_token>.log
+ """
+
+ if logfile is None:
+ if logdir is None:
+ logdir = tempfile.gettempdir()
+
+ # Use a unique name for the log file
+ # (important for multi-user systems)
+ logfilef, logfile = tempfile.mkstemp(prefix=prefix,
+ suffix='.log',
+ dir=logdir)
+ os.close(logfilef)
+
+ handler = logging.FileHandler(logfile)
+ formatter = logging.Formatter(
+ '%(asctime)s %(filename)s:%(lineno)4d: %(message)s', '%H:%M:%S')
+ handler.setFormatter(formatter)
+ log.addHandler(handler)
+ log.setLevel(logging.DEBUG)
+
+ return logfile
+
+
+@contextlib.contextmanager
+def handle_error(ctx):
+ """Used by main as a context manager around the main installation steps.
+ If an error occurs, prints some messages, performs some clean-up/
+ restoration tasks, and exits.
+ """
+
+ try:
+ yield
+
+ except Exception as e:
+ printmsg('\nERROR occurred during installation!', ERROR, EMPHASIS)
+ printmsg(' {}\n'.format(e), INFO)
+
+ # send traceback to log file
+ tb = traceback.format_tb(sys.exc_info()[2])
+ log.debug(''.join(tb))
+
+ # send env to logfile
+ log.debug('Environment variables:')
+ for k, v in os.environ.items():
+ log.debug('{}={}'.format(k, v))
+
+ if op.exists(ctx.destdir):
+ printmsg('Removing failed installation directory '
+ '{}'.format(ctx.destdir), WARNING)
+ ctx.run(Process.check_call, 'rm -r ' + ctx.destdir)
+
+ # overwrite_destdir moves the existing
+ # destdir to a temp location, so we can
+ # restore it if the installation fails
+ if not op.exists(ctx.destdir) and (ctx.old_destdir is not None):
+ printmsg('Restoring contents of {}'.format(ctx.destdir),
+ WARNING)
+ ctx.run(Process.check_call,
+ 'mv {} {}'.format(ctx.old_destdir, ctx.destdir))
+
+ # copy log file to ~/ so it is
+ # easier for the user to access
+ date = datetime.datetime.today().strftime('%Y%m%d%H%M%S')
+ logfile = 'fsl_installation_{}.log'.format(date)
+ logfile = op.join(op.expanduser('~'), logfile)
+ shutil.copy(ctx.logfile, logfile)
+
+ printmsg('\nFSL installation failed!\n', ERROR, EMPHASIS)
+ printmsg('Please check the log file - it may contain some '
+ 'more information to help you diagnose the problem: '
+ '{}\n'.format(logfile), WARNING, EMPHASIS)
+ sys.exit(1)
+
+
+def main(argv=None):
+ """Installer entry point. Downloads and installs miniconda and FSL, and
+ configures the user's environment.
+ """
+
+ printmsg('FSL installer version:', EMPHASIS, UNDERLINE, end='')
+ printmsg(' {}'.format(__version__))
+ printmsg('Press CTRL+C at any time to cancel installation', INFO)
+
+ if os.getuid() == 0:
+ printmsg('Running the installer script as root user is discouraged! '
+ 'You should run this script as a regular user - you will be '
+ 'asked for your administrator password if required.',
+ WARNING, EMPHASIS)
+
+ args = parse_args(argv)
+ logfile = config_logging(logdir=args.workdir, logfile=args.logfile)
+
+ log.debug(' '.join(sys.argv))
+ log.debug('Python: %s', sys.executable)
+ printmsg('Installation log file: {}\n'.format(logfile), INFO)
+
+ ctx = Context(args)
+ ctx.logfile = logfile
+
+ if not args.no_self_update:
+ self_update(ctx.manifest, args.workdir, not args.no_checksum,
+ ssl_verify=(not args.skip_ssl_verify))
+
+ if args.listversions:
+ list_available_versions(ctx.manifest)
+ sys.exit(0)
+
+ agree_to_license(ctx)
+
+ if (not args.skip_registration) and (ctx.registration_url is not None):
+ printmsg('During the installation process, please note that some '
+ 'system details will be automatically sent to the FSL '
+ 'development team. These details are extremely basic and '
+ 'cannot be used in any way to identify individual users. If '
+ 'you do not want any information to be sent, please cancel '
+ 'this installation by pressing CTRL+C, and re-run the '
+ 'installer with the --skip_registration option.\n', INFO)
+
+ try:
+ ctx.finalise_settings()
+ except Exception as e:
+ printmsg('An error has occurred: {}'.format(e), ERROR)
+ sys.exit(1)
+
+ check_rosetta_status(ctx)
+
+ # Do everything in a temporary directory,
+ # but don't delete it, as some operations
+ # may be run as root. The tempdir is
+ # deleted within the post_install_cleanup
+ # function.
+ with tempdir(args.workdir, delete=False) as tmpdir:
+
+ if args.workdir is not None:
+ tmpdir = None
+
+ # Ask the user if they want to overwrite
+ # an existing installation
+ overwrite_destdir(ctx)
+
+ download_fsl_environment(ctx)
+
+ printmsg('\nInstalling FSL in {}\n'.format(ctx.destdir), EMPHASIS)
+ with handle_error(ctx):
+ download_miniconda(ctx)
+ install_miniconda(ctx)
+ install_fsl(ctx)
+ finalise_installation(ctx)
+ post_install_cleanup(ctx, tmpdir)
+ register_installation(ctx)
+
+ if not args.no_shell:
+ configure_shell(ctx.shell, args.homedir, ctx.destdir)
+ if not args.no_matlab:
+ configure_matlab(args.homedir, ctx.destdir)
+
+ printmsg('\nFSL successfully installed\n', IMPORTANT)
+ if not args.no_shell:
+ printmsg('Open a new terminal, or log out and log back in, '
+ 'for the environment changes to take effect.', INFO)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/imcp b/imcp
index 5b91bc26c6d7..dd8f5bbfbf7d 100755
--- a/imcp
+++ b/imcp
@@ -1,10 +1,33 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
+#!/usr/bin/python
+# EASY-INSTALL-ENTRY-SCRIPT: 'fslpy==3.2.2','console_scripts','imcp'
import re
import sys
-from fsl.scripts.imcp import main
+# for compatibility with easy_install; see #2198
+__requires__ = 'fslpy==3.2.2'
+
+try:
+ from importlib.metadata import distribution
+except ImportError:
+ try:
+ from importlib_metadata import distribution
+ except ImportError:
+ from pkg_resources import load_entry_point
+
+
+def importlib_load_entry_point(spec, group, name):
+ dist_name, _, _ = spec.partition('==')
+ matches = (
+ entry_point
+ for entry_point in distribution(dist_name).entry_points
+ if entry_point.group == group and entry_point.name == name
+ )
+ return next(matches).load()
+
+
+globals().setdefault('load_entry_point', importlib_load_entry_point)
+
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
- sys.exit(main())
+ sys.exit(load_entry_point('fslpy==3.2.2', 'console_scripts', 'imcp')())
diff --git a/imglob b/imglob
index d5b40a51d29e..6d2a3c2c9077 100755
--- a/imglob
+++ b/imglob
@@ -1,10 +1,33 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
+#!/usr/bin/python
+# EASY-INSTALL-ENTRY-SCRIPT: 'fslpy==3.2.2','console_scripts','imglob'
import re
import sys
-from fsl.scripts.imglob import main
+# for compatibility with easy_install; see #2198
+__requires__ = 'fslpy==3.2.2'
+
+try:
+ from importlib.metadata import distribution
+except ImportError:
+ try:
+ from importlib_metadata import distribution
+ except ImportError:
+ from pkg_resources import load_entry_point
+
+
+def importlib_load_entry_point(spec, group, name):
+ dist_name, _, _ = spec.partition('==')
+ matches = (
+ entry_point
+ for entry_point in distribution(dist_name).entry_points
+ if entry_point.group == group and entry_point.name == name
+ )
+ return next(matches).load()
+
+
+globals().setdefault('load_entry_point', importlib_load_entry_point)
+
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
- sys.exit(main())
+ sys.exit(load_entry_point('fslpy==3.2.2', 'console_scripts', 'imglob')())
diff --git a/immv b/immv
index d4c62583c148..b012eec138a2 100755
--- a/immv
+++ b/immv
@@ -1,10 +1,33 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
+#!/usr/bin/python
+# EASY-INSTALL-ENTRY-SCRIPT: 'fslpy==3.2.2','console_scripts','immv'
import re
import sys
-from fsl.scripts.immv import main
+# for compatibility with easy_install; see #2198
+__requires__ = 'fslpy==3.2.2'
+
+try:
+ from importlib.metadata import distribution
+except ImportError:
+ try:
+ from importlib_metadata import distribution
+ except ImportError:
+ from pkg_resources import load_entry_point
+
+
+def importlib_load_entry_point(spec, group, name):
+ dist_name, _, _ = spec.partition('==')
+ matches = (
+ entry_point
+ for entry_point in distribution(dist_name).entry_points
+ if entry_point.group == group and entry_point.name == name
+ )
+ return next(matches).load()
+
+
+globals().setdefault('load_entry_point', importlib_load_entry_point)
+
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
- sys.exit(main())
+ sys.exit(load_entry_point('fslpy==3.2.2', 'console_scripts', 'immv')())
diff --git a/systemvars.mk b/systemvars.mk
deleted file mode 100755
index 33d1cc67c8b8..000000000000
--- a/systemvars.mk
+++ /dev/null
@@ -1,46 +0,0 @@
-# $Id: systemvars.mk,v 1.6 2019/02/14 13:29:45 mwebster Exp $
-
-# System dependent paths
-
-RM = /bin/rm
-CHMOD = /bin/chmod
-MKDIR = /bin/mkdir
-CP = /bin/cp
-MV = /bin/mv
-INSTALL = install -p
-TCLSH = /usr/bin/tclsh
-RANLIB = echo
-
-FSLML = ${FSLDIR}/bin/fslml
-
-# for SHELL, do not change the type of shell - only use Bourne or BASH
-SHELL = /bin/sh
-
-# Compiler dependent variables
-
-CC = gcc
-CXX = c++
-CXX11 = c++
-CSTATICFLAGS = -static
-CXXSTATICFLAGS = -static
-
-ARCHFLAGS = -m64
-ARCHLDFLAGS = -Wl,-rpath,'$$ORIGIN/../lib'
-
-PARALLELFLAGS = -fopenmp
-
-DEPENDFLAGS = -MM
-
-OPTFLAGS = -march=native -g -O3 -fexpensive-optimizations ${ARCHFLAGS}
-MACHDBGFLAGS = -g
-GNU_ANSI_FLAGS = -Wall -ansi -pedantic -std=c++11 -Wno-long-long
-SGI_ANSI_FLAGS = -ansi -fullwarn
-ANSI_FLAGS = ${GNU_ANSI_FLAGS}
-
-# CUDA development environment
-CUDA_INSTALLATION = /opt/cuda
-GENCODE_FLAGS = $(shell ${FSLDIR}/config/common/supportedGencodes.sh ${CUDA_INSTALLATION})
-LIB_CUDA = ${CUDA_INSTALLATION}/lib64
-INC_CUDA = ${CUDA_INSTALLATION}/include
-NVCC = ${CUDA_INSTALLATION}/bin/nvcc
-NVCC11= ${CUDA_INSTALLATION}/bin/nvcc