Skip to content

Commit ad91acf

Browse files
committed
Support for seed packages having dependencies
- bump setuptools/wheel - no OS dependent dependencies allowed Signed-off-by: Bernat Gabor <[email protected]>
1 parent b81a0af commit ad91acf

File tree

7 files changed

+87
-143
lines changed

7 files changed

+87
-143
lines changed

src/virtualenv/seed/embed/base_embed.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from six import add_metaclass
66

7+
from virtualenv.seed.wheels.embed import get_wheel_deps
78
from virtualenv.util.path import Path
89
from virtualenv.util.six import ensure_str, ensure_text
910

@@ -49,6 +50,13 @@ def distribution_to_versions(self):
4950
if getattr(self, "no_{}".format(distribution)) is False
5051
}
5152

53+
def get_expanded_distributions(self, creator):
54+
distributions = self.distribution_to_versions()
55+
for dist, value in list(distributions.items()):
56+
for dep in get_wheel_deps(dist, creator.interpreter.version_release_str):
57+
distributions[dep] = value
58+
return distributions
59+
5260
@classmethod
5361
def add_parser_arguments(cls, parser, interpreter, app_data):
5462
group = parser.add_mutually_exclusive_group()

src/virtualenv/seed/embed/pip_invoke.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def run(self, creator):
1818
if not self.enabled:
1919
return
2020
for_py_version = creator.interpreter.version_release_str
21-
with self.get_pip_install_cmd(creator.exe, for_py_version) as cmd:
21+
with self.get_pip_install_cmd(creator.exe, for_py_version, creator) as cmd:
2222
env = pip_wheel_env_run(self.extra_search_dir, self.app_data)
2323
self._execute(cmd, env)
2424

@@ -32,12 +32,12 @@ def _execute(cmd, env):
3232
return process
3333

3434
@contextmanager
35-
def get_pip_install_cmd(self, exe, for_py_version):
35+
def get_pip_install_cmd(self, exe, for_py_version, creator):
3636
cmd = [str(exe), "-m", "pip", "-q", "install", "--only-binary", ":all:", "--disable-pip-version-check"]
3737
if not self.download:
3838
cmd.append("--no-index")
3939
folders = set()
40-
for dist, version in self.distribution_to_versions().items():
40+
for dist, version in self.get_expanded_distributions(creator).items():
4141
wheel = get_wheel(
4242
distribution=dist,
4343
version=version,

src/virtualenv/seed/embed/via_app_data/via_app_data.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ def _get(distribution, version):
117117
with lock:
118118
name_to_whl[distribution] = result
119119

120+
distributions = self.get_expanded_distributions(creator)
120121
threads = list(
121-
Thread(target=_get, args=(distribution, version))
122-
for distribution, version in self.distribution_to_versions().items()
122+
Thread(target=_get, args=(distribution, version)) for distribution, version in distributions.items()
123123
)
124124
for thread in threads:
125125
thread.start()

src/virtualenv/seed/wheels/embed/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@
4747
},
4848
}
4949
MAX = "3.10"
50+
VERSION_DEPS = {
51+
"3.10": {"pip": [], "setuptools": [], "wheel": []},
52+
"3.9": {"pip": [], "setuptools": [], "wheel": []},
53+
"3.8": {"pip": [], "setuptools": [], "wheel": []},
54+
"3.7": {"pip": [], "setuptools": [], "wheel": []},
55+
"3.6": {"pip": [], "setuptools": [], "wheel": []},
56+
"3.5": {"pip": [], "setuptools": [], "wheel": []},
57+
"3.4": {"pip": [], "setuptools": [], "wheel": []},
58+
"2.7": {"pip": [], "setuptools": [], "wheel": []},
59+
}
5060

5161

5262
def get_embed_wheel(distribution, for_py_version):
@@ -57,6 +67,7 @@ def get_embed_wheel(distribution, for_py_version):
5767
__all__ = (
5868
"get_embed_wheel",
5969
"BUNDLE_SUPPORT",
70+
"VERSION_DEPS",
6071
"MAX",
6172
"BUNDLE_FOLDER",
6273
)

src/virtualenv/seed/wheels/periodic_update.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ def periodic_update(distribution, for_py_version, wheel, search_dirs, app_data,
4646
u_log_older_than_hour = now - u_log.completed > timedelta(hours=1) if u_log.completed is not None else False
4747
for _, group in groupby(u_log.versions, key=lambda v: v.wheel.version_tuple[0:2]):
4848
version = next(group) # use only latest patch version per minor, earlier assumed to be buggy
49-
if wheel is not None and Path(version.filename).name == wheel.name:
49+
updated_wheel = Wheel(app_data.house / version.filename)
50+
if wheel is not None and (
51+
Path(version.filename).name == wheel.name or wheel.version_tuple > updated_wheel.version_tuple
52+
):
5053
break
5154
if u_log.periodic is False or (u_log_older_than_hour and version.use(now)):
52-
updated_wheel = Wheel(app_data.house / version.filename)
5355
logging.debug("using %supdated wheel %s", "periodically " if updated_wheel else "", updated_wheel)
5456
wheel = updated_wheel
5557
break

tasks/update_embedded.py

Lines changed: 0 additions & 89 deletions
This file was deleted.

tasks/upgrade_wheels.py

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
import shutil
88
import subprocess
99
import sys
10-
from collections import OrderedDict, defaultdict
10+
from collections import defaultdict
11+
from concurrent.futures import as_completed
12+
from concurrent.futures.thread import ThreadPoolExecutor
1113
from pathlib import Path
1214
from tempfile import TemporaryDirectory
1315
from textwrap import dedent
14-
from threading import Thread
16+
from typing import Dict
1517

1618
STRICT = "UPGRADE_ADVISORY" not in os.environ
1719

@@ -20,43 +22,62 @@
2022
DEST = Path(__file__).resolve().parents[1] / "src" / "virtualenv" / "seed" / "wheels" / "embed"
2123

2224

23-
def download(ver, dest, package):
24-
subprocess.call(
25-
[
26-
sys.executable,
27-
"-m",
28-
"pip",
29-
"--disable-pip-version-check",
30-
"download",
31-
"--only-binary=:all:",
32-
"--python-version",
33-
ver,
34-
"-d",
35-
dest,
36-
package,
37-
],
38-
)
25+
def download(ver: str, into: Path):
26+
dest = into / ver
27+
dest.mkdir()
28+
cmd = [
29+
sys.executable,
30+
"-m",
31+
"pip",
32+
"--disable-pip-version-check",
33+
"download",
34+
"--only-binary=:all:",
35+
"--python-version",
36+
ver,
37+
]
38+
subprocess.check_call(cmd + ["-d", str(dest), *BUNDLED])
39+
deps = {}
40+
for pkg in BUNDLED:
41+
to_folder = dest / pkg
42+
to_folder.mkdir()
43+
try:
44+
subprocess.check_call(cmd + ["-d", str(to_folder), pkg])
45+
found = sorted(list(wheel.name.split("-")[0] for wheel in to_folder.iterdir()))
46+
found.remove(pkg)
47+
deps[pkg] = found
48+
finally:
49+
shutil.rmtree(to_folder)
50+
return ver, dest, deps
3951

4052

4153
def run():
54+
if sys.version_info < (3, 7):
55+
raise RuntimeError("requires python 3.7 or later")
4256
old_batch = {i.name for i in DEST.iterdir() if i.suffix == ".whl"}
4357
with TemporaryDirectory() as temp:
44-
temp_path = Path(temp)
45-
folders = {}
46-
targets = []
47-
for support in SUPPORT:
48-
support_ver = ".".join(str(i) for i in support)
49-
into = temp_path / support_ver
50-
into.mkdir()
51-
folders[into] = support_ver
52-
for package in BUNDLED:
53-
thread = Thread(target=download, args=(support_ver, str(into), package))
54-
targets.append(thread)
55-
thread.start()
56-
for thread in targets:
57-
thread.join()
58-
new_batch = {i.name: i for f in folders.keys() for i in Path(f).iterdir()}
59-
58+
temp = Path(temp)
59+
bundle_support: Dict[str, Dict[str, str]] = {}
60+
version_deps = {}
61+
new_batch = {}
62+
with ThreadPoolExecutor(max_workers=4) as executor:
63+
for future in as_completed({executor.submit(download, ".".join(str(i) for i in s), temp) for s in SUPPORT}):
64+
try:
65+
version, dest, deps = future.result()
66+
except Exception as exc:
67+
raise exc
68+
else:
69+
bundle = {}
70+
for wheel in dest.iterdir():
71+
name = wheel.name.split("-")[0]
72+
bundle[name] = wheel.name
73+
new_batch[wheel.name] = wheel
74+
wheels = {wheel.name.split("-")[0]: wheel.name for wheel in dest.iterdir()}
75+
wheels = {k: v for k, v in sorted(wheels.items())}
76+
bundle_support[version] = wheels
77+
version_deps[version] = deps
78+
sort_by_version = lambda i: tuple(int(j) for j in i[0].split(".")) # noqa
79+
bundle_support = {k: v for k, v in sorted(bundle_support.items(), key=sort_by_version, reverse=True)}
80+
version_deps = {k: v for k, v in sorted(version_deps.items(), key=sort_by_version, reverse=True)}
6081
new_packages = new_batch.keys() - old_batch
6182
remove_packages = old_batch - new_batch.keys()
6283

@@ -78,13 +99,6 @@ def run():
7899
for key, versions in removed.items():
79100
print("* removed embedded {} of {}".format(key, fmt_version(versions)))
80101

81-
support_table = OrderedDict((".".join(str(j) for j in i), list()) for i in SUPPORT)
82-
for package in sorted(new_batch.keys()):
83-
for folder, version in sorted(folders.items()):
84-
if (folder / package).exists():
85-
support_table[version].append(package)
86-
support_table = {k: OrderedDict((i.split("-")[0], i) for i in v) for k, v in support_table.items()}
87-
88102
msg = dedent(
89103
"""
90104
from __future__ import absolute_import, unicode_literals
@@ -93,8 +107,9 @@ def run():
93107
from virtualenv.util.path import Path
94108
95109
BUNDLE_FOLDER = Path(__file__).absolute().parent
96-
BUNDLE_SUPPORT = {{ {0} }}
110+
BUNDLE_SUPPORT = {0}
97111
MAX = {1}
112+
VERSION_DEPS = {2}
98113
99114
100115
def get_embed_wheel(distribution, for_py_version):
@@ -105,16 +120,13 @@ def get_embed_wheel(distribution, for_py_version):
105120
__all__ = (
106121
"get_embed_wheel",
107122
"BUNDLE_SUPPORT",
123+
"VERSION_DEPS",
108124
"MAX",
109125
"BUNDLE_FOLDER",
110126
)
111127
112128
""".format(
113-
",".join(
114-
"{!r}: {{ {} }}".format(v, ",".join("{!r}: {!r}".format(p, f) for p, f in l.items()))
115-
for v, l in support_table.items()
116-
),
117-
repr(next(iter(support_table.keys()))),
129+
repr(bundle_support), repr(next(iter(bundle_support.keys()))), repr(version_deps),
118130
),
119131
)
120132
dest_target = DEST / "__init__.py"

0 commit comments

Comments
 (0)