Commit d0fb2f11 by Sébastien Eustace

Add the run command to execute command inside generated virtualenvs

parent 47c0cecd
......@@ -5,6 +5,7 @@
### Added
- Added support for virtualenv autogeneration (Python 3.6+ only).
- Added the `run` command to execute commands inside the created virtualenvs.
### Fixed
......
import os
from cleo import Application as BaseApplication
from cleo.inputs import ArgvInput
from cleo.outputs import ConsoleOutput
from poetry.io.raw_argv_input import RawArgvInput
from poetry.poetry import Poetry
from poetry.utils.venv import Venv
from .commands import AboutCommand
from .commands import AddCommand
......@@ -14,6 +16,7 @@ from .commands import LockCommand
from .commands import NewCommand
from .commands import PublishCommand
from .commands import RemoveCommand
from .commands import RunCommand
from .commands import ShowCommand
from .commands import UpdateCommand
......@@ -24,6 +27,7 @@ class Application(BaseApplication):
super().__init__('Poetry', Poetry.VERSION)
self._poetry = None
self._skip_io_configuration = False
@property
def poetry(self) -> Poetry:
......@@ -37,6 +41,40 @@ class Application(BaseApplication):
def reset_poetry(self) -> None:
self._poetry = None
def run(self, i=None, o=None) -> int:
if i is None:
i = ArgvInput()
if o is None:
o = ConsoleOutput()
name = i.get_first_argument()
if name == 'run':
self._skip_io_configuration = True
i = RawArgvInput()
return super().run(i, o)
def do_run(self, i, o):
name = self.get_command_name(i)
if name != 'run':
return super().do_run(i, o)
command = self.find(name)
self._running_command = command
status_code = command.run(i, o)
self._running_command = None
return status_code
def configure_io(self, i, o):
if self._skip_io_configuration:
return
super().configure_io(i, o)
def get_default_commands(self) -> list:
commands = super(Application, self).get_default_commands()
......@@ -50,6 +88,7 @@ class Application(BaseApplication):
NewCommand(),
PublishCommand(),
RemoveCommand(),
RunCommand(),
ShowCommand(),
UpdateCommand(),
]
......@@ -7,5 +7,6 @@ from .lock import LockCommand
from .new import NewCommand
from .publish import PublishCommand
from .remove import RemoveCommand
from .run import RunCommand
from .show import ShowCommand
from .update import UpdateCommand
from .venv_command import VenvCommand
class RunCommand(VenvCommand):
"""
Runs a command in the appropriate environment.
run
{ args* : The command and arguments/options to run. }
"""
def handle(self):
args = self.argument('args')
venv = self.venv
return venv.exec(*args)
def merge_application_definition(self, merge_args=True):
if self._application is None \
or (self._application_definition_merged
and (self._application_definition_merged_with_args or not merge_args)):
return
if merge_args:
current_arguments = self._definition.get_arguments()
self._definition.set_arguments(self._application.get_definition().get_arguments())
self._definition.add_arguments(current_arguments)
self._application_definition_merged = True
if merge_args:
self._application_definition_merged_with_args = True
import sys
from cleo.inputs import ArgvInput
class RawArgvInput(ArgvInput):
def parse(self):
self._parsed = self._tokens
while True:
try:
token = self._parsed.pop(0)
except IndexError:
break
self.parse_argument(token)
import glob
import os
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path
from subprocess import CalledProcessError
......@@ -30,6 +30,14 @@ class Venv:
def __init__(self, venv=None):
self._venv = venv
if self._venv:
self._venv = Path(self._venv)
self._bin_dir = None
if venv:
bin_dir = 'bin' if sys.platform != 'win32' else 'Scripts'
self._bin_dir = self._venv / bin_dir
self._version_info = None
@classmethod
......@@ -40,15 +48,6 @@ class Venv:
config = Config.create('config.toml')
create_venv = config.setting('settings.virtualenvs.create')
if create_venv is False:
io.writeln(
'<fg=black;bg=yellow>'
'Skipping virtualenv creation, '
'as specified in config file.'
'</>'
)
return cls()
venv_path = config.setting('settings.virtualenvs.path')
if venv_path is None:
......@@ -63,6 +62,16 @@ class Venv:
venv = venv_path / name
if not venv.exists():
if create_venv is False:
io.writeln(
'<fg=black;bg=yellow>'
'Skipping virtualenv creation, '
'as specified in config file.'
'</>'
)
return cls()
io.writeln(
f'Creating virtualenv <info>{name}</> in {str(venv_path)}'
)
......@@ -91,26 +100,7 @@ class Venv:
# Running properly in the virtualenv, don't need to do anything
return cls()
if sys.platform == "win32":
venv = os.path.join(
os.environ['VIRTUAL_ENV'], 'Lib', 'site-packages'
)
else:
lib = os.path.join(
os.environ['VIRTUAL_ENV'], 'lib'
)
python = glob.glob(
os.path.join(lib, 'python*')
)[0].replace(
lib + '/', ''
)
venv = os.path.join(
lib,
python,
'site-packages'
)
venv = os.environ['VIRTUAL_ENV']
return cls(venv)
......@@ -163,6 +153,37 @@ class Venv:
return output.decode()
def exec(self, bin, *args):
if not self.is_venv():
return subprocess.run([bin] + list(args)).returncode
else:
with self.temp_environ():
os.environ['PATH'] = self._path()
completed = subprocess.run([bin] + list(args))
return completed.returncode
@contextmanager
def temp_environ(self):
environ = dict(os.environ)
try:
yield
finally:
os.environ.clear()
os.environ.update(environ)
def _path(self):
return os.pathsep.join([
str(self._bin_dir),
os.environ['PATH'],
])
def get_shell(self):
shell = Path(os.environ.get('SHELL', '')).stem
if shell in ('bash', 'zsh', 'fish'):
return shell
def _bin(self, bin) -> str:
"""
Return path to the given executable.
......@@ -170,9 +191,7 @@ class Venv:
if not self.is_venv():
return bin
return os.path.realpath(
os.path.join(self._venv, '..', '..', '..', 'bin', bin)
)
return str(self._bin_dir / bin)
def is_venv(self) -> bool:
return self._venv is not None
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment