@@ -110,10 +110,11 @@ def open_issues_for_missing(packages: list[str]) -> dict[str, int]:
110110 return issues
111111
112112
113- def open_summary_issue (report : str , issue_map : dict [str , int ]) -> None :
114- """Open (or skip, if one already exists) a monthly summary issue linking
113+ def open_summary_issue (report : str , issue_map : dict [str , int ], title : str | None = None ) -> None :
114+ """Open (or skip, if one already exists) a summary issue linking
115115 to each per-package issue and notifying the maintainers."""
116- title = f"PyPI riscv64 check - { time .strftime ('%Y-%m' )} "
116+ if title is None :
117+ title = f"PyPI riscv64 check - { time .strftime ('%Y-%m' )} "
117118 if find_existing_issue (title ) is not None :
118119 print (f" [=] Summary issue for { title !r} already open, skipping" )
119120 return
@@ -175,11 +176,15 @@ def fetch_json(url: str) -> dict | list:
175176 return json .loads (r .read ())
176177
177178
178- def analyse_package (name : str , download_count : int ) -> dict | None :
179+ def analyse_package (
180+ name : str , download_count : int | None = None , require_binary : bool = True
181+ ) -> dict | None :
179182 """
180- Returns a result dict if the package ships at least one binary wheel,
181- otherwise None (pure-Python or fetch error).
182-
183+ Returns a result dict describing the package's wheel situation, or None on
184+ fetch error (or, when require_binary, if it ships no binary wheel at all —
185+ pure-Python packages need no riscv64 wheel and are dropped from the top-N
186+ binary-wheel report).
187+
183188 A binary wheel is any .whl file whose filename does NOT end with
184189 'none-any.whl' (the platform-independent tag).
185190 """
@@ -188,7 +193,7 @@ def analyse_package(name: str, download_count: int) -> dict | None:
188193 except Exception as exc :
189194 print (f" [WARN] Could not fetch { name } : { exc } " )
190195 return None
191-
196+
192197 wheel_files = [
193198 u ["filename" ]
194199 for u in info .get ("urls" , [])
@@ -197,19 +202,66 @@ def analyse_package(name: str, download_count: int) -> dict | None:
197202
198203 binary_wheels = [f for f in wheel_files if not f .endswith ("none-any.whl" )]
199204 if not binary_wheels :
200- return None # pure-Python or no wheels at all
205+ if require_binary :
206+ return None # pure-Python or no wheels at all
207+ return {
208+ "project" : name ,
209+ "download_count" : download_count ,
210+ "pure_python" : True ,
211+ "has_riscv64" : False ,
212+ "in_rise_registry" : None ,
213+ }
201214
202215 riscv64_wheels = [f for f in wheel_files if "riscv64" in f .lower ()]
203216 has_riscv64 = len (riscv64_wheels ) > 0
204217
205218 return {
206219 "project" : name ,
207220 "download_count" : download_count ,
221+ "pure_python" : False ,
208222 "has_riscv64" : has_riscv64 ,
209223 "in_rise_registry" : None if has_riscv64 else in_rise_registry (name ),
210224 }
211225
212226
227+ def check_package_list (names : list [str ], create_issues : bool ) -> None :
228+ """Check a maintainer-supplied package list: OK if it has a riscv64 wheel
229+ or is pure-Python (nothing to build), otherwise flagged as needing a port."""
230+ report_lines = [
231+ f"{ 'Package' :<35} { 'Status' :<40} " ,
232+ "-" * 76 ,
233+ ]
234+ needs_work = []
235+ for name in names :
236+ result = analyse_package (name , require_binary = False )
237+ if result is None :
238+ report_lines .append (f"{ name :<35} { 'ERROR (could not fetch from PyPI)' :<40} " )
239+ continue
240+
241+ if result ["has_riscv64" ]:
242+ status = "OK - riscv64 wheel on PyPI"
243+ elif result ["pure_python" ]:
244+ status = "OK - pure Python (py3-none-any)"
245+ elif result ["in_rise_registry" ]:
246+ status = "NEEDS PORT - already in RISE registry"
247+ else :
248+ status = "NEEDS PORT - no riscv64 wheel, not in registry"
249+ needs_work .append (name )
250+ report_lines .append (f"{ name :<35} { status :<40} " )
251+
252+ report_lines += ["" , f"{ len (needs_work )} /{ len (names )} package(s) need a riscv64 port and are untracked." ]
253+ report = "\n " .join (report_lines )
254+ print (report )
255+
256+ if create_issues and needs_work :
257+ print ()
258+ issue_map = open_issues_for_missing (needs_work )
259+ open_summary_issue (
260+ report , issue_map ,
261+ title = f"PyPI riscv64 check (custom list) - { time .strftime ('%Y-%m-%d' )} " ,
262+ )
263+
264+
213265def fmt_count (n : int ) -> str :
214266 """Format large numbers with M/B suffixes."""
215267 if n >= 1_000_000_000 :
@@ -234,8 +286,32 @@ def main():
234286 help = "Open a GitHub issue for each package missing riscv64 wheels "
235287 "upstream and in the RISE registry, unless one is already open"
236288 )
289+ parser .add_argument (
290+ "--packages" ,
291+ help = "Comma-separated package names to check instead of the top-N "
292+ "list (e.g. a maintainer-supplied audit list)"
293+ )
294+ parser .add_argument (
295+ "--packages-file" ,
296+ help = "Path to a file with one package name per line (# comments and "
297+ "blank lines ignored), instead of the top-N list"
298+ )
237299 args = parser .parse_args ()
238300
301+ if args .packages or args .packages_file :
302+ names = []
303+ if args .packages :
304+ names += [n .strip () for n in args .packages .split ("," ) if n .strip ()]
305+ if args .packages_file :
306+ with open (args .packages_file ) as f :
307+ names += [
308+ line .split ("#" , 1 )[0 ].strip ()
309+ for line in f
310+ if line .split ("#" , 1 )[0 ].strip ()
311+ ]
312+ check_package_list (names , create_issues = args .create_issues )
313+ return
314+
239315 print ("Fetching top-packages dataset …" )
240316 dataset = fetch_json (TOP_PACKAGES_URL )
241317 all_packages = dataset ["rows" ]
0 commit comments