Commit d0fb2f11 by Sébastien Eustace

Add the run command to execute command inside generated virtualenvs

parent 47c0cecd
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
### Added ### Added
- Added support for virtualenv autogeneration (Python 3.6+ only). - Added support for virtualenv autogeneration (Python 3.6+ only).
- Added the `run` command to execute commands inside the created virtualenvs.
### Fixed ### Fixed
......
import os import os
from cleo import Application as BaseApplication 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.poetry import Poetry
from poetry.utils.venv import Venv
from .commands import AboutCommand from .commands import AboutCommand
from .commands import AddCommand from .commands import AddCommand
...@@ -14,6 +16,7 @@ from .commands import LockCommand ...@@ -14,6 +16,7 @@ from .commands import LockCommand
from .commands import NewCommand from .commands import NewCommand
from .commands import PublishCommand from .commands import PublishCommand
from .commands import RemoveCommand from .commands import RemoveCommand
from .commands import RunCommand
from .commands import ShowCommand from .commands import ShowCommand
from .commands import UpdateCommand from .commands import UpdateCommand
...@@ -24,6 +27,7 @@ class Application(BaseApplication): ...@@ -24,6 +27,7 @@ class Application(BaseApplication):
super().__init__('Poetry', Poetry.VERSION) super().__init__('Poetry', Poetry.VERSION)
self._poetry = None self._poetry = None
self._skip_io_configuration = False
@property @property
def poetry(self) -> Poetry: def poetry(self) -> Poetry:
...@@ -37,6 +41,40 @@ class Application(BaseApplication): ...@@ -37,6 +41,40 @@ class Application(BaseApplication):
def reset_poetry(self) -> None: def reset_poetry(self) -> None:
self._poetry = 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: def get_default_commands(self) -> list:
commands = super(Application, self).get_default_commands() commands = super(Application, self).get_default_commands()
...@@ -50,6 +88,7 @@ class Application(BaseApplication): ...@@ -50,6 +88,7 @@ class Application(BaseApplication):
NewCommand(), NewCommand(),
PublishCommand(), PublishCommand(),
RemoveCommand(), RemoveCommand(),
RunCommand(),
ShowCommand(), ShowCommand(),
UpdateCommand(), UpdateCommand(),
] ]
...@@ -7,5 +7,6 @@ from .lock import LockCommand ...@@ -7,5 +7,6 @@ from .lock import LockCommand
from .new import NewCommand from .new import NewCommand
from .publish import PublishCommand from .publish import PublishCommand
from .remove import RemoveCommand from .remove import RemoveCommand
from .run import RunCommand
from .show import ShowCommand from .show import ShowCommand
from .update import UpdateCommand 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 os
import subprocess import subprocess
import sys import sys
from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from subprocess import CalledProcessError from subprocess import CalledProcessError
...@@ -30,6 +30,14 @@ class Venv: ...@@ -30,6 +30,14 @@ class Venv:
def __init__(self, venv=None): def __init__(self, venv=None):
self._venv = venv 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 self._version_info = None
@classmethod @classmethod
...@@ -40,15 +48,6 @@ class Venv: ...@@ -40,15 +48,6 @@ class Venv:
config = Config.create('config.toml') config = Config.create('config.toml')
create_venv = config.setting('settings.virtualenvs.create') 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') venv_path = config.setting('settings.virtualenvs.path')
if venv_path is None: if venv_path is None:
...@@ -63,6 +62,16 @@ class Venv: ...@@ -63,6 +62,16 @@ class Venv:
venv = venv_path / name venv = venv_path / name
if not venv.exists(): 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( io.writeln(
f'Creating virtualenv <info>{name}</> in {str(venv_path)}' f'Creating virtualenv <info>{name}</> in {str(venv_path)}'
) )
...@@ -91,26 +100,7 @@ class Venv: ...@@ -91,26 +100,7 @@ class Venv:
# Running properly in the virtualenv, don't need to do anything # Running properly in the virtualenv, don't need to do anything
return cls() return cls()
if sys.platform == "win32": venv = os.environ['VIRTUAL_ENV']
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'
)
return cls(venv) return cls(venv)
...@@ -163,6 +153,37 @@ class Venv: ...@@ -163,6 +153,37 @@ class Venv:
return output.decode() 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: def _bin(self, bin) -> str:
""" """
Return path to the given executable. Return path to the given executable.
...@@ -170,9 +191,7 @@ class Venv: ...@@ -170,9 +191,7 @@ class Venv:
if not self.is_venv(): if not self.is_venv():
return bin return bin
return os.path.realpath( return str(self._bin_dir / bin)
os.path.join(self._venv, '..', '..', '..', 'bin', bin)
)
def is_venv(self) -> bool: def is_venv(self) -> bool:
return self._venv is not None 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