66import  platform 
77import  re 
88import  shutil 
9- import  subprocess 
10- import  sys 
119import  tarfile 
1210import  tempfile 
1311import  urllib .request 
1412import  zipfile 
1513from  typing  import  Dict , List , Optional , Tuple 
1614
17- 
1815logger  =  logging .getLogger (__name__ )
1916logger .addHandler (logging .NullHandler ())
2017
@@ -37,81 +34,68 @@ def is_linux_x86() -> bool:
3734    )
3835
3936
40- ######################### 
41- # Cache directory helper 
42- ######################### 
43- 
44- APP_NAMESPACE  =  ["executorch" , "qnn" ]
45- 
46- 
47- def  _get_staging_dir (* parts : str ) ->  pathlib .Path :
48-     r""" 
49-     Return a cross-platform staging directory for staging SDKs/libraries. 
50- 
51-     - On Linux: 
52-         ~/.cache/executorch/qnn/<parts...> 
53-         (falls back to $HOME/.cache if $XDG_CACHE_HOME is unset) 
54- 
55-     - On Windows (not supported yet, but as placeholder): 
56-         %LOCALAPPDATA%\executorch\qnn\<parts...> 
57-         (falls back to $HOME/AppData/Local if %LOCALAPPDATA% is unset) 
58- 
59-     - Override: 
60-         If QNN_STAGING_DIR is set in the environment, that path is used instead. 
61- 
62-     Args: 
63-         parts (str): Subdirectories to append under the root staging dir. 
37+ import  subprocess 
6438
65-     Returns: 
66-         pathlib.Path: Fully qualified staging path. 
67-     """ 
68-     # Environment override wins 
69-     base  =  os .environ .get ("QNN_STAGING_DIR" )
70-     if  base :
71-         return  pathlib .Path (base ).joinpath (* parts )
72- 
73-     system  =  platform .system ().lower ()
74-     if  system  ==  "windows" :
75-         # On Windows, prefer %LOCALAPPDATA%, fallback to ~/AppData/Local 
76-         base  =  pathlib .Path (
77-             os .environ .get ("LOCALAPPDATA" , pathlib .Path .home () /  "AppData"  /  "Local" )
78-         )
79-     elif  is_linux_x86 ():
80-         # On Linux/Unix, prefer $XDG_CACHE_HOME, fallback to ~/.cache 
81-         base  =  pathlib .Path (
82-             os .environ .get ("XDG_CACHE_HOME" , pathlib .Path .home () /  ".cache" )
83-         )
84-     else :
85-         raise  ValueError (f"Unsupported platform: { system }  )
39+ MINIMUM_LIBC_VERSION  =  2.29 
8640
87-     return  base .joinpath (* APP_NAMESPACE , * parts )
41+ REQUIRED_LIBC_LIBS  =  [
42+     "/lib/x86_64-linux-gnu/libc.so.6" ,
43+     "/lib64/libc.so.6" ,
44+     "/lib/libc.so.6" ,
45+ ]
8846
8947
90- def  _atomic_download ( url :  str ,  dest :  pathlib . Path ) :
48+ def  check_glibc_exist_and_validate ()  ->   bool :
9149    """ 
92-     Download URL into dest atomically: 
93-       - Write to a temp file in the same dir 
94-       - Move into place if successful 
50+     Check if users have glibc installed. 
9551    """ 
96-     dest .parent .mkdir (parents = True , exist_ok = True )
97- 
98-     # Temp file in same dir (guarantees atomic rename) 
99-     with  tempfile .NamedTemporaryFile (dir = dest .parent , delete = False ) as  tmp :
100-         tmp_path  =  pathlib .Path (tmp .name )
101- 
102-     try :
103-         urllib .request .urlretrieve (url , tmp_path )
104-         tmp_path .replace (dest )  # atomic rename 
105-     except  Exception :
106-         # Clean up partial file on failure 
107-         if  tmp_path .exists ():
108-             tmp_path .unlink (missing_ok = True )
109-         raise 
52+     exists  =  False 
53+     for  path  in  REQUIRED_LIBC_LIBS :
54+         try :
55+             output  =  subprocess .check_output (
56+                 [path , "--version" ], stderr = subprocess .STDOUT 
57+             )
58+             output  =  output .decode ().split ("\n " )[0 ]
59+             logger .debug (f"[QNN] glibc version for path { path } { output }  )
60+             match  =  re .search (r"version (\d+\.\d+)" , output )
61+             if  match :
62+                 version  =  match .group (1 )
63+                 if  float (version ) >=  MINIMUM_LIBC_VERSION :
64+                     logger .debug (f"[QNN] glibc version is { version }  )
65+                     exists  =  True 
66+                     return  True 
67+                 else :
68+                     logger .error (
69+                         f"[QNN] glibc version is too low. The minimum libc version is { MINIMUM_LIBC_VERSION }  
70+                     )
71+             else :
72+                 logger .error ("[QNN] glibc version not found." )
11073
74+         except  Exception :
75+             continue 
11176
112- #################### 
113- # qnn sdk download management 
114- #################### 
77+     if  not  exists :
78+         logger .error (
79+             r"""" 
80+             [QNN] glibc not found or the version is too low. Please install glibc following the commands below. 
81+             Ubuntu/Debian: 
82+                 sudo apt update 
83+                 sudo apt install libc6 
84+ 
85+             Fedora/Red Hat: 
86+                 sudo dnf install glibc 
87+ 
88+             Arch Linux: 
89+                 sudo pacman -S glibc 
90+              
91+             Also please make sure the glibc version is >= MINIMUM_LIBC_VERSION. You can verify the glibc version by running the following command: 
92+             Option 1: 
93+                 ldd --version 
94+             Option 2: 
95+                 /path/to/libc.so.6 --version 
96+             """ 
97+         )
98+     return  exists 
11599
116100
117101def  _download_archive (url : str , archive_path : pathlib .Path ) ->  bool :
@@ -194,6 +178,9 @@ def _download_qnn_sdk(dst_folder=SDK_DIR) -> Optional[pathlib.Path]:
194178    if  not  is_linux_x86 ():
195179        logger .info ("[QNN] Skipping Qualcomm SDK (only supported on Linux x86)." )
196180        return  None 
181+     elif  not  check_glibc_exist_and_validate ():
182+         logger .info ("[QNN] Skipping Qualcomm SDK (glibc not found or version too old)." )
183+         return  None 
197184    else :
198185        logger .info ("[QNN] Downloading Qualcomm SDK for Linux x86" )
199186
@@ -254,136 +241,6 @@ def _extract_tar(archive_path: pathlib.Path, prefix: str, target_dir: pathlib.Pa
254241                    dst .write (src .read ())
255242
256243
257- #################### 
258- # libc management 
259- #################### 
260- 
261- GLIBC_VERSION  =  "2.34" 
262- GLIBC_REEXEC_GUARD  =  "QNN_GLIBC_REEXEC" 
263- MINIMUM_LIBC_VERSION  =  GLIBC_VERSION 
264- 
265- 
266- def  _get_glibc_libdir () ->  pathlib .Path :
267-     glibc_root  =  _get_staging_dir (f"glibc-{ GLIBC_VERSION }  )
268-     return  glibc_root  /  "lib" 
269- 
270- 
271- def  _parse_version (v : str ) ->  tuple [int , int ]:
272-     """Turn '2.34' → (2,34) so it can be compared.""" 
273-     parts  =  v .split ("." )
274-     return  int (parts [0 ]), int (parts [1 ]) if  len (parts ) >  1  else  0 
275- 
276- 
277- def  _current_glibc_version () ->  str :
278-     """Return system glibc version string (via ctypes).""" 
279-     try :
280-         libc  =  ctypes .CDLL ("libc.so.6" )
281-         func  =  libc .gnu_get_libc_version 
282-         func .restype  =  ctypes .c_char_p 
283-         return  func ().decode ()
284-     except  Exception  as  e :
285-         return  f"error:{ e }  
286- 
287- 
288- def  _resolve_glibc_loader () ->  pathlib .Path  |  None :
289-     """Return staged ld.so path if available.""" 
290-     for  p  in  [
291-         _get_glibc_libdir () /  f"ld-{ GLIBC_VERSION }  ,
292-         _get_glibc_libdir () /  "ld-linux-x86-64.so.2" ,
293-     ]:
294-         if  p .exists ():
295-             return  p 
296-     return  None 
297- 
298- 
299- def  _stage_prebuilt_glibc ():
300-     """Download + extract Fedora 35 glibc RPM into /tmp.""" 
301-     logger .info (">>> Staging prebuilt glibc-%s from Fedora 35 RPM" , GLIBC_VERSION )
302-     _get_glibc_libdir ().mkdir (parents = True , exist_ok = True )
303-     rpm_path  =  _get_staging_dir ("glibc" ) /  "glibc.rpm" 
304-     work_dir  =  _get_staging_dir ("glibc" ) /  "extracted" 
305-     rpm_url  =  (
306-         "https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/35/" 
307-         "Everything/x86_64/os/Packages/g/glibc-2.34-7.fc35.x86_64.rpm" 
308-     )
309- 
310-     rpm_path .parent .mkdir (parents = True , exist_ok = True )
311-     logger .info ("[glibc] Downloading %s -> %s" , rpm_url , rpm_path )
312-     try :
313-         urllib .request .urlretrieve (rpm_url , rpm_path )
314-     except  Exception  as  e :
315-         logger .error ("[glibc] Failed to download %s: %s" , rpm_url , e )
316-         raise 
317- 
318-     # Extract 
319-     if  work_dir .exists ():
320-         shutil .rmtree (work_dir )
321-     work_dir .mkdir (parents = True )
322-     subprocess .check_call (["bsdtar" , "-C" , str (work_dir ), "-xf" , str (rpm_path )])
323- 
324-     # Copy runtime libs 
325-     staged  =  [
326-         "ld-linux-x86-64.so.2" ,
327-         "libc.so.6" ,
328-         "libdl.so.2" ,
329-         "libpthread.so.0" ,
330-         "librt.so.1" ,
331-         "libm.so.6" ,
332-         "libutil.so.1" ,
333-     ]
334-     for  lib  in  staged :
335-         src  =  work_dir  /  "lib64"  /  lib 
336-         if  src .exists ():
337-             shutil .copy2 (src , _get_glibc_libdir () /  lib )
338-             logger .info ("[glibc] Staged %s" , lib )
339-         else :
340-             logger .warning ("[glibc] Missing %s in RPM" , lib )
341- 
342- 
343- def  ensure_glibc_minimum (min_version : str  =  GLIBC_VERSION ):
344-     """ 
345-     Ensure process runs under glibc >= min_version. 
346-     - If system glibc is new enough → skip. 
347-     - Else → stage Fedora RPM and re-exec under staged loader. 
348-     """ 
349-     current  =  _current_glibc_version ()
350-     logger .info ("[glibc] Current loaded glibc: %s" , current )
351- 
352-     # If system glibc already sufficient → skip everything 
353-     m  =  re .match (r"(\d+\.\d+)" , current )
354-     if  m  and  _parse_version (m .group (1 )) >=  _parse_version (min_version ):
355-         logger .info ("[glibc] System glibc >= %s, no staging needed." , min_version )
356-         return 
357- 
358-     # Avoid infinite loop 
359-     if  os .environ .get (GLIBC_REEXEC_GUARD ) ==  "1" :
360-         logger .info ("[glibc] Already re-exec'd once, continuing." )
361-         return 
362- 
363-     # Stage prebuilt if not already staged 
364-     if  not  (_get_glibc_libdir () /  "libc.so.6" ).exists ():
365-         _stage_prebuilt_glibc ()
366- 
367-     loader  =  _resolve_glibc_loader ()
368-     if  not  loader :
369-         logger .error ("[glibc] Loader not found in %s" , _get_glibc_libdir ())
370-         return 
371- 
372-     logger .info (
373-         "[glibc] Re-execing under loader %s with libdir %s" , loader , _get_glibc_libdir ()
374-     )
375-     os .environ [GLIBC_REEXEC_GUARD ] =  "1" 
376-     os .execv (
377-         str (loader ),
378-         [str (loader ), "--library-path" , str (_get_glibc_libdir ()), sys .executable ]
379-         +  sys .argv ,
380-     )
381- 
382- 
383- #################### 
384- # libc++ management 
385- #################### 
386- 
387244LLVM_VERSION  =  "14.0.0" 
388245LIBCXX_BASE_NAME  =  f"clang+llvm-{ LLVM_VERSION }  
389246LLVM_URL  =  f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{ LLVM_VERSION } { LIBCXX_BASE_NAME }  
@@ -401,17 +258,12 @@ def _stage_libcxx(target_dir: pathlib.Path):
401258        logger .info ("[libcxx] Already staged at %s, skipping download" , target_dir )
402259        return 
403260
404-     libcxx_stage  =  _get_staging_dir (f"libcxx-{ LLVM_VERSION }  )
405-     temp_tar  =  libcxx_stage  /  f"{ LIBCXX_BASE_NAME }  
406-     temp_extract  =  libcxx_stage  /  LIBCXX_BASE_NAME 
261+     temp_tar  =  pathlib .Path ("/tmp" ) /  f"{ LIBCXX_BASE_NAME }  
262+     temp_extract  =  pathlib .Path ("/tmp" ) /  LIBCXX_BASE_NAME 
407263
408264    if  not  temp_tar .exists ():
409265        logger .info ("[libcxx] Downloading %s" , LLVM_URL )
410-         _atomic_download (LLVM_URL , temp_tar )
411- 
412-     # Sanity check before extracting 
413-     if  not  temp_tar .exists () or  temp_tar .stat ().st_size  ==  0 :
414-         raise  FileNotFoundError (f"[libcxx] Tarball missing or empty: { temp_tar }  )
266+         urllib .request .urlretrieve (LLVM_URL , temp_tar )
415267
416268    logger .info ("[libcxx] Extracting %s" , temp_tar )
417269    with  tarfile .open (temp_tar , "r:xz" ) as  tar :
@@ -585,10 +437,8 @@ def install_qnn_sdk() -> bool:
585437    Returns: 
586438        True if both steps succeeded (or were already satisfied), else False. 
587439    """ 
588-     logger .info ("[QNN] Starting SDK installation" )
589- 
590-     # Make sure we’re running under >= 2.34 
591-     ensure_glibc_minimum (GLIBC_VERSION )
592- 
593-     # libc++ and QNN SDK setup 
594-     return  _ensure_libcxx_stack () and  _ensure_qnn_sdk_lib ()
440+     if  check_glibc_exist_and_validate ():
441+         if  _ensure_libcxx_stack ():
442+             if  _ensure_qnn_sdk_lib ():
443+                 return  True 
444+     return  False 
0 commit comments