-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Create environment using venv
or conda
#19848
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b5cbdc8
2e5ae43
4e4f60e
48e31f8
d11fff5
d323df9
703dc9e
9f44ab6
cd09e93
b4c8d00
9fca478
71ab854
e9e7ca4
7995c2c
1eb96cc
952454c
afc9c4f
37fb908
6b4cceb
d7729ca
69729f4
29ba248
bc83068
7f6e503
7463805
e7b3516
7e38079
861cd98
c55d4c6
cbb337a
503c2da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. | ||
|
||
import argparse | ||
import os | ||
import pathlib | ||
import subprocess | ||
import sys | ||
from typing import Optional, Sequence, Union | ||
|
||
CONDA_ENV_NAME = ".conda" | ||
CWD = pathlib.PurePath(os.getcwd()) | ||
|
||
|
||
class VenvError(Exception): | ||
pass | ||
|
||
|
||
def parse_args(argv: Sequence[str]) -> argparse.Namespace: | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
"--python", | ||
action="store", | ||
help="Python version to install in the virtual environment.", | ||
default=f"{sys.version_info.major}.{sys.version_info.minor}", | ||
) | ||
parser.add_argument( | ||
"--install", | ||
action="store_true", | ||
default=False, | ||
help="Install packages into the virtual environment.", | ||
) | ||
parser.add_argument( | ||
"--git-ignore", | ||
action="store_true", | ||
default=False, | ||
help="Add .gitignore to the newly created virtual environment.", | ||
) | ||
parser.add_argument( | ||
"--name", | ||
default=CONDA_ENV_NAME, | ||
type=str, | ||
help="Name of the virtual environment.", | ||
metavar="NAME", | ||
action="store", | ||
) | ||
return parser.parse_args(argv) | ||
|
||
|
||
def file_exists(path: Union[str, pathlib.PurePath]) -> bool: | ||
return os.path.exists(path) | ||
|
||
|
||
def conda_env_exists(name: Union[str, pathlib.PurePath]) -> bool: | ||
return os.path.exists(CWD / name) | ||
|
||
|
||
def run_process(args: Sequence[str], error_message: str) -> None: | ||
try: | ||
print("Running: " + " ".join(args)) | ||
subprocess.run(args, cwd=os.getcwd(), check=True) | ||
except subprocess.CalledProcessError: | ||
raise VenvError(error_message) | ||
|
||
|
||
def get_conda_env_path(name: str) -> str: | ||
return os.fspath(CWD / name) | ||
|
||
|
||
def install_packages(env_path: str) -> None: | ||
yml = os.fspath(CWD / "environment.yml") | ||
if file_exists(yml): | ||
print(f"CONDA_INSTALLING_YML: {yml}") | ||
run_process( | ||
[ | ||
sys.executable, | ||
"-m", | ||
"conda", | ||
"env", | ||
"update", | ||
"--prefix", | ||
env_path, | ||
"--file", | ||
yml, | ||
], | ||
"CREATE_CONDA.FAILED_INSTALL_YML", | ||
) | ||
|
||
|
||
def add_gitignore(name: str) -> None: | ||
git_ignore = os.fspath(CWD / name / ".gitignore") | ||
if not file_exists(git_ignore): | ||
print(f"Creating: {git_ignore}") | ||
with open(git_ignore, "w") as f: | ||
f.write("*") | ||
|
||
|
||
def main(argv: Optional[Sequence[str]] = None) -> None: | ||
if argv is None: | ||
argv = [] | ||
args = parse_args(argv) | ||
|
||
if not conda_env_exists(args.name): | ||
run_process( | ||
[ | ||
sys.executable, | ||
"-m", | ||
"conda", | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"create", | ||
"--yes", | ||
"--prefix", | ||
args.name, | ||
f"python={args.python}", | ||
], | ||
"CREATE_CONDA.ENV_FAILED_CREATION", | ||
) | ||
if args.git_ignore: | ||
add_gitignore(args.name) | ||
|
||
env_path = get_conda_env_path(args.name) | ||
print(f"CREATED_CONDA_ENV:{env_path}") | ||
|
||
if args.install: | ||
install_packages(env_path) | ||
|
||
|
||
if __name__ == "__main__": | ||
main(sys.argv[1:]) |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,130 @@ | ||||||||||||||||||||||||||||||||||||||||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||||||||||||||||||||||||||||||||||||||||
# Licensed under the MIT License. | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
import argparse | ||||||||||||||||||||||||||||||||||||||||
import importlib.util as import_util | ||||||||||||||||||||||||||||||||||||||||
import os | ||||||||||||||||||||||||||||||||||||||||
import pathlib | ||||||||||||||||||||||||||||||||||||||||
import subprocess | ||||||||||||||||||||||||||||||||||||||||
import sys | ||||||||||||||||||||||||||||||||||||||||
from typing import Optional, Sequence, Union | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
VENV_NAME = ".venv" | ||||||||||||||||||||||||||||||||||||||||
CWD = pathlib.PurePath(os.getcwd()) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
class VenvError(Exception): | ||||||||||||||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def parse_args(argv: Sequence[str]) -> argparse.Namespace: | ||||||||||||||||||||||||||||||||||||||||
parser = argparse.ArgumentParser() | ||||||||||||||||||||||||||||||||||||||||
parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||
"--install", | ||||||||||||||||||||||||||||||||||||||||
action="store_true", | ||||||||||||||||||||||||||||||||||||||||
default=False, | ||||||||||||||||||||||||||||||||||||||||
help="Install packages into the virtual environment.", | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||
"--git-ignore", | ||||||||||||||||||||||||||||||||||||||||
action="store_true", | ||||||||||||||||||||||||||||||||||||||||
default=False, | ||||||||||||||||||||||||||||||||||||||||
help="Add .gitignore to the newly created virtual environment.", | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||
"--name", | ||||||||||||||||||||||||||||||||||||||||
default=VENV_NAME, | ||||||||||||||||||||||||||||||||||||||||
type=str, | ||||||||||||||||||||||||||||||||||||||||
help="Name of the virtual environment.", | ||||||||||||||||||||||||||||||||||||||||
metavar="NAME", | ||||||||||||||||||||||||||||||||||||||||
action="store", | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
return parser.parse_args(argv) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def is_installed(module: str) -> bool: | ||||||||||||||||||||||||||||||||||||||||
return import_util.find_spec(module) is not None | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def file_exists(path: Union[str, pathlib.PurePath]) -> bool: | ||||||||||||||||||||||||||||||||||||||||
return os.path.exists(path) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def venv_exists(name: str) -> bool: | ||||||||||||||||||||||||||||||||||||||||
return os.path.exists(CWD / name) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def run_process(args: Sequence[str], error_message: str) -> None: | ||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||
print("Running: " + " ".join(args)) | ||||||||||||||||||||||||||||||||||||||||
subprocess.run(args, cwd=os.getcwd(), check=True) | ||||||||||||||||||||||||||||||||||||||||
except subprocess.CalledProcessError: | ||||||||||||||||||||||||||||||||||||||||
raise VenvError(error_message) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def get_venv_path(name: str) -> str: | ||||||||||||||||||||||||||||||||||||||||
karthiknadig marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||
# See `venv` doc here for more details on binary location: | ||||||||||||||||||||||||||||||||||||||||
# https://docs.python.org/3/library/venv.html#creating-virtual-environments | ||||||||||||||||||||||||||||||||||||||||
if sys.platform == "win32": | ||||||||||||||||||||||||||||||||||||||||
return os.fspath(CWD / name / "Scripts" / "python.exe") | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any documentation for this assumption? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the same assumption we have in the extension. The docs here describe the "Scripts" or "bin" : https://docs.python.org/3/library/venv.html#creating-virtual-environments There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha. Maybe still add a one line comment linking the doc? I'm asking because it looks like we actually don't use that assumption in discovery and identification: vscode-python/src/client/pythonEnvironments/common/environmentManagers/simplevirtualenvs.ts Lines 13 to 31 in e26a1ed
and this is the first time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The purpose of the code here and in the discovery is different. In discovery we are tyring to find the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think the comment is misleading: // envFolder
// |__ pyvenv.cfg <--- check if this file exists
// |__ python <--- interpreterPath It seems to indicate |
||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||
return os.fspath(CWD / name / "bin" / "python") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def install_packages(venv_path: str) -> None: | ||||||||||||||||||||||||||||||||||||||||
if not is_installed("pip"): | ||||||||||||||||||||||||||||||||||||||||
raise VenvError("CREATE_VENV.PIP_NOT_FOUND") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
requirements = os.fspath(CWD / "requirements.txt") | ||||||||||||||||||||||||||||||||||||||||
pyproject = os.fspath(CWD / "pyproject.toml") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
run_process( | ||||||||||||||||||||||||||||||||||||||||
[venv_path, "-m", "pip", "install", "--upgrade", "pip"], | ||||||||||||||||||||||||||||||||||||||||
"CREATE_VENV.PIP_UPGRADE_FAILED", | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
if file_exists(requirements): | ||||||||||||||||||||||||||||||||||||||||
print(f"VENV_INSTALLING_REQUIREMENTS: {requirements}") | ||||||||||||||||||||||||||||||||||||||||
run_process( | ||||||||||||||||||||||||||||||||||||||||
[venv_path, "-m", "pip", "install", "-r", requirements], | ||||||||||||||||||||||||||||||||||||||||
"CREATE_VENV.PIP_FAILED_INSTALL_REQUIREMENTS", | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
elif file_exists(pyproject): | ||||||||||||||||||||||||||||||||||||||||
print(f"VENV_INSTALLING_PYPROJECT: {pyproject}") | ||||||||||||||||||||||||||||||||||||||||
run_process( | ||||||||||||||||||||||||||||||||||||||||
[venv_path, "-m", "pip", "install", "-e", ".[extras]"], | ||||||||||||||||||||||||||||||||||||||||
"CREATE_VENV.PIP_FAILED_INSTALL_PYPROJECT", | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def add_gitignore(name: str) -> None: | ||||||||||||||||||||||||||||||||||||||||
git_ignore = CWD / name / ".gitignore" | ||||||||||||||||||||||||||||||||||||||||
if not file_exists(git_ignore): | ||||||||||||||||||||||||||||||||||||||||
print("Creating: " + os.fspath(git_ignore)) | ||||||||||||||||||||||||||||||||||||||||
with open(git_ignore, "w") as f: | ||||||||||||||||||||||||||||||||||||||||
f.write("*") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
def main(argv: Optional[Sequence[str]] = None) -> None: | ||||||||||||||||||||||||||||||||||||||||
if argv is None: | ||||||||||||||||||||||||||||||||||||||||
argv = [] | ||||||||||||||||||||||||||||||||||||||||
args = parse_args(argv) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
if is_installed("venv"): | ||||||||||||||||||||||||||||||||||||||||
if not venv_exists(args.name): | ||||||||||||||||||||||||||||||||||||||||
run_process( | ||||||||||||||||||||||||||||||||||||||||
[sys.executable, "-m", "venv", args.name], | ||||||||||||||||||||||||||||||||||||||||
"CREATE_VENV.VENV_FAILED_CREATION", | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
if args.git_ignore: | ||||||||||||||||||||||||||||||||||||||||
add_gitignore(args.name) | ||||||||||||||||||||||||||||||||||||||||
venv_path = get_venv_path(args.name) | ||||||||||||||||||||||||||||||||||||||||
print(f"CREATED_VENV:{venv_path}") | ||||||||||||||||||||||||||||||||||||||||
if args.install: | ||||||||||||||||||||||||||||||||||||||||
install_packages(venv_path) | ||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||
raise VenvError("CREATE_VENV.VENV_NOT_FOUND") | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
if __name__ == "__main__": | ||||||||||||||||||||||||||||||||||||||||
main(sys.argv[1:]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. | ||
|
||
import importlib | ||
import sys | ||
|
||
import create_conda | ||
import pytest | ||
|
||
|
||
@pytest.mark.parametrize("env_exists", [True, False]) | ||
@pytest.mark.parametrize("git_ignore", [True, False]) | ||
@pytest.mark.parametrize("install", [True, False]) | ||
@pytest.mark.parametrize("python", [True, False]) | ||
def test_create_env(env_exists, git_ignore, install, python): | ||
importlib.reload(create_conda) | ||
create_conda.conda_env_exists = lambda _n: env_exists | ||
|
||
install_packages_called = False | ||
|
||
def install_packages(_name): | ||
nonlocal install_packages_called | ||
install_packages_called = True | ||
|
||
create_conda.install_packages = install_packages | ||
|
||
run_process_called = False | ||
|
||
def run_process(args, error_message): | ||
nonlocal run_process_called | ||
run_process_called = True | ||
version = ( | ||
"12345" if python else f"{sys.version_info.major}.{sys.version_info.minor}" | ||
) | ||
if not env_exists: | ||
assert args == [ | ||
sys.executable, | ||
"-m", | ||
"conda", | ||
"create", | ||
"--yes", | ||
"--prefix", | ||
create_conda.CONDA_ENV_NAME, | ||
f"python={version}", | ||
] | ||
assert error_message == "CREATE_CONDA.ENV_FAILED_CREATION" | ||
|
||
create_conda.run_process = run_process | ||
|
||
add_gitignore_called = False | ||
|
||
def add_gitignore(_name): | ||
nonlocal add_gitignore_called | ||
add_gitignore_called = True | ||
|
||
create_conda.add_gitignore = add_gitignore | ||
|
||
args = [] | ||
if git_ignore: | ||
args.append("--git-ignore") | ||
if install: | ||
args.append("--install") | ||
if python: | ||
args.extend(["--python", "12345"]) | ||
create_conda.main(args) | ||
assert install_packages_called == install | ||
|
||
# run_process is called when the venv does not exist | ||
assert run_process_called != env_exists | ||
|
||
# add_gitignore is called when new venv is created and git_ignore is True | ||
assert add_gitignore_called == (not env_exists and git_ignore) |
Uh oh!
There was an error while loading. Please reload this page.