Repository: python-poetry/poetry

Issue:
`poetry run` should use execve syscall
<!--
  Hi there! Thank you for discovering and submitting an issue.

  Before you submit this; let's make sure of a few things.
  Please make sure the following boxes are ticked if they are correct.
  If not, please try and fulfill these first.
-->

<!-- Checked checkbox should look like this: [x] -->
- [x] I am on the [latest](https://github.com/sdispater/poetry/releases/latest) Poetry version.
- [x] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).

<!--
  Once those are done, if you're able to fill in the following list with your information,
  it'd be very helpful to whoever handles the issue.
-->

- **OS version and name**: any
- **Poetry version**: 11.5
- **Link of a [Gist](https://gist.github.com/) with the contents of your pyproject.toml file**: <!-- Gist Link Here -->

## Issue

When running a program through `poetry run`, [`subprocess.call`](https://github.com/sdispater/poetry/blob/0.11.5/poetry/utils/venv.py#L251) is used to execute the command. This has the negative side effect of causing poetry to swallow all signals, making it difficult to wrap commands that depend on signals to be handled properly.

I think the behavior that `poetry run` should have is that the command provided is `exec`'d using the [`exceve`](https://docs.python.org/2/library/os.html#os.execve) syscall, which will replace the process and allow signals to be handled properly. This is similar to the behavior of similar utilities, like [`bundle exec`](https://github.com/bundler/bundler/blob/d368aa9e6257a27fab31efe0396069c8977cdc99/lib/bundler/cli/exec.rb#L53) in ruby.

Poetry should play nice with other unix utilities.

## Steps to reproduce

```console
$ cd $(mktemp -d)
$ poetry init
$ go get -u github.com/ejholmes/sigp
$ sigp sleep 60 &
$ kill -TERM $!
signal 15 received (terminated)
signal 20 received (child exited)
$ poetry run sigp sleep 60 &
$ kill -TERM $! # notice that sigp doesn't print anything, because it didn't get the TERM signal
$ ps -A | grep sigp # zombie process now
18862 ttys013    0:00.01 sigp sleep 60
```


Discussion:
`subprocess.run()` is good for it. But supported platforms has to be updated to python 3.5+.
I agree with OP that this is a great idea; should probably be a flag though, ie `poetry run --exec` (short form `poetry run -x` or something)

Resolving patch:
diff --git a/poetry/console/commands/run.py b/poetry/console/commands/run.py
--- a/poetry/console/commands/run.py
+++ b/poetry/console/commands/run.py
@@ -40,15 +40,13 @@ def run_script(self, script, args):
         cmd = ["python", "-c"]
 
         cmd += [
-            '"import sys; '
+            "import sys; "
             "from importlib import import_module; "
             "sys.argv = {!r}; {}"
-            "import_module('{}').{}()\"".format(
-                args, src_in_sys_path, module, callable_
-            )
+            "import_module('{}').{}()".format(args, src_in_sys_path, module, callable_)
         ]
 
-        return self.env.run(*cmd, shell=True, call=True)
+        return self.env.execute(*cmd)
 
     @property
     def _module(self):
diff --git a/poetry/utils/env.py b/poetry/utils/env.py
--- a/poetry/utils/env.py
+++ b/poetry/utils/env.py
@@ -712,7 +712,16 @@ def run(self, bin, *args, **kwargs):
     def execute(self, bin, *args, **kwargs):
         bin = self._bin(bin)
 
-        return subprocess.call([bin] + list(args), **kwargs)
+        if not self._is_windows:
+            args = [bin] + list(args)
+            if "env" in kwargs:
+                return os.execvpe(bin, args, kwargs["env"])
+            else:
+                return os.execvp(bin, args)
+        else:
+            exe = subprocess.Popen([bin] + list(args), **kwargs)
+            exe.communicate()
+            return exe.returncode
 
     def is_venv(self):  # type: () -> bool
         raise NotImplementedError()


Test patch: