Repository: nipy/nipype

Issue:
expert.opts are being written to the root directory
### Summary
`expert.opts` file is being written to the root directory which is not writable under singularity. 
See https://neurostars.org/t/singularity-fmriprep-export-opts-error/1491 for more details.


Discussion:
At a first glance, this is probably related to previous attempts to properly set the working directory for `Node`s.
https://github.com/nipy/nipype/blob/6ca791d9c5ec0efb9f56cc9e44758d6e53ffb800/nipype/interfaces/freesurfer/preprocess.py#L1317
Related: #2368.
Okay, it looks like `interface.cmdline` is called before the working directory is entered.

Also afterwards, but for some reason the running command is the one generated outside.

Probably the big problem is that it's all a massive hack to turn what should be runtime flags into a file. Maybe we should have gone with the "ExpertOptions" interface (260a27577dea454640bfc5117e0ad8e53ece02dd). Anyway, I'll see if there's any plausible way to have `cmdline` not drop a file in the CWD, whatever that happens to be.
Actually, my mistake. The correct command is being run, the problem is just that `cmdline` dumps a file wherever it happens to be, and we call `node.interface.cmdline` before running the node for logging purposes.
Good catch

Resolving patch:
diff --git a/nipype/pipeline/engine/nodes.py b/nipype/pipeline/engine/nodes.py
--- a/nipype/pipeline/engine/nodes.py
+++ b/nipype/pipeline/engine/nodes.py
@@ -27,7 +27,7 @@
 from ...utils.filemanip import (md5, FileNotFoundError, filename_to_list,
                                 list_to_filename, copyfiles, fnames_presuffix,
                                 loadpkl, split_filename, load_json, makedirs,
-                                emptydirs, savepkl, to_str)
+                                emptydirs, savepkl, to_str, indirectory)
 
 from ...interfaces.base import (traits, InputMultiPath, CommandLine, Undefined,
                                 DynamicTraitedSpec, Bunch, InterfaceResult,
@@ -627,7 +627,8 @@ def _run_command(self, execute, copyfiles=True):
             self._interface.__class__.__name__)
         if issubclass(self._interface.__class__, CommandLine):
             try:
-                cmd = self._interface.cmdline
+                with indirectory(outdir):
+                    cmd = self._interface.cmdline
             except Exception as msg:
                 result.runtime.stderr = '{}\n\n{}'.format(
                     getattr(result.runtime, 'stderr', ''), msg)
diff --git a/nipype/utils/filemanip.py b/nipype/utils/filemanip.py
--- a/nipype/utils/filemanip.py
+++ b/nipype/utils/filemanip.py
@@ -18,6 +18,7 @@
 import os.path as op
 import re
 import shutil
+import contextlib
 import posixpath
 import simplejson as json
 import numpy as np
@@ -916,3 +917,13 @@ def relpath(path, start=None):
     if not rel_list:
         return os.curdir
     return op.join(*rel_list)
+
+
+@contextlib.contextmanager
+def indirectory(path):
+    cwd = os.getcwd()
+    os.chdir(path)
+    try:
+        yield
+    finally:
+        os.chdir(cwd)


Test patch:
diff --git a/nipype/utils/tests/test_filemanip.py b/nipype/utils/tests/test_filemanip.py
--- a/nipype/utils/tests/test_filemanip.py
+++ b/nipype/utils/tests/test_filemanip.py
@@ -14,7 +14,7 @@
     save_json, load_json, fname_presuffix, fnames_presuffix, hash_rename,
     check_forhash, _parse_mount_table, _cifs_table, on_cifs, copyfile,
     copyfiles, filename_to_list, list_to_filename, check_depends,
-    split_filename, get_related_files)
+    split_filename, get_related_files, indirectory)
 
 
 def _ignore_atime(stat):
@@ -490,3 +490,34 @@ def test_cifs_check():
 
     _cifs_table[:] = []
     _cifs_table.extend(orig_table)
+
+
+def test_indirectory(tmpdir):
+    tmpdir.chdir()
+
+    os.makedirs('subdir1/subdir2')
+    sd1 = os.path.abspath('subdir1')
+    sd2 = os.path.abspath('subdir1/subdir2')
+
+    assert os.getcwd() == tmpdir.strpath
+    with indirectory('/'):
+        assert os.getcwd() == '/'
+    assert os.getcwd() == tmpdir.strpath
+    with indirectory('subdir1'):
+        assert os.getcwd() == sd1
+        with indirectory('subdir2'):
+            assert os.getcwd() == sd2
+            with indirectory('..'):
+                assert os.getcwd() == sd1
+                with indirectory('/'):
+                    assert os.getcwd() == '/'
+                assert os.getcwd() == sd1
+            assert os.getcwd() == sd2
+        assert os.getcwd() == sd1
+    assert os.getcwd() == tmpdir.strpath
+    try:
+        with indirectory('subdir1'):
+            raise ValueError("Erroring out of context")
+    except ValueError:
+        pass
+    assert os.getcwd() == tmpdir.strpath