171 lines
5.3 KiB
Python
171 lines
5.3 KiB
Python
import os
|
|
import subprocess
|
|
import sys
|
|
from os.path import expandvars
|
|
|
|
from pipenv.utils.project import ensure_project
|
|
from pipenv.utils.shell import cmd_list_to_shell, system_which
|
|
from pipenv.vendor import click
|
|
|
|
|
|
def do_shell(
|
|
project, python=False, fancy=False, shell_args=None, pypi_mirror=None, quiet=False
|
|
):
|
|
# Ensure that virtualenv is available.
|
|
ensure_project(
|
|
project,
|
|
python=python,
|
|
validate=False,
|
|
pypi_mirror=pypi_mirror,
|
|
)
|
|
|
|
# Support shell compatibility mode.
|
|
if project.s.PIPENV_SHELL_FANCY:
|
|
fancy = True
|
|
|
|
from pipenv.shells import choose_shell
|
|
|
|
shell = choose_shell(project)
|
|
if not quiet:
|
|
click.echo("Launching subshell in virtual environment...", err=True)
|
|
|
|
fork_args = (
|
|
project.virtualenv_location,
|
|
project.project_directory,
|
|
shell_args,
|
|
)
|
|
|
|
# Set an environment variable, so we know we're in the environment.
|
|
# Only set PIPENV_ACTIVE after finishing reading virtualenv_location
|
|
# otherwise its value will be changed
|
|
os.environ["PIPENV_ACTIVE"] = "1"
|
|
|
|
if fancy:
|
|
shell.fork(*fork_args)
|
|
return
|
|
|
|
try:
|
|
shell.fork_compat(*fork_args)
|
|
except (AttributeError, ImportError):
|
|
click.echo(
|
|
"Compatibility mode not supported. "
|
|
"Trying to continue as well-configured shell...",
|
|
err=True,
|
|
)
|
|
shell.fork(*fork_args)
|
|
|
|
|
|
def do_run(project, command, args, python=False, pypi_mirror=None):
|
|
"""Attempt to run command either pulling from project or interpreting as executable.
|
|
|
|
Args are appended to the command in [scripts] section of project if found.
|
|
"""
|
|
from pipenv.cmdparse import ScriptEmptyError
|
|
|
|
env = os.environ.copy()
|
|
|
|
# Ensure that virtualenv is available.
|
|
ensure_project(
|
|
project,
|
|
python=python,
|
|
validate=False,
|
|
pypi_mirror=pypi_mirror,
|
|
)
|
|
|
|
path = env.get("PATH", "")
|
|
if project.virtualenv_location:
|
|
new_path = os.path.join(
|
|
project.virtualenv_location, "Scripts" if os.name == "nt" else "bin"
|
|
)
|
|
paths = path.split(os.pathsep)
|
|
paths.insert(0, new_path)
|
|
path = os.pathsep.join(paths)
|
|
env["VIRTUAL_ENV"] = project.virtualenv_location
|
|
env["PATH"] = path
|
|
|
|
# Set an environment variable, so we know we're in the environment.
|
|
# Only set PIPENV_ACTIVE after finishing reading virtualenv_location
|
|
# such as in inline_activate_virtual_environment
|
|
# otherwise its value will be changed
|
|
env["PIPENV_ACTIVE"] = "1"
|
|
|
|
try:
|
|
script = project.build_script(command, args)
|
|
cmd_string = cmd_list_to_shell([script.command] + script.args)
|
|
if project.s.is_verbose():
|
|
click.echo(click.style(f"$ {cmd_string}"), err=True)
|
|
except ScriptEmptyError:
|
|
click.echo("Can't run script {0!r}-it's empty?", err=True)
|
|
run_args = [project, script]
|
|
run_kwargs = {"env": env}
|
|
if os.name == "nt":
|
|
run_fn = do_run_nt
|
|
else:
|
|
run_fn = do_run_posix
|
|
run_kwargs.update({"command": command})
|
|
run_fn(*run_args, **run_kwargs)
|
|
|
|
|
|
def do_run_posix(project, script, command, env):
|
|
path = env.get("PATH")
|
|
command_path = system_which(script.command, path=path)
|
|
if not command_path:
|
|
if project.has_script(command):
|
|
click.echo(
|
|
"{}: the command {} (from {}) could not be found within {}."
|
|
"".format(
|
|
click.style("Error", fg="red", bold=True),
|
|
click.style(script.command, fg="yellow"),
|
|
click.style(command, bold=True),
|
|
click.style("PATH", bold=True),
|
|
),
|
|
err=True,
|
|
)
|
|
else:
|
|
click.echo(
|
|
"{}: the command {} could not be found within {} or Pipfile's {}."
|
|
"".format(
|
|
click.style("Error", fg="red", bold=True),
|
|
click.style(command, fg="yellow"),
|
|
click.style("PATH", bold=True),
|
|
click.style("[scripts]", bold=True),
|
|
),
|
|
err=True,
|
|
)
|
|
sys.exit(1)
|
|
os.execve(
|
|
command_path,
|
|
[command_path, *(os.path.expandvars(arg) for arg in script.args)],
|
|
env,
|
|
)
|
|
|
|
|
|
def do_run_nt(project, script, env):
|
|
p = _launch_windows_subprocess(script, env)
|
|
p.communicate()
|
|
sys.exit(p.returncode)
|
|
|
|
|
|
def _launch_windows_subprocess(script, env):
|
|
path = env.get("PATH", "")
|
|
command = system_which(script.command, path=path)
|
|
|
|
options = {"universal_newlines": True, "env": env}
|
|
script.cmd_args[1:] = [expandvars(arg) for arg in script.args]
|
|
|
|
# Command not found, maybe this is a shell built-in?
|
|
if not command:
|
|
return subprocess.Popen(script.cmdify(), shell=True, **options)
|
|
|
|
# Try to use CreateProcess directly if possible. Specifically catch
|
|
# Windows error 193 "Command is not a valid Win32 application" to handle
|
|
# a "command" that is non-executable. See pypa/pipenv#2727.
|
|
try:
|
|
return subprocess.Popen([command] + script.args, **options)
|
|
except OSError as e:
|
|
if e.winerror != 193:
|
|
raise
|
|
|
|
# Try shell mode to use Windows's file association for file launch.
|
|
return subprocess.Popen(script.cmdify(), shell=True, **options)
|