Skip to content

Commit cafa348

Browse files
committed
refactor: rewrite and expect install
1 parent df23396 commit cafa348

File tree

6 files changed

+173
-128
lines changed

6 files changed

+173
-128
lines changed

.github/workflows/update-dependencies.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: "Run update: dependencies"
2222
run: python ./bin/update_dependencies.py
2323
- name: "Run update: python configs"
24-
run: python ./bin/update_dependencies.py
24+
run: python ./bin/update_pythons.py --inplace
2525
- name: Create Pull Request
2626
if: github.ref == 'refs/heads/master'
2727
uses: peter-evans/create-pull-request@v3

.pre-commit-config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ repos:
2121
hooks:
2222
- id: black
2323
files: ^bin/update_pythons.py$
24+
args: ["--line-length=120"]
2425

2526
- repo: https://github.com/pre-commit/mirrors-mypy
2627
rev: v0.790

bin/update_pythons.py

+116-113
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,40 @@
11
#!/usr/bin/env python3
22

3-
import sys
4-
from collections import defaultdict
53
from itertools import groupby
64
from pathlib import Path
7-
from typing import Dict, List, Tuple
5+
from typing import List
86

7+
import click
98
import requests
109
import toml
1110
from packaging.version import Version
1211

13-
if sys.version_info < (3, 8):
14-
from typing_extensions import TypedDict
15-
else:
16-
from typing import TypedDict
17-
18-
# Use pretty printing for debugging
19-
# from rich import print
20-
21-
22-
allow_prerelease = False
23-
24-
25-
class InlineArrayDictEncoder(toml.encoder.TomlEncoder):
26-
def dump_sections(self, o: dict, sup: str):
27-
if all(isinstance(a, list) for a in o.values()):
28-
val = ""
29-
for k, v in o.items():
30-
inner = ",\n ".join(self.dump_inline_table(d_i).strip() for d_i in v)
31-
val += f"{k} = [\n {inner},\n]\n"
32-
return val, self._dict()
33-
else:
34-
return super().dump_sections(o, sup)
35-
12+
from cibuildwheel.extra import InlineArrayDictEncoder
13+
from cibuildwheel.typing import PlatformName, TypedDict
3614

15+
# Looking up the dir instead of using utils.resources_dir
16+
# since we want to write to it.
3717
DIR = Path(__file__).parent.parent.resolve()
3818
RESOURCES_DIR = DIR / "cibuildwheel/resources"
3919

4020

41-
class ConfigWinCP(TypedDict):
21+
class AnyConfig(TypedDict):
4222
identifier: str
43-
version: str
23+
version: Version
24+
25+
26+
class ConfigWinCP(AnyConfig):
4427
arch: str
4528

4629

47-
class ConfigWinPP(TypedDict):
48-
identifier: str
49-
version: str
30+
class ConfigWinPP(AnyConfig):
5031
arch: str
5132
url: str
5233

5334

54-
def get_cpython_windows() -> Dict[str, List[Version]]:
35+
def get_cpython_windows() -> List[ConfigWinCP]:
36+
ARCH_DICT = {"32": "win32", "64": "win_amd64"}
37+
5538
response = requests.get("https://api.nuget.org/v3/index.json")
5639
response.raise_for_status()
5740
api_info = response.json()
@@ -60,120 +43,140 @@ def get_cpython_windows() -> Dict[str, List[Version]]:
6043
if resource["@type"] == "PackageBaseAddress/3.0.0":
6144
endpoint = resource["@id"]
6245

63-
cp_versions: Dict[str, List[Version]] = {"64": [], "32": []}
64-
for id, package in [("64", "python"), ("32", "pythonx86")]:
46+
items: List[ConfigWinCP] = []
47+
48+
for arch, package in [("64", "python"), ("32", "pythonx86")]:
6549
response = requests.get(f"{endpoint}{package}/index.json")
6650
response.raise_for_status()
6751
cp_info = response.json()
6852

6953
for version_str in cp_info["versions"]:
7054
version = Version(version_str)
55+
7156
if version.is_devrelease:
7257
continue
73-
if not allow_prerelease and version.is_prerelease:
74-
continue
75-
cp_versions[id].append(version)
76-
cp_versions[id].sort()
7758

78-
return cp_versions
59+
identifier = f"cp{version.major}{version.minor}-{ARCH_DICT[arch]}"
60+
61+
items.append(
62+
ConfigWinCP(
63+
identifier=identifier,
64+
version=version,
65+
arch=arch,
66+
)
67+
)
68+
return items
7969

8070

81-
def get_pypy_windows(
82-
plat_arch: str = "win32-x86",
83-
) -> Dict[str, List[Tuple[Version, str]]]:
71+
def get_pypy(platform: PlatformName) -> List[AnyConfig]:
8472

8573
response = requests.get("https://downloads.python.org/pypy/versions.json")
8674
response.raise_for_status()
87-
pp_realeases = response.json()
88-
pp_versions = defaultdict(list)
75+
pp_releases = response.json()
8976

90-
for pp_realease in pp_realeases:
91-
if pp_realease["pypy_version"] == "nightly":
92-
continue
93-
version = Version(pp_realease["pypy_version"])
94-
python_version = Version(pp_realease["python_version"])
95-
python_version_str = f"{python_version.major}.{python_version.minor}"
96-
url = None
97-
for file in pp_realease["files"]:
98-
if f"{file['platform']}-{file['arch']}" == plat_arch:
99-
url = file["download_url"]
100-
break
101-
if url:
102-
pp_versions[python_version_str].append((version, url))
77+
items: List[AnyConfig] = []
10378

104-
return pp_versions
79+
for pp_release in pp_releases:
10580

81+
if pp_release["pypy_version"] == "nightly":
82+
continue
83+
pypy_version = Version(pp_release["pypy_version"])
84+
if pypy_version.is_prerelease or pypy_version.is_devrelease:
85+
continue
10686

107-
# Debugging printout:
108-
# print(get_cpython_windows())
109-
# print()
110-
# print("[bold]Getting PyPy")
111-
# print(get_pypy_windows())
87+
version = Version(pp_release["python_version"])
88+
89+
for rf in pp_release["files"]:
90+
if platform == "windows":
91+
if rf["platform"] == "win32" and rf["arch"] == "x86":
92+
identifier = f"pp{version.major}{version.minor}-win32"
93+
items.append(
94+
ConfigWinPP(
95+
identifier=identifier,
96+
version=Version(f"{version.major}.{version.minor}"),
97+
arch="32",
98+
url=rf["download_url"],
99+
)
100+
)
101+
break
112102

113-
ARCH_DICT = {"32": "win32", "64": "win_amd64"}
103+
return items
114104

115105

116-
def build_ids_cp(in_dict: Dict[str, List[Version]]) -> List[ConfigWinCP]:
117-
items: List[ConfigWinCP] = []
118-
for arch in in_dict:
119-
for minor, grp in groupby(in_dict[arch], lambda v: v.minor):
120-
# Filter pre-releases, unless it's all pre-releases
121-
if not all(v.is_devrelease for v in grp):
122-
grp = filter(lambda v: not v.is_devrelease, grp)
106+
def sort_and_filter_configs(
107+
orig_items: List[AnyConfig],
108+
*,
109+
prereleases: bool = False,
110+
) -> List[AnyConfig]:
123111

124-
version = sorted(grp)[-1]
125-
identifier = f"cp3{minor}-{ARCH_DICT[arch]}"
112+
items: List[AnyConfig] = []
126113

127-
items.append(
128-
ConfigWinCP(
129-
identifier=identifier,
130-
version=str(version),
131-
arch=arch,
132-
)
133-
)
114+
# Groupby requires pre-grouped input
115+
orig_items = sorted(orig_items, key=lambda x: x["identifier"])
134116

135-
return items
117+
for _, grp in groupby(orig_items, lambda x: x["identifier"]):
118+
# Never select dev releases
119+
choices = list(filter(lambda x: not x["version"].is_devrelease, grp))
136120

121+
# Filter pre-releases, unless it's all pre-releases
122+
if not all(x["version"].is_prerelease for x in grp):
123+
choices = list(filter(lambda x: not x["version"].is_prerelease, choices))
137124

138-
def build_ids_pp(in_dict: Dict[str, List[Tuple[Version, str]]]) -> List[ConfigWinPP]:
139-
items: List[ConfigWinPP] = []
140-
for vers, matches in in_dict.items():
141-
vers_id = vers.replace(".", "")
142-
if not all(v[0].is_devrelease for v in matches):
143-
matches = list(filter(lambda v: not v[0].is_devrelease, matches))
125+
# Select the highest choice unless there are none
126+
_url = "url" # Needed for MyPy, see https://github.com/python/mypy/issues/9902
127+
choices = sorted(choices, key=lambda x: (x["version"], x.get(_url)))
128+
if not choices:
129+
continue
130+
best_choice = choices[-1]
144131

145-
version, url = sorted(matches, key=lambda v: v[0])[-1]
146-
identifier = f"pp{vers_id}-win32"
132+
# Only allow a pre-release if they are all prereleases, and we've asked for them
133+
if best_choice["version"].is_prerelease and not prereleases:
134+
continue
147135

148-
items.append(
149-
ConfigWinPP(
150-
identifier=identifier,
151-
version=vers,
152-
arch="32",
153-
url=url,
154-
)
155-
)
136+
items.append(best_choice)
156137

157-
return items
138+
return sorted(
139+
items,
140+
key=lambda x: (
141+
x["identifier"][:3],
142+
x["version"].minor,
143+
x["identifier"].split("-")[-1],
144+
),
145+
)
158146

159147

160-
windows_configs = [
161-
*build_ids_cp(get_cpython_windows()),
162-
*build_ids_pp(get_pypy_windows()),
148+
CLASSIC_WINDOWS: List[ConfigWinCP] = [
149+
{"identifier": "cp27-win32", "version": Version("2.7.18"), "arch": "32"},
150+
{"identifier": "cp27-win_amd64", "version": Version("2.7.18"), "arch": "64"},
163151
]
164152

165-
configs = toml.load(RESOURCES_DIR / "build-platforms.toml")
166-
origpy2 = list(
167-
filter(
168-
lambda c: c["identifier"].startswith("cp27"),
169-
configs["windows"]["python_configurations"],
170-
)
171-
)
172153

173-
items = origpy2 + windows_configs
174-
new_configs = sorted(items, key=lambda x: (x["identifier"][:3], x["identifier"][3:]))
154+
@click.command()
155+
@click.option("--inplace", is_flag=True)
156+
@click.option("--prereleases", is_flag=True)
157+
@click.option("--all", is_flag=True)
158+
def update_pythons(inplace: bool, prereleases: bool, all: bool) -> None:
159+
windows_configs: List[AnyConfig] = [
160+
*CLASSIC_WINDOWS,
161+
*get_cpython_windows(),
162+
*get_pypy("windows"),
163+
]
164+
165+
if not all:
166+
windows_configs = sort_and_filter_configs(
167+
windows_configs,
168+
prereleases=prereleases,
169+
)
170+
171+
configs = toml.load(RESOURCES_DIR / "build-platforms.toml")
172+
configs["windows"]["python_configurations"] = windows_configs
173+
174+
if inplace:
175+
with open(RESOURCES_DIR / "build-platforms.toml", "w") as f:
176+
toml.dump(configs, f, encoder=InlineArrayDictEncoder()) # type: ignore
177+
else:
178+
print(toml.dumps(configs, encoder=InlineArrayDictEncoder())) # type: ignore
175179

176-
configs["windows"]["python_configurations"] = new_configs
177180

178-
with open(RESOURCES_DIR / "build-platforms.toml", "w") as f:
179-
toml.dump(configs, f, encoder=InlineArrayDictEncoder()) # type: ignore
181+
if __name__ == "__main__":
182+
update_pythons()

cibuildwheel/extra.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
These are utilities for the `/bin` scripts, not for the `cibuildwheel` program.
3+
"""
4+
5+
from typing import Any, Dict
6+
7+
import toml.encoder
8+
from packaging.version import Version
9+
10+
11+
class InlineArrayDictEncoder(toml.encoder.TomlEncoder): # type: ignore
12+
def __init__(self) -> None:
13+
super().__init__()
14+
self.dump_funcs[Version] = lambda v: f'"{v}"'
15+
16+
def dump_sections(self, o: Dict[str, Any], sup: str) -> Any:
17+
if all(isinstance(a, list) for a in o.values()):
18+
val = ""
19+
for k, v in o.items():
20+
inner = ",\n ".join(self.dump_inline_table(d_i).strip() for d_i in v)
21+
val += f"{k} = [\n {inner},\n]\n"
22+
return val, self._dict()
23+
else:
24+
return super().dump_sections(o, sup)

cibuildwheel/typing.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
from typing import TYPE_CHECKING, NoReturn, Set, Union
55

66
if sys.version_info < (3, 8):
7-
from typing_extensions import Final, Literal
7+
from typing_extensions import Final, Literal, TypedDict
88
else:
9-
from typing import Final, Literal
9+
from typing import Final, Literal, TypedDict
10+
11+
12+
__all__ = ("Final", "Literal", "TypedDict", "Set", "Union", "PopenBytes", "PathOrStr", "PlatformName", "PLATFORMS", "assert_never")
1013

1114

1215
if TYPE_CHECKING:

0 commit comments

Comments
 (0)