Skip to content

Commit 154f5e0

Browse files
Merge
1 parent 69a54cf commit 154f5e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+7784
-17
lines changed

pythonFiles/create_venv.py

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import argparse
5+
import importlib.util as import_util
6+
import json
7+
import os
8+
import pathlib
9+
import subprocess
10+
import sys
11+
import urllib.request as url_lib
12+
from typing import List, Optional, Sequence, Union
13+
14+
VENV_NAME = ".venv"
15+
CWD = pathlib.Path.cwd()
16+
MICROVENV_SCRIPT_PATH = pathlib.Path(__file__).parent / "create_microvenv.py"
17+
18+
19+
class VenvError(Exception):
20+
pass
21+
22+
23+
def parse_args(argv: Sequence[str]) -> argparse.Namespace:
24+
parser = argparse.ArgumentParser()
25+
26+
parser.add_argument(
27+
"--requirements",
28+
action="append",
29+
default=[],
30+
help="Install additional dependencies into the virtual environment.",
31+
)
32+
33+
parser.add_argument(
34+
"--toml",
35+
action="store",
36+
default=None,
37+
help="Install additional dependencies from sources like `pyproject.toml` into the virtual environment.",
38+
)
39+
parser.add_argument(
40+
"--extras",
41+
action="append",
42+
default=[],
43+
help="Install specific package groups from `pyproject.toml` into the virtual environment.",
44+
)
45+
46+
parser.add_argument(
47+
"--git-ignore",
48+
action="store_true",
49+
default=False,
50+
help="Add .gitignore to the newly created virtual environment.",
51+
)
52+
parser.add_argument(
53+
"--name",
54+
default=VENV_NAME,
55+
type=str,
56+
help="Name of the virtual environment.",
57+
metavar="NAME",
58+
action="store",
59+
)
60+
parser.add_argument(
61+
"--stdin",
62+
action="store_true",
63+
default=False,
64+
help="Read arguments from stdin.",
65+
)
66+
return parser.parse_args(argv)
67+
68+
69+
def is_installed(module: str) -> bool:
70+
return import_util.find_spec(module) is not None
71+
72+
73+
def file_exists(path: Union[str, pathlib.PurePath]) -> bool:
74+
return os.path.exists(path)
75+
76+
77+
def venv_exists(name: str) -> bool:
78+
return os.path.exists(CWD / name) and file_exists(get_venv_path(name))
79+
80+
81+
def run_process(args: Sequence[str], error_message: str) -> None:
82+
try:
83+
print("Running: " + " ".join(args))
84+
subprocess.run(args, cwd=os.getcwd(), check=True)
85+
except subprocess.CalledProcessError:
86+
raise VenvError(error_message)
87+
88+
89+
def get_venv_path(name: str) -> str:
90+
# See `venv` doc here for more details on binary location:
91+
# https://docs.python.org/3/library/venv.html#creating-virtual-environments
92+
if sys.platform == "win32":
93+
return os.fspath(CWD / name / "Scripts" / "python.exe")
94+
else:
95+
return os.fspath(CWD / name / "bin" / "python")
96+
97+
98+
def install_requirements(venv_path: str, requirements: List[str]) -> None:
99+
if not requirements:
100+
return
101+
102+
for requirement in requirements:
103+
print(f"VENV_INSTALLING_REQUIREMENTS: {requirement}")
104+
run_process(
105+
[venv_path, "-m", "pip", "install", "-r", requirement],
106+
"CREATE_VENV.PIP_FAILED_INSTALL_REQUIREMENTS",
107+
)
108+
print("CREATE_VENV.PIP_INSTALLED_REQUIREMENTS")
109+
110+
111+
def install_toml(venv_path: str, extras: List[str]) -> None:
112+
args = "." if len(extras) == 0 else f".[{','.join(extras)}]"
113+
run_process(
114+
[venv_path, "-m", "pip", "install", "-e", args],
115+
"CREATE_VENV.PIP_FAILED_INSTALL_PYPROJECT",
116+
)
117+
print("CREATE_VENV.PIP_INSTALLED_PYPROJECT")
118+
119+
120+
def upgrade_pip(venv_path: str) -> None:
121+
print("CREATE_VENV.UPGRADING_PIP")
122+
run_process(
123+
[venv_path, "-m", "pip", "install", "--upgrade", "pip"],
124+
"CREATE_VENV.UPGRADE_PIP_FAILED",
125+
)
126+
print("CREATE_VENV.UPGRADED_PIP")
127+
128+
129+
def add_gitignore(name: str) -> None:
130+
git_ignore = CWD / name / ".gitignore"
131+
if not file_exists(git_ignore):
132+
print("Creating: " + os.fspath(git_ignore))
133+
with open(git_ignore, "w") as f:
134+
f.write("*")
135+
136+
137+
def download_pip_pyz(name: str):
138+
url = "https://bootstrap.pypa.io/pip/pip.pyz"
139+
print("CREATE_VENV.DOWNLOADING_PIP")
140+
141+
try:
142+
with url_lib.urlopen(url) as response:
143+
pip_pyz_path = os.fspath(CWD / name / "pip.pyz")
144+
with open(pip_pyz_path, "wb") as out_file:
145+
data = response.read()
146+
out_file.write(data)
147+
out_file.flush()
148+
except Exception:
149+
raise VenvError("CREATE_VENV.DOWNLOAD_PIP_FAILED")
150+
151+
152+
def install_pip(name: str):
153+
pip_pyz_path = os.fspath(CWD / name / "pip.pyz")
154+
executable = get_venv_path(name)
155+
print("CREATE_VENV.INSTALLING_PIP")
156+
run_process(
157+
[executable, pip_pyz_path, "install", "pip"],
158+
"CREATE_VENV.INSTALL_PIP_FAILED",
159+
)
160+
161+
162+
def get_requirements_from_args(args: argparse.Namespace) -> List[str]:
163+
requirements = []
164+
if args.stdin:
165+
data = json.loads(sys.stdin.read())
166+
requirements = data.get("requirements", [])
167+
if args.requirements:
168+
requirements.extend(args.requirements)
169+
return requirements
170+
171+
172+
def main(argv: Optional[Sequence[str]] = None) -> None:
173+
if argv is None:
174+
argv = []
175+
args = parse_args(argv)
176+
177+
use_micro_venv = False
178+
venv_installed = is_installed("venv")
179+
pip_installed = is_installed("pip")
180+
ensure_pip_installed = is_installed("ensurepip")
181+
distutils_installed = is_installed("distutils")
182+
183+
if not venv_installed:
184+
if sys.platform == "win32":
185+
raise VenvError("CREATE_VENV.VENV_NOT_FOUND")
186+
else:
187+
use_micro_venv = True
188+
if not distutils_installed:
189+
print("Install `python3-distutils` package or equivalent for your OS.")
190+
print("On Debian/Ubuntu: `sudo apt install python3-distutils`")
191+
raise VenvError("CREATE_VENV.DISTUTILS_NOT_INSTALLED")
192+
193+
if venv_exists(args.name):
194+
# A virtual environment with same name exists.
195+
# We will use the existing virtual environment.
196+
venv_path = get_venv_path(args.name)
197+
print(f"EXISTING_VENV:{venv_path}")
198+
else:
199+
if use_micro_venv:
200+
# `venv` was not found but on this platform we can use `microvenv`
201+
run_process(
202+
[
203+
sys.executable,
204+
os.fspath(MICROVENV_SCRIPT_PATH),
205+
"--name",
206+
args.name,
207+
],
208+
"CREATE_VENV.MICROVENV_FAILED_CREATION",
209+
)
210+
elif not pip_installed or not ensure_pip_installed:
211+
# `venv` was found but `pip` or `ensurepip` was not found.
212+
# We create a venv without `pip` in it. We will later install `pip`.
213+
run_process(
214+
[sys.executable, "-m", "venv", "--without-pip", args.name],
215+
"CREATE_VENV.VENV_FAILED_CREATION",
216+
)
217+
else:
218+
# Both `venv` and `pip` were found. So create a .venv normally
219+
run_process(
220+
[sys.executable, "-m", "venv", args.name],
221+
"CREATE_VENV.VENV_FAILED_CREATION",
222+
)
223+
224+
venv_path = get_venv_path(args.name)
225+
print(f"CREATED_VENV:{venv_path}")
226+
227+
if args.git_ignore:
228+
add_gitignore(args.name)
229+
230+
# At this point we have a .venv. Now we handle installing `pip`.
231+
if pip_installed and ensure_pip_installed:
232+
# We upgrade pip if it is already installed.
233+
upgrade_pip(venv_path)
234+
else:
235+
# `pip` was not found, so we download it and install it.
236+
download_pip_pyz(args.name)
237+
install_pip(args.name)
238+
239+
requirements = get_requirements_from_args(args)
240+
if requirements:
241+
print(f"VENV_INSTALLING_REQUIREMENTS: {requirements}")
242+
install_requirements(venv_path, requirements)
243+
244+
if args.toml:
245+
print(f"VENV_INSTALLING_PYPROJECT: {args.toml}")
246+
install_toml(venv_path, args.extras)
247+
248+
249+
if __name__ == "__main__":
250+
main(sys.argv[1:])

pythonFiles/install_debugpy.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import io
5+
import json
6+
import os
7+
import urllib.request as url_lib
8+
import zipfile
9+
10+
from packaging.version import parse as version_parser
11+
12+
EXTENSION_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
13+
DEBUGGER_DEST = os.path.join(EXTENSION_ROOT, "pythonFiles", "lib", "python")
14+
DEBUGGER_PACKAGE = "debugpy"
15+
DEBUGGER_PYTHON_ABI_VERSIONS = ("cp310",)
16+
DEBUGGER_VERSION = "1.6.7" # can also be "latest"
17+
18+
19+
def _contains(s, parts=()):
20+
return any(p in s for p in parts)
21+
22+
23+
def _get_package_data():
24+
json_uri = "https://pypi.org/pypi/{0}/json".format(DEBUGGER_PACKAGE)
25+
# Response format: https://warehouse.readthedocs.io/api-reference/json/#project
26+
# Release metadata format: https://github.com/pypa/interoperability-peps/blob/master/pep-0426-core-metadata.rst
27+
with url_lib.urlopen(json_uri) as response:
28+
return json.loads(response.read())
29+
30+
31+
def _get_debugger_wheel_urls(data, version):
32+
return list(
33+
r["url"]
34+
for r in data["releases"][version]
35+
if _contains(r["url"], DEBUGGER_PYTHON_ABI_VERSIONS)
36+
)
37+
38+
39+
def _download_and_extract(root, url, version):
40+
root = os.getcwd() if root is None or root == "." else root
41+
print(url)
42+
with url_lib.urlopen(url) as response:
43+
data = response.read()
44+
with zipfile.ZipFile(io.BytesIO(data), "r") as wheel:
45+
for zip_info in wheel.infolist():
46+
# Ignore dist info since we are merging multiple wheels
47+
if ".dist-info/" in zip_info.filename:
48+
continue
49+
print("\t" + zip_info.filename)
50+
wheel.extract(zip_info.filename, root)
51+
52+
53+
def main(root):
54+
data = _get_package_data()
55+
56+
if DEBUGGER_VERSION == "latest":
57+
use_version = max(data["releases"].keys(), key=version_parser)
58+
else:
59+
use_version = DEBUGGER_VERSION
60+
61+
for url in _get_debugger_wheel_urls(data, use_version):
62+
_download_and_extract(root, url, use_version)
63+
64+
65+
if __name__ == "__main__":
66+
main(DEBUGGER_DEST)

0 commit comments

Comments
 (0)