import argparse import glob import os import subprocess import sys import zipfile from . import detect, repack, rebuild from .manifest import parse_manifest # Lazy import for integration staging (optional dependency) try: from scripts.stage_integration import stage_integration as _stage_integration except ImportError: _stage_integration = None BUILD_RECIPES = { "numpy": rebuild.build_numpy_wheel, } MANIFEST_SOURCES_DIR = "output/sources" def _clone_repo(repo_url, pkg_name): dest = os.path.join(MANIFEST_SOURCES_DIR, pkg_name) if os.path.isdir(dest) and os.listdir(dest): print(f"[manifest] Source already exists: {dest}") return dest os.makedirs(dest, exist_ok=True) try: print(f"[manifest] Cloning {repo_url} \u2192 {dest}") subprocess.run( ["git", "clone", "--depth", "1", repo_url, dest], check=True, capture_output=True, timeout=120, ) except Exception as e: print(f"[manifest] Warning: git clone failed for {pkg_name}: {e}") return dest def _collect_wheels(wheel_dir, output_dir, zip_name="ecoark-rebuild-output.zip"): whls = sorted(glob.glob(os.path.join(wheel_dir, "*.whl"))) zip_path = os.path.join(output_dir, zip_name) os.makedirs(os.path.dirname(zip_path), exist_ok=True) with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: for whl in whls: zf.write(whl, os.path.basename(whl)) print(f"[manifest] Bundle: {zip_path} ({len(whls)} wheel(s))") return zip_path def run_manifest(manifest_path, output_dir, target_os="harmonyos", arch="arm64"): os.makedirs(output_dir, exist_ok=True) entries = parse_manifest(manifest_path) if not entries: print(f"[manifest] No active entries in {manifest_path} \u2014 nothing to build.") print("[manifest] Edit packages.txt or create a temporary manifest to proceed.") _collect_wheels(output_dir, output_dir) return print(f"[manifest] Processing {len(entries)} package(s) from {manifest_path}") wheel_dir = os.path.join(output_dir, "wheels") for pkg_name, repo_url in entries: print(f"\n{'=' * 60}") print(f"[manifest] Package: {pkg_name}") print(f"[manifest] Repo : {repo_url}") src = _clone_repo(repo_url, pkg_name) rebuild.rebuild_from_source(src, pkg_name, wheel_dir, target_os=target_os, arch=arch) bundle = _collect_wheels(wheel_dir, output_dir) print(f"\n{'=' * 60}") print(f"[manifest] Done. Bundle: {bundle}") def main(): parser = argparse.ArgumentParser( description="EcoArk HarmonyOS Wheel Pipeline \u2014 repack / rebuild Python wheels for HarmonyOS/arm64" ) parser.add_argument("input", nargs="?", help="Path to a .whl file (single-wheel mode)") parser.add_argument("--manifest", help="Path to packages manifest file (manifest-driven mode)") parser.add_argument("--build-from-source", choices=list(BUILD_RECIPES.keys()), help="Build a package from source (cross-compile for HarmonyOS)") parser.add_argument("--package-version", default="1.26.4", help="Package version for --build-from-source (default: 1.26.4)") parser.add_argument("--output-dir", "-o", default="output", help="Output directory (default: ./output)") parser.add_argument("--target-os", default="harmonyos", choices=["harmonyos", "linux", "ohos"]) parser.add_argument("--arch", default="arm64", choices=["arm64", "aarch64", "x86_64"]) parser.add_argument("--force-native", action="store_true", help="Treat as native wheel even if detection says pure") parser.add_argument("--force-repack", action="store_true", help="Force repack even for native wheels") parser.add_argument("--rebuild", action="store_true", help="Force rebuild even if build dir exists") parser.add_argument("--stage-integration", nargs="?", const="output/wheels/numpy-1.26.4-cp312-cp312-harmonyos_arm64.whl", help="Stage native .so files for EcoArk integration (optional: specify wheel path)") args = parser.parse_args() if args.build_from_source: builder = BUILD_RECIPES[args.build_from_source] output_dir = os.path.abspath(args.output_dir) os.makedirs(output_dir, exist_ok=True) wheel_path = builder( version=args.package_version, output_dir=output_dir, rebuild=args.rebuild, ) if wheel_path: print(f"[pipeline] Done. Wheel: {wheel_path}") else: print("[pipeline] FAILED", file=sys.stderr) sys.exit(1) return if args.manifest: run_manifest(args.manifest, args.output_dir, args.target_os, args.arch) return if args.stage_integration: if _stage_integration is None: print("[pipeline] ERROR: scripts.stage_integration not importable", file=sys.stderr) sys.exit(1) wheel = os.path.abspath(args.stage_integration) out = os.path.join(os.path.abspath(args.output_dir), "integration") rc = _stage_integration(wheel, out) sys.exit(rc) if not args.input: parser.error("either INPUT (a .whl file), --manifest, --build-from-source, or --stage-integration is required") input_path = args.input if not os.path.isfile(input_path): print(f"Error: input file not found: {input_path}", file=sys.stderr) sys.exit(1) if not input_path.endswith(".whl"): print(f"Error: input must be a .whl file, got: {input_path}", file=sys.stderr) sys.exit(1) info = detect.detect_wheel_type(input_path) if "error" in info: print(f"Error: {info['error']}", file=sys.stderr) sys.exit(1) print(f"[pipeline] Input : {info['wheel']}") print(f"[pipeline] Pure Py : {info['pure_python']}") print(f"[pipeline] Detail : {info['detail']}") output_dir = os.path.abspath(args.output_dir) os.makedirs(output_dir, exist_ok=True) is_native = info["native"] or args.force_native if not is_native: print("[pipeline] Pure Python wheel \u2014 repack with harmonyos tag") out = repack.repack_wheel(input_path, output_dir) print(f"[pipeline] Output : {out}") print("[pipeline] Done \u2014 wheel can be installed directly on HarmonyOS.") else: if args.force_repack: print("[pipeline] Pure wheel detected but force-repack requested.") out = repack.repack_wheel(input_path, output_dir) print(f"[pipeline] Repacked : {out}") else: print("[pipeline] Native wheel \u2014 needs cross-compilation.") out = rebuild.rebuild_native_wheel( input_path, output_dir, target_os=args.target_os, arch=args.arch, ) print(f"[pipeline] Output : {out}") print("[pipeline] Done \u2014 native rebuild entry point triggered.") if __name__ == "__main__": main()