Felipe97/llama-cpp-compiled
01.2k
1#!/usr/bin/env python32#3# Build llama.cpp for Snapdragon (via Docker or natively) and push to device.4#5 6import sys7import os8import argparse9import subprocess10import platform11import shutil12import logging13 14from sdk import validate_windows_sdks15 16logger = logging.getLogger("build")17 18 19def parse_target(target_str):20 if not target_str:21 return None, None22 if target_str.startswith("adb") or target_str.startswith("android"):23 parts = target_str.split(":", 1)24 serial = parts[1] if len(parts) > 1 else None25 return "android", serial26 elif target_str.startswith("lnx") or target_str.startswith("linux") or target_str.startswith("ubuntu"):27 parts = target_str.split(":", 1)28 host = parts[1] if len(parts) > 1 else None29 return "linux", host30 elif target_str in ("wos", "windows"):31 return "windows", None32 else:33 return None, None34 35 36def get_uid_gid():37 if platform.system() != "Windows":38 return [f"{os.getuid()}:{os.getgid()}"]39 return []40 41 42def main():43 logging.basicConfig(level=logging.INFO, format='%(message)s')44 parser = argparse.ArgumentParser(45 description="Build llama.cpp for Snapdragon using cross-compilation docker containers or natively."46 )47 parser.add_argument("--target", default="android", help="Compilation target and deployment definition (e.g. android[:serial]/adb[:serial], linux:[user@]host/lnx:[user@]host/ubuntu:[user@]host, windows/wos) (default: android)")48 parser.add_argument("--build-dir", help="Build directory name (defaults to build-TARGET[-dbg], e.g. build-android)")49 parser.add_argument("--install-dir", help="Install directory name (defaults to pkg-TARGET[-dbg], e.g. pkg-android)")50 parser.add_argument("--jobs", "-j", type=int, help="Number of build jobs (defaults to CPU thread count)")51 parser.add_argument("--no-docker", action="store_true", help="Build natively on the host instead of in a docker container")52 parser.add_argument("--preset", help="Override the CMake preset to use")53 parser.add_argument("--debug", action="store_true", help="Build in debug mode (uses -debug presets instead of -release)")54 55 # Push options56 parser.add_argument("--push", action="store_true", help="Push built package to the target device via ADB or SSH/SCP")57 parser.add_argument("--target-dir", help="Target directory on the device (default: /data/local/tmp/llama.cpp for Android, ~/llama.cpp for Linux)")58 59 # Toolchain options60 parser.add_argument("--toolchain-version", default="v0.7", help="Docker toolchain image version/tag (default: v0.7)")61 parser.add_argument("--toolchain-url", default="ghcr.io/snapdragon-toolchain", help="Docker toolchain registry URL/namespace (default: ghcr.io/snapdragon-toolchain)")62 63 args = parser.parse_args()64 65 target_type, target_val = parse_target(args.target)66 if not target_type:67 logger.error(f"Error: Invalid target format '{args.target}'. Must be android[:serial]/adb[:serial], linux:[user@]host/lnx:[user@]host/ubuntu:[user@]host, or windows/wos.")68 sys.exit(1)69 70 if target_type == "windows":71 logger.info("Windows target selected. Forcing native compilation...")72 args.no_docker = True73 if platform.system() != "Windows":74 logger.warning("Warning: Windows compilation is intended to run on Windows arm64 hosts.")75 validate_windows_sdks()76 77 # Determine preset and check if it's debug78 preset = args.preset79 if preset:80 is_debug = args.debug or ("debug" in preset.lower())81 else:82 is_debug = args.debug83 config_type = "debug" if is_debug else "release"84 if args.no_docker:85 if target_type == "windows" or platform.system() == "Windows":86 preset = f"arm64-windows-snapdragon-{config_type}"87 elif target_type == "linux":88 preset = f"arm64-linux-snapdragon-{config_type}"89 else:90 preset = f"arm64-android-snapdragon-{config_type}"91 else:92 preset = f"arm64-linux-snapdragon-{config_type}" if target_type == "linux" else f"arm64-android-snapdragon-{config_type}"93 94 target_prefix = args.target.split(":", 1)[0]95 suffix = "-dbg" if is_debug else ""96 97 build_dir = args.build_dir98 if not build_dir:99 build_dir = f"build-{target_prefix}{suffix}"100 101 install_dir = args.install_dir102 if not install_dir:103 install_dir = f"pkg-{target_prefix}{suffix}"104 105 repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))106 107 # Ensure CMakeUserPresets.json is in the workspace root, update if docs version is newer108 preset_src = os.path.join(repo_root, "docs", "backend", "snapdragon", "CMakeUserPresets.json")109 preset_dst = os.path.join(repo_root, "CMakeUserPresets.json")110 if os.path.exists(preset_src):111 should_copy = False112 if not os.path.exists(preset_dst):113 should_copy = True114 else:115 # Check modification times116 src_mtime = os.path.getmtime(preset_src)117 dst_mtime = os.path.getmtime(preset_dst)118 if src_mtime > dst_mtime:119 preset_bak = preset_dst + ".bak"120 logger.info(f"Docs CMakeUserPresets.json is newer. Backing up existing {preset_dst} to {preset_bak}")121 shutil.copy2(preset_dst, preset_bak)122 should_copy = True123 124 if should_copy:125 logger.info(f"Copying CMakeUserPresets.json from {preset_src} to {preset_dst}")126 shutil.copy2(preset_src, preset_dst)127 else:128 logger.warning("Warning: CMakeUserPresets.json not found in docs/backend/snapdragon/.")129 130 jobs = args.jobs if args.jobs else os.cpu_count() or 4131 132 if args.no_docker:133 # Native/local host build134 logger.info("Running native/local CMake build...")135 install_prefix = os.path.join(repo_root, install_dir, "llama.cpp")136 137 # Configure138 configure_cmd = ["cmake", f"--preset={preset}", "-B", build_dir]139 logger.info(f"+ {' '.join(configure_cmd)}")140 res = subprocess.run(configure_cmd, cwd=repo_root)141 if res.returncode != 0:142 logger.error("CMake configuration failed.")143 sys.exit(res.returncode)144 145 # Build146 build_cmd = ["cmake", "--build", build_dir, "-j", str(jobs)]147 logger.info(f"+ {' '.join(build_cmd)}")148 res = subprocess.run(build_cmd, cwd=repo_root)149 if res.returncode != 0:150 logger.error("CMake build failed.")151 sys.exit(res.returncode)152 153 # Install154 install_cmd = ["cmake", "--install", build_dir, "--prefix", install_prefix]155 logger.info(f"+ {' '.join(install_cmd)}")156 res = subprocess.run(install_cmd, cwd=repo_root)157 if res.returncode != 0:158 logger.error("CMake install failed.")159 sys.exit(res.returncode)160 else:161 # Docker-based build162 logger.info("Running Docker-based cross-compilation build...")163 image_name = "arm64-linux" if target_type == "linux" else "arm64-android"164 image = f"{args.toolchain_url}/{image_name}:{args.toolchain_version}"165 166 install_prefix_container = f"/workspace/{install_dir}/llama.cpp"167 168 build_sh_cmd = (169 f"cmake --preset {preset} -B /workspace/{build_dir} && "170 f"cmake --build /workspace/{build_dir} -j {jobs} && "171 f"cmake --install /workspace/{build_dir} --prefix {install_prefix_container}"172 )173 174 docker_cmd = [175 "docker", "run", "--rm",176 "--volume", f"{repo_root}:/workspace",177 "--workdir", "/workspace",178 "--platform", "linux/amd64"179 ]180 uid_gid = get_uid_gid()181 if uid_gid:182 docker_cmd += ["-u", uid_gid[0]]183 184 docker_cmd += [image, "bash", "-c", build_sh_cmd]185 186 logger.info(f"+ {' '.join(docker_cmd)}")187 res = subprocess.run(docker_cmd, cwd=repo_root)188 if res.returncode != 0:189 logger.error("Docker-based build failed.")190 sys.exit(res.returncode)191 192 logger.info("\nBuild and installation completed successfully!")193 194 # Push/deploy if requested195 if args.push:196 src_path = os.path.join(repo_root, install_dir, "llama.cpp")197 if not os.path.exists(src_path):198 logger.error(f"Error: installation directory {src_path} does not exist. Cannot deploy.")199 sys.exit(1)200 201 # Resolve target directory on device202 target_dir = args.target_dir203 if not target_dir:204 target_dir = "/data/local/tmp/llama.cpp" if target_type == "android" else "~/llama.cpp"205 target_dir = target_dir.rstrip("/")206 207 sub_items = [item for item in os.listdir(src_path) if not item.startswith(".")]208 209 if target_type == "android":210 logger.info("\nPushing built artifacts to Android device via ADB...")211 adb_cmd = ["adb"]212 if target_val: # serial213 adb_cmd += ["-s", target_val]214 215 # Clean stale package files on device216 if sub_items:217 clean_paths = " ".join(f"{target_dir}/{item}" for item in sub_items)218 clean_cmd = adb_cmd + ["shell", f"rm -rf {clean_paths}"]219 logger.info(f"+ {' '.join(clean_cmd)}")220 subprocess.run(clean_cmd)221 222 # Android destination directory is target_dir223 push_cmd = adb_cmd + ["push", os.path.join(src_path, "."), target_dir]224 logger.info(f"+ {' '.join(push_cmd)}")225 res = subprocess.run(push_cmd)226 if res.returncode != 0:227 logger.error("ADB push failed.")228 sys.exit(res.returncode)229 230 chmod_cmd = adb_cmd + ["shell", f"chmod -R 755 {target_dir}/bin 2>/dev/null || true"]231 logger.info(f"+ {' '.join(chmod_cmd)}")232 subprocess.run(chmod_cmd)233 logger.info("ADB push completed successfully!")234 235 elif target_type == "linux":236 ssh_host = target_val237 if not ssh_host:238 logger.error("Error: SSH host not specified in target (e.g. use linux:user@host, lnx:user@host, or ubuntu:user@host). Cannot deploy.")239 sys.exit(1)240 logger.info(f"\nDeploying built artifacts to Linux device {ssh_host} via SSH/SCP...")241 242 # Clean stale package files on remote host243 if sub_items:244 clean_paths = " ".join(f"{target_dir}/{item}" for item in sub_items)245 clean_cmd = ["ssh", ssh_host, f"rm -rf {clean_paths}"]246 logger.info(f"+ {' '.join(clean_cmd)}")247 subprocess.run(clean_cmd)248 249 # Deploy to target_dir250 deploy_cmd = ["scp", "-r", os.path.join(src_path, "."), f"{ssh_host}:{target_dir}"]251 logger.info(f"+ {' '.join(deploy_cmd)}")252 res = subprocess.run(deploy_cmd)253 if res.returncode != 0:254 logger.error("SSH/SCP deploy failed.")255 sys.exit(res.returncode)256 257 chmod_cmd = ["ssh", ssh_host, f"chmod -R 755 {target_dir}/bin 2>/dev/null || true"]258 logger.info(f"+ {' '.join(chmod_cmd)}")259 subprocess.run(chmod_cmd)260 logger.info("SSH/SCP deploy completed successfully!")261 262 elif target_type == "windows":263 logger.info("\nPush for Windows on Snapdragon (windows) target is currently a stub.")264 265 266if __name__ == "__main__":267 try:268 main()269 except KeyboardInterrupt:270 logger.info("\nInterrupted by user.")271 sys.exit(130)272 except RuntimeError as err:273 logger.error("Error: %s", err)274 sys.exit(1)275 