Repository: conda/conda

Issue:
Envs need to be activated before post-link scripts run
In Python 3.7, we removed a patch to Python that put Library/bin on PATH.  This was done because putting that on PATH broke the new conda activate stuff somehow.  A side effect of this change is that packages with post-link scripts may fail to run (see https://github.com/ContinuumIO/anaconda-issues/issues/10082 for one example).  At a minimum, we should be activating environments before we run post-link scripts.  We should think about pre-unlink and pre-link, too, I guess.


Discussion:
Without https://github.com/conda/conda/issues/6820, I'm a little bit nervous about the effect this could have on install times.  I guess we'll see.

Resolving patch:
diff --git a/conda/core/link.py b/conda/core/link.py
--- a/conda/core/link.py
+++ b/conda/core/link.py
@@ -6,9 +6,10 @@
 from collections import defaultdict, namedtuple
 from logging import getLogger
 import os
-from os.path import basename, dirname, isdir, join
+from os.path import abspath, basename, dirname, isdir, join
 from subprocess import CalledProcessError
 import sys
+import tempfile
 from traceback import format_exception_only
 import warnings
 
@@ -596,7 +597,8 @@ def _execute_actions(pkg_idx, axngroup):
             for axn_idx, action in enumerate(axngroup.actions):
                 action.execute()
             if axngroup.type in ('unlink', 'link'):
-                run_script(target_prefix, prec, 'post-unlink' if is_unlink else 'post-link')
+                run_script(target_prefix, prec, 'post-unlink' if is_unlink else 'post-link',
+                           activate=True)
         except Exception as e:  # this won't be a multi error
             # reverse this package
             log.debug("Error in action #%d for pkg_idx #%d %r", axn_idx, pkg_idx, action,
@@ -958,7 +960,7 @@ def _calculate_change_report(prefix, unlink_precs, link_precs, download_urls, sp
         return change_report
 
 
-def run_script(prefix, prec, action='post-link', env_prefix=None):
+def run_script(prefix, prec, action='post-link', env_prefix=None, activate=False):
     """
     call the post-link (or pre-unlink) script, and return True on success,
     False on failure
@@ -994,13 +996,35 @@ def run_script(prefix, prec, action='post-link', env_prefix=None):
 
     if on_win:
         try:
-            command_args = [os.environ[str('COMSPEC')], '/d', '/c', path]
+            comspec = os.environ[str('COMSPEC')]
         except KeyError:
             log.info("failed to run %s for %s due to COMSPEC KeyError", action, prec.dist_str())
             return False
+        if activate:
+            conda_bat = env.get("CONDA_BAT", abspath(join(context.root_prefix, 'bin', 'conda')))
+            with tempfile.NamedTemporaryFile(
+                    mode='w', prefix=prefix, suffix='.bat', delete=False) as fh:
+                fh.write('@CALL \"{0}\" activate \"{1}\"\n'.format(conda_bat, prefix))
+                fh.write('echo "PATH: %PATH%\n')
+                fh.write('@CALL \"{0}\"\n'.format(path))
+                script_caller = fh.name
+            command_args = [comspec, '/d', '/c', script_caller]
+        else:
+            command_args = [comspec, '/d', '/c', path]
+
     else:
         shell_path = 'sh' if 'bsd' in sys.platform else 'bash'
-        command_args = [shell_path, "-x", path]
+        if activate:
+            conda_exe = env.get("CONDA_EXE", abspath(join(context.root_prefix, 'bin', 'conda')))
+            with tempfile.NamedTemporaryFile(mode='w', prefix=prefix, delete=False) as fh:
+                fh.write("eval \"$(\"{0}\" \"shell.posix\" \"hook\")\"\n".format(conda_exe)),
+                fh.write("conda activate \"{0}\"\n".format(prefix)),
+                fh.write("source \"{}\"\n".format(path))
+                script_caller = fh.name
+            command_args = [shell_path, "-x", script_caller]
+        else:
+            command_args = [shell_path, "-x", path]
+            script_caller = None
 
     env['ROOT_PREFIX'] = context.root_prefix
     env['PREFIX'] = env_prefix or prefix
@@ -1036,6 +1060,9 @@ def run_script(prefix, prec, action='post-link', env_prefix=None):
     else:
         messages(prefix)
         return True
+    finally:
+        if script_caller is not None:
+            os.remove(script_caller)
 
 
 def messages(prefix):


Test patch:
diff --git a/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/activate_d_test.bat b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/activate_d_test.bat
new file mode 100644
--- /dev/null
+++ b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/activate_d_test.bat
@@ -0,0 +1,2 @@
+echo "setting TEST_VAR"
+set TEST_VAR=1
diff --git a/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/bld.bat b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/bld.bat
new file mode 100644
--- /dev/null
+++ b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/bld.bat
@@ -0,0 +1,6 @@
+mkdir %PREFIX%\etc\conda\activate.d
+copy %RECIPE_DIR%\activate_d_test.bat %PREFIX%\etc\conda\activate.d\test.bat
+
+mkdir %PREFIX%\etc\conda\deactivate.d
+echo "echo setting TEST_VAR" > %PREFIX%\etc\conda\deactivate.d\test.bat
+echo set TEST_VAR= > %PREFIX%\etc\conda\deactivate.d\test.bat
diff --git a/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/build.sh b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/build.sh
new file mode 100644
--- /dev/null
+++ b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/build.sh
@@ -0,0 +1,7 @@
+mkdir -p $PREFIX/etc/conda/activate.d
+echo "echo 'setting TEST_VAR' && export TEST_VAR=1" > $PREFIX/etc/conda/activate.d/test.sh
+chmod +x $PREFIX/etc/conda/activate.d/test.sh
+
+mkdir -p $PREFIX/etc/conda/deactivate.d
+echo "echo 'unsetting TEST_VAR' && unset TEST_VAR" > $PREFIX/etc/conda/deactivate.d/test.sh
+chmod +x $PREFIX/etc/conda/deactivate.d/test.sh
diff --git a/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/meta.yaml b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/meta.yaml
@@ -0,0 +1,13 @@
+package:
+  name: _conda_test_env_activated_when_post_link_executed
+  version: 1.0
+
+about:
+  summary: test package that checks for an activated env in the post link script
+  description: |
+    This is a test packages that checks that conda environment are activated 
+    when running a post link script. This package has an activate.d script which
+    sets the environment variable TEST_VAR. The packages post link script tests
+    that this variable is set, if it is not the script fails. If this post
+    link script is run under a different environment, for example the base env,
+    there is a good chance that it will fail.
diff --git a/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/post-link.bat b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/post-link.bat
new file mode 100644
--- /dev/null
+++ b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/post-link.bat
@@ -0,0 +1,8 @@
+
+echo "TEST_VAR is set to :%TEST_VAR%:" >> %PREFIX%\.messages.txt
+if "%TEST_VAR%"=="1" (
+    echo "Success: TEST_VAR is set correctly" >> %PREFIX%\.messages.txt
+    exit 0
+)
+echo "ERROR: TEST_VAR is not set or set incorrectly" >> %PREFIX%\.messages.txt
+exit 1
diff --git a/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/post-link.sh b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/post-link.sh
new file mode 100644
--- /dev/null
+++ b/tests/data/_conda_test_env_activated_when_post_link_executed_recipe/post-link.sh
@@ -0,0 +1,8 @@
+# send feedback to .messages to make debugging easier
+echo "TEST_VAR is set to :${TEST_VAR}:" >> "$PREFIX/.messages.txt"
+if [ "${TEST_VAR}" != "1" ]; then
+    echo "Error: TEST_VAR is not set or set incorrectly" >> "$PREFIX/.messages.txt";
+    exit 1;
+fi
+echo "Success: TEST_VAR is set correctly" >> "$PREFIX/.messages.txt"
+exit 0
diff --git a/tests/test_create.py b/tests/test_create.py
--- a/tests/test_create.py
+++ b/tests/test_create.py
@@ -247,7 +247,7 @@ def package_is_installed(prefix, spec):
     spec = MatchSpec(spec)
     prefix_recs = tuple(PrefixData(prefix).query(spec))
     if len(prefix_recs) > 1:
-        raise AssertionError("Multiple packages installed.%s" 
+        raise AssertionError("Multiple packages installed.%s"
                              % (dashlist(prec.dist_str() for prec in prefix_recs)))
     return bool(len(prefix_recs))
 
@@ -2152,6 +2152,13 @@ def test_run_script_called(self):
                 assert package_is_installed(prefix, 'openssl')
                 assert rs.call_count == 1
 
+    def test_post_link_run_in_env(self):
+        test_pkg = '_conda_test_env_activated_when_post_link_executed'
+        # a non-unicode name must be provided here as activate.d scripts
+        # are not execuated on windows, see https://github.com/conda/conda/issues/8241
+        with make_temp_env(test_pkg, '-c conda-test', name='post_link_test') as prefix:
+            assert package_is_installed(prefix, test_pkg)
+
     def test_conda_info_python(self):
         stdout, stderr = run_command(Commands.INFO, None, "python=3.5")
         assert "python 3.5.1 0" in stdout