1818from urllib .request import Request , urlopen
1919
2020GITHUB_API = "https://api.github.com"
21+ GITHUB_WEB = "https://github.com"
2122USER_AGENT = "codex-python-binary-fetcher"
2223ZSTD_TIMEOUT_SECONDS = 300
2324
@@ -59,10 +60,17 @@ def parse_args() -> argparse.Namespace:
5960def main () -> int :
6061 args = parse_args ()
6162 token = _read_optional_env ("GITHUB_TOKEN" )
62- release_assets = list_release_assets (args .repo , args .release_tag , token )
6363 dest_root = Path (args .dest_root ).resolve ()
6464 targets : list [str ] = list (dict .fromkeys (args .target_triple ))
65+ resolved_tag = resolve_release_tag (args .repo , args .release_tag , token )
66+
67+ release_assets : list [ReleaseAsset ] | None = None
6568 for target in targets :
69+ if try_install_direct_asset (args .repo , resolved_tag , target , dest_root , token ):
70+ continue
71+
72+ if release_assets is None :
73+ release_assets = list_release_assets (args .repo , resolved_tag , token )
6674 asset = select_asset_for_target (release_assets , target )
6775 if asset is None :
6876 raise RuntimeError (
@@ -82,10 +90,7 @@ def _read_optional_env(name: str) -> str | None:
8290
8391
8492def list_release_assets (repo : str , release_tag : str , token : str | None ) -> list [ReleaseAsset ]:
85- if release_tag == "latest" :
86- url = f"{ GITHUB_API } /repos/{ repo } /releases/latest"
87- else :
88- url = f"{ GITHUB_API } /repos/{ repo } /releases/tags/{ release_tag } "
93+ url = f"{ GITHUB_API } /repos/{ repo } /releases/tags/{ release_tag } "
8994 payload = github_json (url , token )
9095 assets = payload .get ("assets" )
9196 if not isinstance (assets , list ):
@@ -101,16 +106,33 @@ def list_release_assets(repo: str, release_tag: str, token: str | None) -> list[
101106 return release_assets
102107
103108
104- def select_asset_for_target (assets : list [ReleaseAsset ], target : str ) -> ReleaseAsset | None :
109+ def resolve_release_tag (repo : str , release_tag : str , token : str | None ) -> str :
110+ if release_tag != "latest" :
111+ return release_tag
112+
113+ url = f"{ GITHUB_WEB } /{ repo } /releases/latest"
114+ final_url = github_redirect_url (url , token )
115+ marker = "/releases/tag/"
116+ if marker not in final_url :
117+ raise RuntimeError (f"Could not resolve latest release tag from redirect URL: { final_url } " )
118+ return final_url .split (marker , 1 )[1 ]
119+
120+
121+ def candidate_asset_names (target : str ) -> list [str ]:
105122 prefix = f"codex-{ target } "
106- exact_candidates = [
123+ return [
107124 f"{ prefix } .tar.gz" ,
108125 f"{ prefix } .zip" ,
109126 f"{ prefix } .exe" ,
110127 f"{ prefix } " ,
111128 f"{ prefix } .zst" ,
112129 f"{ prefix } .exe.zst" ,
113130 ]
131+
132+
133+ def select_asset_for_target (assets : list [ReleaseAsset ], target : str ) -> ReleaseAsset | None :
134+ prefix = f"codex-{ target } "
135+ exact_candidates = candidate_asset_names (target )
114136 by_name = {asset .name : asset for asset in assets }
115137 for candidate in exact_candidates :
116138 if candidate in by_name :
@@ -123,6 +145,28 @@ def select_asset_for_target(assets: list[ReleaseAsset], target: str) -> ReleaseA
123145 return None
124146
125147
148+ def try_install_direct_asset (
149+ repo : str ,
150+ release_tag : str ,
151+ target : str ,
152+ dest_root : Path ,
153+ token : str | None ,
154+ ) -> bool :
155+ for asset_name in candidate_asset_names (target ):
156+ asset = ReleaseAsset (
157+ name = asset_name ,
158+ url = f"{ GITHUB_WEB } /{ repo } /releases/download/{ release_tag } /{ asset_name } " ,
159+ )
160+ try :
161+ install_asset (asset , target , dest_root , token )
162+ except RuntimeError as exc :
163+ if _is_not_found_download_error (exc ):
164+ continue
165+ raise
166+ return True
167+ return False
168+
169+
126170def install_asset (asset : ReleaseAsset , target : str , dest_root : Path , token : str | None ) -> None :
127171 with tempfile .TemporaryDirectory (prefix = f"codex-asset-{ target } -" ) as tmp :
128172 tmp_dir = Path (tmp )
@@ -227,6 +271,24 @@ def github_json(url: str, token: str | None) -> dict[str, Any]:
227271 return data
228272
229273
274+ def github_redirect_url (url : str , token : str | None ) -> str :
275+ _require_https_url (url )
276+ headers = {"User-Agent" : USER_AGENT }
277+ if token is not None :
278+ headers ["Authorization" ] = f"Bearer { token } "
279+ request = Request (url , headers = headers )
280+ try :
281+ with urlopen (request ) as response : # nosec B310
282+ return response .geturl ()
283+ except HTTPError as exc :
284+ body = exc .read ().decode ("utf-8" , errors = "replace" )
285+ raise RuntimeError (
286+ f"GitHub redirect request failed ({ exc .code } ) for { url } : { body } "
287+ ) from exc
288+ except URLError as exc :
289+ raise RuntimeError (f"Network error while requesting { url } : { exc } " ) from exc
290+
291+
230292def download (url : str , destination : Path , token : str | None ) -> None :
231293 _require_https_url (url )
232294 headers = {"User-Agent" : USER_AGENT }
@@ -249,5 +311,9 @@ def _require_https_url(url: str) -> None:
249311 raise RuntimeError (f"Refusing to fetch non-HTTPS URL: { url } " )
250312
251313
314+ def _is_not_found_download_error (exc : RuntimeError ) -> bool :
315+ return str (exc ).startswith ("Download failed (404)" )
316+
317+
252318if __name__ == "__main__" :
253319 raise SystemExit (main ())
0 commit comments