2020]
2121
2222
23+ def log (message : str ) -> None :
24+ print (f"[setup-llvm] { message } " , flush = True )
25+
26+
2327def read_manifest (path : Path ) -> list [dict ]:
2428 with path .open ("r" , encoding = "utf-8" ) as handle :
2529 return json .load (handle )
2630
2731
28- def normalize_build_type (name : str ) -> str :
29- lowered = name .lower ()
30- if "debug" in lowered :
31- return "Debug"
32- return "Release"
33-
34-
3532def detect_platform () -> str :
3633 plat = sys .platform
3734 if plat .startswith ("win" ):
@@ -72,14 +69,46 @@ def sha256sum(path: Path) -> str:
7269
7370
7471def download (url : str , dest : Path , token : str | None ) -> None :
72+ log (f"Start download: { url } -> { dest } " )
7573 dest .parent .mkdir (parents = True , exist_ok = True )
7674 headers = {"User-Agent" : "clice-setup-llvm" }
7775 if token :
7876 headers ["Authorization" ] = f"Bearer { token } "
7977 request = Request (url , headers = headers )
8078 try :
8179 with urlopen (request ) as response , dest .open ("wb" ) as handle :
82- shutil .copyfileobj (response , handle )
80+ total_bytes = response .length
81+ if total_bytes is None :
82+ header_len = response .getheader ("Content-Length" )
83+ if header_len and header_len .isdigit ():
84+ total_bytes = int (header_len )
85+ downloaded = 0
86+ next_percent = 10
87+ next_unknown_mark = 10 * 1024 * 1024 # 10MB steps when size is unknown
88+ while True :
89+ chunk = response .read (1024 * 512 )
90+ if not chunk :
91+ break
92+ handle .write (chunk )
93+ downloaded += len (chunk )
94+ if total_bytes :
95+ percent = int (downloaded * 100 / total_bytes )
96+ while percent >= next_percent and next_percent <= 100 :
97+ log (
98+ f"Download progress: { next_percent } % "
99+ f"({ downloaded / 1024 / 1024 :.1f} MB/"
100+ f"{ total_bytes / 1024 / 1024 :.1f} MB)"
101+ )
102+ next_percent += 10
103+ else :
104+ if downloaded >= next_unknown_mark :
105+ log (
106+ f"Downloaded { downloaded / 1024 / 1024 :.1f} MB (size unknown)"
107+ )
108+ next_unknown_mark += 10 * 1024 * 1024
109+ if total_bytes and next_percent <= 100 :
110+ log ("Download progress: 100% (size verified by server)" )
111+ log (f"Finished download: { dest } ({ downloaded / 1024 / 1024 :.1f} MB)" )
83112 except HTTPError as err :
84113 raise RuntimeError (f"HTTP error { err .code } while downloading { url } " ) from err
85114 except URLError as err :
@@ -104,21 +133,35 @@ def ensure_download(
104133
105134
106135def extract_archive (archive : Path , dest_dir : Path ) -> None :
136+ log (f"Extracting { archive .name } to { dest_dir } " )
107137 dest_dir .mkdir (parents = True , exist_ok = True )
108138 name = archive .name .lower ()
109139 if name .endswith (".tar.xz" ) or name .endswith (".tar.gz" ) or name .endswith (".tar" ):
110140 with tarfile .open (archive , "r:*" ) as tar :
111141 tar .extractall (path = dest_dir )
112- return
113- if name .endswith (".7z" ):
114- seven_z = shutil .which ("7z" ) or shutil .which ("7zz" )
115- if not seven_z :
116- raise RuntimeError ("7z/7zz not found; required to extract .7z archives" )
117- subprocess .run ([seven_z , "x" , "-y" , str (archive ), f"-o{ dest_dir } " ], check = True )
142+ log ("Extraction complete" )
118143 return
119144 raise RuntimeError (f"Unsupported archive format: { archive } " )
120145
121146
147+ def flatten_install_dir (dest_dir : Path ) -> None :
148+ # Some archives add an extra root directory (llvm-install, build-install, etc.).
149+ for name in ("llvm-install" , "build-install" ):
150+ nested = dest_dir / name
151+ if not nested .is_dir ():
152+ continue
153+ log (f"Flattening nested install directory: { nested } " )
154+ for entry in nested .iterdir ():
155+ target = dest_dir / entry .name
156+ if target .exists ():
157+ raise RuntimeError (
158+ f"Cannot flatten { nested } : target already exists: { target } "
159+ )
160+ shutil .move (str (entry ), str (target ))
161+ nested .rmdir ()
162+ break
163+
164+
122165def parse_version_tuple (text : str ) -> tuple [int , ...]:
123166 digits = []
124167 current = ""
@@ -208,6 +251,7 @@ def ensure_private_headers(
208251 dest = work_dir / "include" / "clang" / rel
209252 dest .parent .mkdir (parents = True , exist_ok = True )
210253 url = f"https://raw.githubusercontent.com/llvm/llvm-project/{ commit } /clang/lib/{ rel } "
254+ log (f"Fetching private header: { url } " )
211255 download (url , dest , token )
212256
213257
@@ -223,43 +267,71 @@ def main() -> None:
223267 parser .add_argument ("--output" , required = True )
224268 args = parser .parse_args ()
225269
270+ log (
271+ "Args: "
272+ f"version={ args .version } , build_type={ args .build_type } , "
273+ f"binary_dir={ args .binary_dir } , install_path={ args .install_path or '(auto)' } , "
274+ f"enable_lto={ args .enable_lto } , offline={ args .offline } "
275+ )
226276 token = os .environ .get ("GH_TOKEN" ) or os .environ .get ("GITHUB_TOKEN" )
227- build_type = normalize_build_type ( args .build_type )
277+ build_type = args .build_type
228278 platform_name = detect_platform ()
279+ log (f"Platform detected: { platform_name } , normalized build type: { build_type } " )
229280 manifest = read_manifest (Path (args .manifest ))
230281
282+ binary_dir = Path (args .binary_dir ).resolve ()
283+ install_root = binary_dir / ".llvm"
284+
231285 install_path : Path | None = None
286+ needs_install = False
232287 if args .install_path :
233288 candidate = Path (args .install_path )
234289 if candidate .exists ():
235- install_path = candidate
290+ log ( f"Using provided LLVM install at { candidate } " )
236291 else :
237- raise RuntimeError (
238- f"Provided LLVM_INSTALL_PATH does not exist: { candidate } "
292+ log (
293+ f"Provided LLVM install path does not exist; will install to { candidate } "
239294 )
240-
241- if install_path is None :
295+ needs_install = True
296+ install_path = candidate
297+ else :
242298 detected = system_llvm_ok (args .version , build_type )
243299 if detected :
300+ log (f"Found suitable system LLVM at { detected } " )
244301 install_path = detected
245302
246- binary_dir = Path (args .binary_dir ).resolve ()
247- install_root = binary_dir / ".llvm"
248-
249303 artifact = None
250304 if install_path is None :
305+ needs_install = True
251306 artifact = pick_artifact (
252307 manifest , args .version , build_type , args .enable_lto , platform_name
253308 )
309+ log (f"Selected artifact: { artifact .get ('filename' )} for download" )
254310 filename = artifact ["filename" ]
255311 url_version = args .version .replace ("+" , "%2B" )
256312 url = f"https://github.com/clice-io/clice-llvm/releases/download/{ url_version } /{ filename } "
257313 download_path = binary_dir / filename
258314 ensure_download (url , download_path , artifact ["sha256" ], token )
259315 extract_archive (download_path , install_root )
316+ flatten_install_dir (install_root )
260317 install_path = install_root
318+ elif needs_install :
319+ artifact = pick_artifact (
320+ manifest , args .version , build_type , args .enable_lto , platform_name
321+ )
322+ log (f"Selected artifact: { artifact .get ('filename' )} for download" )
323+ filename = artifact ["filename" ]
324+ url_version = args .version .replace ("+" , "%2B" )
325+ url = f"https://github.com/clice-io/clice-llvm/releases/download/{ url_version } /{ filename } "
326+ download_path = binary_dir / filename
327+ ensure_download (url , download_path , artifact ["sha256" ], token )
328+ target_dir = install_path .resolve ()
329+ extract_archive (download_path , target_dir )
330+ flatten_install_dir (target_dir )
331+ install_path = target_dir
261332 else :
262333 install_path = install_path .resolve ()
334+ log (f"Using existing LLVM install at { install_path } " )
263335
264336 cmake_dir = install_path / "lib" / "cmake" / "llvm"
265337 ensure_private_headers (install_path , binary_dir , args .version , token , args .offline )
0 commit comments