Skip to content

Commit a6cd6ee

Browse files
committed
fix error and add libcxx
1 parent 2387796 commit a6cd6ee

File tree

4 files changed

+288
-139
lines changed

4 files changed

+288
-139
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ cmake-android-out/
1616
cmake-ios-out/
1717
cmake-out*
1818
cmake-out-android/
19+
build-android/
20+
build-x86/
1921
dist/
2022
ethos-u-scratch/
2123
executorch.egg-info

backends/qualcomm/__init__.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,46 @@
1+
import ctypes
12
import os
23
import pathlib
34

4-
from .scripts.download_qnn_sdk import _download_qnn_sdk, SDK_DIR
5+
from .scripts.download_qnn_sdk import _download_qnn_sdk, _load_libcxx_libs, SDK_DIR
56

6-
# -----------------------------------------------------------------------------
7-
# Main SDK setup
8-
# -----------------------------------------------------------------------------
7+
LLVM_VERSION = "14.0.0"
8+
PKG_ROOT = pathlib.Path(__file__).parent.parent
9+
LIBCXX_DIR = PKG_ROOT / "sdk" / f"libcxx-{LLVM_VERSION}"
10+
11+
# --- Qualcomm SDK handling ---
912
qnn_root = os.environ.get("QNN_SDK_ROOT")
1013
if qnn_root:
11-
SDK_DIR = pathlib.Path(qnn_root)
14+
QNN_SDK_DIR = pathlib.Path(qnn_root)
1215
else:
13-
if not SDK_DIR.exists():
16+
QNN_SDK_DIR = PKG_ROOT / "sdk" / "qnn"
17+
if not QNN_SDK_DIR.exists():
1418
print("Qualcomm SDK not found. Downloading...")
1519
_download_qnn_sdk()
16-
os.environ["QNN_SDK_ROOT"] = str(SDK_DIR)
20+
21+
os.environ["QNN_SDK_ROOT"] = str(QNN_SDK_DIR)
22+
23+
# Load QNN library
24+
qnn_lib = QNN_SDK_DIR / "lib" / "x86_64-linux-clang" / "libQnnHtp.so"
25+
try:
26+
ctypes.CDLL(str(qnn_lib), mode=ctypes.RTLD_GLOBAL)
27+
print(f"Loaded QNN library from {qnn_lib}")
28+
except OSError as e:
29+
print(f"[ERROR] Failed to load QNN library at {qnn_lib}: {e}")
30+
raise
31+
32+
# --- libc++ handling ---
33+
if LIBCXX_DIR.exists():
34+
include_path = LIBCXX_DIR / "include"
35+
lib_path = LIBCXX_DIR / "lib"
36+
37+
# Prepend paths to environment
38+
os.environ["CPLUS_INCLUDE_PATH"] = (
39+
f"{include_path}:{os.environ.get('CPLUS_INCLUDE_PATH','')}"
40+
)
41+
os.environ["LD_LIBRARY_PATH"] = f"{lib_path}:{os.environ.get('LD_LIBRARY_PATH','')}"
42+
os.environ["LIBRARY_PATH"] = f"{lib_path}:{os.environ.get('LIBRARY_PATH','')}"
43+
44+
_load_libcxx_libs(lib_path)
45+
else:
46+
print(f"libc++ not found at {LIBCXX_DIR}, please check installation.")
Lines changed: 173 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,228 @@
1+
# Add these imports for additional logging
2+
import ctypes
3+
import os
14
import pathlib
25
import platform
3-
import sys
6+
import shutil
47
import tarfile
58
import tempfile
69
import urllib.request
7-
import zipfile
10+
from typing import Optional
11+
12+
SDK_DIR = pathlib.Path(__file__).parent.parent / "sdk" / "qnn"
13+
PKG_ROOT = pathlib.Path(__file__).parent
814

915

1016
def is_linux_x86() -> bool:
1117
"""
12-
Check if the current system is Linux running on an x86 architecture.
18+
Check if the current platform is Linux x86_64.
1319
1420
Returns:
15-
bool: True if the system is Linux and the architecture is one of
16-
x86_64, i386, or i686. False otherwise.
21+
bool: True if the system is Linux x86_64, False otherwise.
1722
"""
18-
return sys.platform.startswith("linux") and platform.machine() in {
19-
"x86_64",
20-
"i386",
21-
"i686",
22-
}
23-
23+
system = platform.system().lower()
24+
machine = platform.machine().lower()
2425

25-
SDK_DIR = pathlib.Path(__file__).parent / "sdk"
26+
return system == "linux" and machine in ("x86_64", "amd64", "i386", "i686")
2627

2728

28-
def _download_qnn_sdk() -> pathlib.Path:
29+
def _download_qnn_sdk() -> Optional[pathlib.Path]:
2930
"""
30-
Download and extract the Qualcomm SDK from the given URL into target_dir.
31-
32-
Args:
33-
url (str): The URL of the archive (.zip, .tar.gz, or .tgz).
34-
prefix_to_strip (str): Top-level directory inside the archive to strip
35-
from extracted file paths.
36-
target_dir (pathlib.Path): Directory to extract the SDK into.
31+
Download and extract the Qualcomm SDK into SDK_DIR.
3732
3833
Notes:
3934
- Only runs on Linux x86 platforms. Skips otherwise.
40-
- Creates the target_dir if it does not exist.
4135
"""
42-
# Default path where the Qualcomm SDK will be installed (under the script directory).
43-
44-
# URL to download the Qualcomm AI Runtime SDK archive.
36+
print("Downloading Qualcomm SDK...")
4537
qairt_url = (
4638
"https://softwarecenter.qualcomm.com/api/download/software/sdks/"
4739
"Qualcomm_AI_Runtime_Community/All/2.34.0.250424/v2.34.0.250424.zip"
4840
)
49-
50-
# Top-level directory inside the SDK archive to extract.
5141
qairt_content_dir = "qairt/2.34.0.250424"
5242

5343
if not is_linux_x86():
5444
print("Skipping Qualcomm SDK (only supported on Linux x86).")
55-
return
45+
return None
5646

5747
SDK_DIR.mkdir(parents=True, exist_ok=True)
48+
print(f"SDK_DIR is {SDK_DIR}, exists: {SDK_DIR.exists()}")
49+
print(f"Current working directory: {os.getcwd()}")
5850

5951
with tempfile.TemporaryDirectory() as tmpdir:
6052
archive_path = pathlib.Path(tmpdir) / pathlib.Path(qairt_url).name
53+
print(f"Temporary directory: {tmpdir}")
54+
print(f"Archive will be saved to: {archive_path}")
6155

6256
print(f"Downloading Qualcomm SDK from {qairt_url}...")
63-
urllib.request.urlretrieve(qairt_url, archive_path)
57+
try:
58+
# Use urlretrieve with a reporthook to show progress
59+
def report_progress(block_num, block_size, total_size):
60+
downloaded = block_num * block_size
61+
percent = downloaded / total_size * 100
62+
print(
63+
f"Downloaded: {downloaded}/{total_size} bytes ({percent:.2f}%)",
64+
end="\r",
65+
)
66+
67+
urllib.request.urlretrieve(qairt_url, archive_path, report_progress)
68+
print("\nDownload completed!")
69+
70+
# Check if file was downloaded successfully
71+
if archive_path.exists():
72+
file_size = archive_path.stat().st_size
73+
print(f"Downloaded file size: {file_size} bytes")
74+
if file_size == 0:
75+
print("WARNING: Downloaded file is empty!")
76+
else:
77+
print("ERROR: File was not downloaded!")
78+
return None
6479

80+
except Exception as e:
81+
print(f"Error during download: {e}")
82+
return None
83+
84+
# Check extraction method
6585
if qairt_url.endswith(".zip"):
86+
print("Extracting ZIP archive...")
6687
_extract_zip(archive_path, qairt_content_dir, SDK_DIR)
6788
elif qairt_url.endswith((".tar.gz", ".tgz")):
89+
print("Extracting TAR archive...")
6890
_extract_tar(archive_path, qairt_content_dir, SDK_DIR)
6991
else:
7092
raise ValueError(f"Unsupported archive format: {qairt_url}")
7193

94+
# Verify extraction
95+
print(f"Verifying extraction to {SDK_DIR}")
96+
if SDK_DIR.exists():
97+
print(f"SDK directory exists. Contents:")
98+
for item in SDK_DIR.iterdir():
99+
print(f" {item.name}")
100+
else:
101+
print("ERROR: SDK directory was not created!")
102+
72103
print(f"Qualcomm SDK extracted to {SDK_DIR}")
73104

74105
return SDK_DIR
75106

76107

77-
def _extract_zip(archive_path: pathlib.Path, prefix: str, target_dir: pathlib.Path):
108+
# You might also want to add detailed logging to your extraction functions
109+
def _extract_zip(archive_path, content_dir, target_dir):
110+
print(f"Extracting {archive_path} to {target_dir}")
111+
print(f"Looking for content in subdirectory: {content_dir}")
112+
113+
# Add your zip extraction code here, with additional logging
114+
# For example:
115+
import zipfile
116+
117+
with zipfile.ZipFile(archive_path, "r") as zip_ref:
118+
# List all files in the archive
119+
print("Files in archive:")
120+
for file in zip_ref.namelist():
121+
print(f" {file}")
122+
123+
# Extract only the specific content directory
124+
for file in zip_ref.namelist():
125+
if file.startswith(content_dir):
126+
# Extract with path relative to content_dir
127+
relative_path = os.path.relpath(file, content_dir)
128+
if relative_path == ".":
129+
continue # Skip the directory entry itself
130+
target_path = target_dir / relative_path
131+
if file.endswith("/"):
132+
# Create directory
133+
target_path.mkdir(parents=True, exist_ok=True)
134+
else:
135+
# Extract file
136+
with zip_ref.open(file) as source, open(
137+
target_path, "wb"
138+
) as target:
139+
shutil.copyfileobj(source, target)
140+
print(f"Extracted: {relative_path}")
141+
142+
143+
LLVM_VERSION = "14.0.0"
144+
LIBCXX_LIB_DIR = (
145+
PKG_ROOT / "executorch" / "backends" / "qualcomm" / "sdk" / f"libcxx-{LLVM_VERSION}"
146+
)
147+
LIBCXX_BASE_NAME = f"clang+llvm-{LLVM_VERSION}-x86_64-linux-gnu-ubuntu-20.04"
148+
149+
150+
def stage_libcxx(target_dir: pathlib.Path):
78151
"""
79-
Extract files from a zip archive into target_dir, stripping a prefix.
80-
81-
Args:
82-
archive_path (pathlib.Path): Path to the .zip archive.
83-
prefix (str): Prefix folder inside the archive to strip.
84-
target_dir (pathlib.Path): Destination directory.
152+
Download (if needed) and stage libc++ shared libraries into the wheel package.
153+
- target_dir: destination folder in the wheel, e.g.
154+
executorch/backends/qualcomm/sdk/libcxx-14.0.0
85155
"""
86-
with zipfile.ZipFile(archive_path, "r") as zf:
87-
for member in zf.infolist():
88-
if not member.filename.startswith(prefix + "/"):
89-
continue
90-
relpath = pathlib.Path(member.filename).relative_to(prefix)
91-
if not relpath.parts or relpath.parts[0] == "..":
92-
continue
93-
out_path = target_dir / relpath
94-
if member.is_dir():
95-
out_path.mkdir(parents=True, exist_ok=True)
96-
else:
97-
out_path.parent.mkdir(parents=True, exist_ok=True)
98-
with zf.open(member) as src, open(out_path, "wb") as dst:
99-
dst.write(src.read())
156+
target_dir.mkdir(parents=True, exist_ok=True)
100157

158+
# Check if already staged
159+
existing_files = list(target_dir.glob("*"))
160+
if existing_files:
161+
print(f"[libcxx] Already staged at {target_dir}, skipping")
162+
return
101163

102-
def _extract_tar(archive_path: pathlib.Path, prefix: str, target_dir: pathlib.Path):
164+
# URL to a tarball with prebuilt libc++ shared libraries
165+
LLVM_URL = f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{LLVM_VERSION}/{LIBCXX_BASE_NAME}.tar.xz"
166+
temp_tar = pathlib.Path("/tmp") / f"{LIBCXX_BASE_NAME}.tar.xz"
167+
temp_extract = pathlib.Path("/tmp") / f"{LIBCXX_BASE_NAME}"
168+
169+
# Download if not already exists
170+
if not temp_tar.exists():
171+
print(f"[libcxx] Downloading {LLVM_URL}")
172+
urllib.request.urlretrieve(LLVM_URL, temp_tar)
173+
174+
# Extract
175+
print(f"[libcxx] Extracting {temp_tar}")
176+
with tarfile.open(temp_tar, "r:xz") as tar:
177+
tar.extractall(temp_extract.parent)
178+
179+
# Copy only the required .so files
180+
lib_src = temp_extract / "lib"
181+
required_files = [
182+
"libc++.so.1.0",
183+
"libc++abi.so.1.0",
184+
"libunwind.so.1",
185+
"libm.so.6",
186+
"libpython3.10.so.1.0",
187+
]
188+
for fname in required_files:
189+
src_path = lib_src / fname
190+
if not src_path.exists():
191+
raise FileNotFoundError(f"{fname} not found in extracted LLVM")
192+
shutil.copy(src_path, target_dir / fname)
193+
194+
# Create symlinks
195+
os.symlink("libc++.so.1.0", target_dir / "libc++.so.1")
196+
os.symlink("libc++.so.1", target_dir / "libc++.so")
197+
os.symlink("libc++abi.so.1.0", target_dir / "libc++abi.so.1")
198+
os.symlink("libc++abi.so.1", target_dir / "libc++abi.so")
199+
200+
print(f"[libcxx] Staged libc++ to {target_dir}")
201+
202+
203+
def _load_libcxx_libs(lib_path):
103204
"""
104-
Extract files from a tar.gz archive into target_dir, stripping a prefix.
205+
Load libc++ shared libraries from the given directory.
105206
106-
Args:
107-
archive_path (pathlib.Path): Path to the .tar.gz or .tgz archive.
108-
prefix (str): Prefix folder inside the archive to strip.
109-
target_dir (pathlib.Path): Destination directory.
207+
Ensures libc++abi is loaded first, then libc++, then any other .so files.
110208
"""
111-
with tarfile.open(archive_path, "r:gz") as tf:
112-
for m in tf.getmembers():
113-
if not m.name.startswith(prefix + "/"):
114-
continue
115-
relpath = pathlib.Path(m.name).relative_to(prefix)
116-
if not relpath.parts or relpath.parts[0] == "..":
117-
continue
118-
119-
out_path = target_dir / relpath
120-
if m.isdir():
121-
out_path.mkdir(parents=True, exist_ok=True)
122-
else:
123-
out_path.parent.mkdir(parents=True, exist_ok=True)
124-
src = tf.extractfile(m)
125-
if src is None:
126-
# Skip non-regular files (links, devices, etc.)
127-
continue
128-
with src, open(out_path, "wb") as dst:
129-
dst.write(src.read())
209+
candidates = list(lib_path.glob("*.so*"))
210+
211+
priority = ["libc++abi", "libc++"]
212+
sorted_candidates = []
213+
214+
for name in priority:
215+
for f in candidates:
216+
if f.name.startswith(name):
217+
sorted_candidates.append(f)
218+
219+
for f in candidates:
220+
if f not in sorted_candidates:
221+
sorted_candidates.append(f)
222+
223+
for sofile in sorted_candidates:
224+
try:
225+
ctypes.CDLL(str(sofile), mode=ctypes.RTLD_GLOBAL)
226+
print(f"Loaded {sofile.name}")
227+
except OSError as e:
228+
print(f"[WARN] Failed to load {sofile.name}: {e}")

0 commit comments

Comments
 (0)