From ee976a1c1a712ce2bae48bb52067299187ffc057 Mon Sep 17 00:00:00 2001 From: Ruslan Kuprieiev Date: Mon, 30 Sep 2019 16:57:58 +0300 Subject: [PATCH] run: make shell not load config files Config files like `~/.zshenv`, `~/.bashenv` etc, are loaded on each shell invocation, which might overwrite the environment breaking the commands we are trying to wrap. To fix that, we need to provide appropriate flags to the shell we are using to avoid loading those configs. Fixes #2506 --- dvc/stage.py | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/dvc/stage.py b/dvc/stage.py index 7af3e516e..d244ab10b 100644 --- a/dvc/stage.py +++ b/dvc/stage.py @@ -789,8 +789,34 @@ class Stage(object): def _run(self): self._check_missing_deps() - executable = os.getenv("SHELL") if os.name != "nt" else None - self._warn_if_fish(executable) + + kwargs = {"cwd": self.wdir, "env": fix_env(None), "close_fds": True} + + if os.name == "nt": + kwargs["shell"] = True + cmd = self.cmd + else: + # NOTE: when you specify `shell=True`, `Popen` [1] will default to + # `/bin/sh` on *nix and will add ["/bin/sh", "-c"] to your command. + # But we actually want to run the same shell that we are running + # from right now, which is usually determined by the `SHELL` env + # var. So instead, we compose our command on our own, making sure + # to include special flags to prevent shell from reading any + # configs and modifying env, which may change the behavior or the + # command we are running. See [2] for more info. + # + # [1] https://github.com/python/cpython/blob/3.7/Lib/subprocess.py + # #L1426 + # [2] https://github.com/iterative/dvc/issues/2506 + # #issuecomment-535396799 + kwargs["shell"] = False + executable = os.getenv("SHELL") or "/bin/sh" + + self._warn_if_fish(executable) + + opts = {"zsh": ["--no-rcs"], "bash": ["--noprofile", "--norc"]} + name = os.path.basename(executable).lower() + cmd = [executable] + opts.get(name, []) + ["-c", self.cmd] main_thread = isinstance( threading.current_thread(), threading._MainThread @@ -799,14 +825,7 @@ class Stage(object): p = None try: - p = subprocess.Popen( - self.cmd, - cwd=self.wdir, - shell=True, - env=fix_env(os.environ), - executable=executable, - close_fds=True, - ) + p = subprocess.Popen(cmd, **kwargs) if main_thread: old_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) p.communicate()