22# Licensed under the MIT License.
33"""All the action we need during build"""
44
5- import hashlib
6- import io
75import json
86import os
97import pathlib
108import re
119import tempfile
12- import urllib .request as url_lib
1310import zipfile
1411
1512import nox # pylint: disable=import-error
13+ from nox .command import CommandFailed
14+
15+ # Keep this list explicit and ordered (oldest -> newest).
16+ # Update it whenever we bump supported Python versions.
17+ SUPPORTED_DEBUGPY_CPYTHONS = [
18+ "cp310" ,
19+ "cp311" ,
20+ "cp312" ,
21+ "cp313" ,
22+ "cp314" ,
23+ ]
24+
25+
26+ # Single source of truth for the debugpy version we bundle.
27+ # Update this when bumping debugpy (and update bundled/libs/debugpy accordingly).
28+ DEBUGPY_VERSION = "1.8.19"
29+
30+
31+ def _build_debugpy_wheel_requests (vsce_target : str , version : str ) -> list [dict ]:
32+ # Platform tags are pip --platform values; keep a small fallback list for resiliency.
33+ # Note: these are used only when we build per-target VSIXs (VSCETARGET is set in CI).
34+ if "darwin" in vsce_target :
35+ platforms = [
36+ "macosx_15_0_universal2" ,
37+ "macosx_14_0_universal2" ,
38+ "macosx_13_0_universal2" ,
39+ "macosx_12_0_universal2" ,
40+ ]
41+ return [
42+ {
43+ "version" : version ,
44+ "python_version" : cp .removeprefix ("cp" ),
45+ "implementation" : "cp" ,
46+ "abi" : cp ,
47+ "platforms" : platforms ,
48+ }
49+ for cp in SUPPORTED_DEBUGPY_CPYTHONS
50+ ]
51+
52+ if vsce_target == "win32-x64" :
53+ return [
54+ {
55+ "version" : version ,
56+ "python_version" : cp .removeprefix ("cp" ),
57+ "implementation" : "cp" ,
58+ "abi" : cp ,
59+ "platforms" : ["win_amd64" ],
60+ }
61+ for cp in SUPPORTED_DEBUGPY_CPYTHONS
62+ ]
63+
64+ if vsce_target == "linux-x64" :
65+ platforms = [
66+ "manylinux_2_34_x86_64" ,
67+ "manylinux_2_31_x86_64" ,
68+ "manylinux_2_28_x86_64" ,
69+ "manylinux_2_27_x86_64" ,
70+ "manylinux_2_17_x86_64" ,
71+ ]
72+ return [
73+ {
74+ "version" : version ,
75+ "python_version" : cp .removeprefix ("cp" ),
76+ "implementation" : "cp" ,
77+ "abi" : cp ,
78+ "platforms" : platforms ,
79+ }
80+ for cp in SUPPORTED_DEBUGPY_CPYTHONS
81+ ]
82+
83+ # Default/fallback: ensure we only download the pure-Python wheel (py2.py3-none-any).
84+ # This is used for targets that don't have compiled wheels (e.g., linux-arm64) and
85+ # for workflows that don't set VSCETARGET.
86+ return [
87+ {
88+ "version" : version ,
89+ # Intentionally omit pip targeting flags here.
90+ # Passing --python-version 3 makes pip treat it as Python 3.0, which
91+ # excludes debugpy (Requires-Python >= 3.8).
92+ "python_version" : None ,
93+ "implementation" : None ,
94+ "abi" : None ,
95+ "platforms" : [],
96+ }
97+ ]
1698
1799
18100@nox .session ()
@@ -60,54 +142,14 @@ def install_bundled_libs(session):
60142 )
61143 session .install ("packaging" )
62144
63- debugpy_info_json_path = pathlib .Path (__file__ ).parent / "debugpy_info.json"
64- debugpy_info = json .loads (debugpy_info_json_path .read_text (encoding = "utf-8" ))
65-
66145 target = os .environ .get ("VSCETARGET" , "" )
67146 print ("target:" , target )
68- if "darwin" in target :
69- wheels = debugpy_info ["macOS" ]
70- elif "win32-ia32" == target :
71- wheels = debugpy_info .get ("win32" , debugpy_info ["any" ])
72- elif "win32-x64" == target :
73- wheels = debugpy_info ["win64" ]
74- elif "linux-x64" == target :
75- wheels = debugpy_info ["linux" ]
76- else :
77- wheels = debugpy_info ["any" ]
78-
79- download_debugpy_via_pip (session , wheels )
80147
148+ requests = _build_debugpy_wheel_requests (target , DEBUGPY_VERSION )
149+ download_debugpy_via_pip (session , requests )
81150
82- def _parse_wheel_info (url : str ) -> dict :
83- """Parse version and platform info from a wheel URL.
84151
85- Example URL: .../debugpy-1.8.19-cp311-cp311-win_amd64.whl
86- Returns: {"version": "1.8.19", "py_ver": "311", "abi": "cp311", "platform": "win_amd64"}
87- """
88- filename = url .rsplit ("/" , 1 )[- 1 ]
89- # Wheel filename format: {name}-{version}-{python}-{abi}-{platform}.whl
90- match = re .match (r"debugpy-([^-]+)-cp(\d+)-([^-]+)-(.+)\.whl" , filename )
91- if match :
92- return {
93- "version" : match .group (1 ),
94- "py_ver" : match .group (2 ),
95- "abi" : match .group (3 ),
96- "platform" : match .group (4 ),
97- }
98- # Fallback for py2.py3-none-any wheels
99- match = re .match (r"debugpy-([^-]+)-py\d\.py\d-none-any\.whl" , filename )
100- if match :
101- return {
102- "version" : match .group (1 ),
103- "py_ver" : None ,
104- "abi" : "none" ,
105- "platform" : "any" ,
106- }
107- raise ValueError (f"Could not parse wheel filename: { filename } " )
108-
109-
110- def download_debugpy_via_pip (session : nox .Session , wheels : list ) -> None :
152+ def download_debugpy_via_pip (session : nox .Session , requests : list [dict ]) -> None :
111153 """Downloads debugpy wheels via pip and extracts them into bundled/libs.
112154
113155 Uses pip to download by package name, allowing pip to use configured
@@ -116,35 +158,59 @@ def download_debugpy_via_pip(session: nox.Session, wheels: list) -> None:
116158 libs_dir = pathlib .Path .cwd () / "bundled" / "libs"
117159 libs_dir .mkdir (parents = True , exist_ok = True )
118160
119- # Parse version and platform info from wheel URLs
120- parsed = [ _parse_wheel_info ( w [ "url" ]) for w in wheels ]
121- version = parsed [0 ]["version" ]
161+ if not requests :
162+ raise ValueError ( "No debugpy wheel requests were provided." )
163+ version = requests [0 ]["version" ]
122164
123165 with tempfile .TemporaryDirectory (prefix = "debugpy_wheels_" ) as tmp_dir :
124166 tmp_path = pathlib .Path (tmp_dir )
125167
126- for info in parsed :
127- args = [
168+ for req in requests :
169+ base_args = [
128170 "python" ,
129171 "-m" ,
130172 "pip" ,
131173 "download" ,
132- f"debugpy=={ version } " ,
174+ f"debugpy=={ req [ ' version' ] } " ,
133175 "--no-deps" ,
134176 "--only-binary" ,
135177 ":all:" ,
136178 "--dest" ,
137179 str (tmp_path ),
138180 ]
139- if info ["py_ver" ]:
140- # Platform-specific wheel
141- args .extend (["--python-version" , info ["py_ver" ]])
142- args .extend (["--implementation" , "cp" ])
143- args .extend (["--abi" , info ["abi" ]])
144- args .extend (["--platform" , info ["platform" ]])
145- # For none-any wheels, no platform args needed
146181
147- session .run (* args )
182+ python_version = req .get ("python_version" )
183+ implementation = req .get ("implementation" )
184+ abi = req .get ("abi" )
185+ platforms = req .get ("platforms" ) or []
186+
187+ if python_version is None and implementation is None and abi is None and not platforms :
188+ session .run (* base_args )
189+ continue
190+
191+ last_error = None
192+ for platform in platforms :
193+ args = base_args + [
194+ "--python-version" ,
195+ python_version ,
196+ "--implementation" ,
197+ implementation ,
198+ "--abi" ,
199+ abi ,
200+ "--platform" ,
201+ platform ,
202+ ]
203+
204+ try :
205+ session .run (* args )
206+ last_error = None
207+ break
208+ except CommandFailed as exc :
209+ last_error = exc
210+ continue
211+
212+ if last_error is not None :
213+ raise last_error
148214
149215 wheel_paths = sorted (tmp_path .glob ("debugpy-*.whl" ))
150216 if not wheel_paths :
@@ -160,22 +226,6 @@ def download_debugpy_via_pip(session: nox.Session, wheels: list) -> None:
160226 wheel .extract (zip_info .filename , libs_dir )
161227
162228
163- def download_url (values ):
164- for value in values :
165- with url_lib .urlopen (value ["url" ]) as response :
166- data = response .read ()
167- digest = value ["hash" ]["sha256" ]
168- if hashlib .new ("sha256" , data ).hexdigest () != digest :
169- raise ValueError (f"Failed hash verification for { value ['url' ]} ." )
170-
171- print ("Download: " , value ["url" ])
172- with zipfile .ZipFile (io .BytesIO (data ), "r" ) as wheel :
173- libs_dir = pathlib .Path .cwd () / "bundled" / "libs"
174- for zip_info in wheel .infolist ():
175- print ("\t " + zip_info .filename )
176- wheel .extract (zip_info .filename , libs_dir )
177-
178-
179229@nox .session ()
180230def update_build_number (session : nox .Session ) -> None :
181231 """Updates build number for the extension."""
@@ -197,52 +247,3 @@ def update_build_number(session: nox.Session) -> None:
197247 session .log (f"Updating version from { package_json ['version' ]} to { version } " )
198248 package_json ["version" ] = version
199249 package_json_path .write_text (json .dumps (package_json , indent = 4 ), encoding = "utf-8" )
200-
201-
202- def _get_pypi_package_data (package_name ):
203- json_uri = "https://pypi.org/pypi/{0}/json" .format (package_name )
204- # Response format: https://warehouse.readthedocs.io/api-reference/json/#project
205- # Release metadata format: https://github.com/pypa/interoperability-peps/blob/master/pep-0426-core-metadata.rst
206- with url_lib .urlopen (json_uri ) as response :
207- return json .loads (response .read ())
208-
209-
210- def _get_debugpy_info (version = "latest" , platform = "none-any" , cp = "cp311" ):
211- from packaging .version import parse as version_parser
212-
213- data = _get_pypi_package_data ("debugpy" )
214-
215- if version == "latest" :
216- use_version = max (data ["releases" ].keys (), key = version_parser )
217- else :
218- use_version = version
219-
220- return list (
221- {"url" : r ["url" ], "hash" : {"sha256" : r ["digests" ]["sha256" ]}}
222- for r in data ["releases" ][use_version ]
223- if f"{ cp } -{ platform } " in r ["url" ] or f"py3-{ platform } " in r ["url" ]
224- )[0 ]
225-
226-
227- @nox .session
228- def create_debugpy_json (session : nox .Session ):
229- platforms = [
230- ("macOS" , "macosx" ),
231- # ("win32", "win32"), # VS Code does not support 32-bit Windows anymore
232- ("win64" , "win_amd64" ),
233- ("linux" , "manylinux" ),
234- ("any" , "none-any" ),
235- ]
236- debugpy_info_json_path = pathlib .Path (__file__ ).parent / "debugpy_info.json"
237- debugpy_info = {}
238- for p , id in platforms :
239- # we typically have the latest 3 versions of debugpy compiled bits
240- downloads = []
241- for cp in ["cp310" , "cp311" , "cp312" ]:
242- data = _get_debugpy_info ("latest" , id , cp )
243- if not any (d ["url" ] == data ["url" ] for d in downloads ):
244- downloads .append (data )
245- debugpy_info [p ] = downloads
246- debugpy_info_json_path .write_text (
247- json .dumps (debugpy_info , indent = 4 ), encoding = "utf-8"
248- )
0 commit comments