fast_parallel_download.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #!/usr/bin/env python3
  2. """
  3. Fast Parallel Archive Download
  4. This script implements the logic previously embedded inline in the action YAML.
  5. It expects to be executed with the working directory set to the kernel workspace
  6. where `manifest.xml` resides.
  7. """
  8. import xml.etree.ElementTree as ET
  9. import subprocess
  10. import os
  11. import shutil
  12. from concurrent.futures import ThreadPoolExecutor
  13. import traceback
  14. import time
  15. import sys
  16. def try_download(url, name):
  17. aria_cmd = f"aria2c -x16 -s16 -k1M -j5 --file-allocation=none -o {name}.tar.gz '{url}'"
  18. print(f" Trying download: {url}")
  19. result = subprocess.run(aria_cmd, shell=True)
  20. if result.returncode == 0:
  21. return True
  22. print(f" Download failed, retrying in 10 seconds...")
  23. time.sleep(10)
  24. result = subprocess.run(aria_cmd, shell=True)
  25. return result.returncode == 0
  26. def sync_project(task):
  27. name, path, url, strip, rev, linkfiles, copyfiles = task
  28. if path not in ["./", "."]:
  29. os.makedirs(path, exist_ok=True)
  30. print(f"Syncing: {name} -> {path}")
  31. print(f" Download URL: {url}")
  32. try:
  33. downloaded = False
  34. # Only apply deprecated fallback for googlesource URLs
  35. if "googlesource.com" in url:
  36. downloaded = try_download(url, name)
  37. if not downloaded:
  38. if "+archive/" in url:
  39. parts = url.split("+archive/")
  40. branch = parts[1].split(".tar.gz")[0]
  41. dep_url = f"{parts[0]}+archive/deprecated/{branch}.tar.gz"
  42. print(f" Main branch failed, trying deprecated branch: {dep_url}")
  43. downloaded = try_download(dep_url, name)
  44. else:
  45. downloaded = try_download(url, name)
  46. if not downloaded:
  47. print(f"Failed to download {name} from all attempted URLs.")
  48. return False
  49. nproc = int(subprocess.check_output("nproc", shell=True).strip() or 1)
  50. tar_cmd = f"tar -I 'pigz -p {nproc} -b 256' -x --record-size=1M -C {path} {strip} -f {name}.tar.gz"
  51. subprocess.run(tar_cmd, shell=True, check=True)
  52. os.remove(f"{name}.tar.gz")
  53. top_dir = os.getcwd()
  54. for src_rel, dest_rel in linkfiles:
  55. src_path = os.path.join(top_dir, path, src_rel)
  56. dest_path = os.path.join(top_dir, dest_rel)
  57. os.makedirs(os.path.dirname(dest_path), exist_ok=True)
  58. if os.path.lexists(dest_path):
  59. os.remove(dest_path)
  60. rel_target = os.path.relpath(src_path, os.path.dirname(dest_path))
  61. os.symlink(rel_target, dest_path)
  62. print(f" [Link] {dest_rel} -> {src_rel}")
  63. for src_rel, dest_rel in copyfiles:
  64. src_path = os.path.join(top_dir, path, src_rel)
  65. dest_path = os.path.join(top_dir, dest_rel)
  66. os.makedirs(os.path.dirname(dest_path), exist_ok=True)
  67. shutil.copy2(src_path, dest_path)
  68. print(f" [Copy] {dest_rel} from {src_rel}")
  69. print(f"Synced {name} successfully!")
  70. return True
  71. except Exception as e:
  72. print(f"Failed to sync {name}: {e}")
  73. traceback.print_exc()
  74. return False
  75. def main(manifest_path='manifest.xml'):
  76. if not os.path.exists(manifest_path):
  77. print(f"ERROR: manifest file not found: {manifest_path}")
  78. return 2
  79. with open(manifest_path, 'r') as f:
  80. manifest_content = f.read()
  81. root = ET.fromstring(manifest_content)
  82. remotes = {}
  83. for r in root.findall('remote'):
  84. fetch = (r.get('fetch') or '').rstrip('/')
  85. if fetch == '..':
  86. fetch = 'https://android.googlesource.com'
  87. remotes[r.get('name')] = fetch
  88. default = root.find('default')
  89. def_remote = default.get('remote') if default is not None else None
  90. def_rev = default.get('revision') if default is not None else None
  91. sync_tasks = []
  92. for project in root.findall('project'):
  93. name = project.get('name')
  94. path = project.get('path', name)
  95. remote_name = project.get('remote', def_remote)
  96. rev = project.get('revision', def_rev)
  97. base_url = remotes.get(remote_name)
  98. if not base_url:
  99. continue
  100. if "github.com" in base_url:
  101. url = f"{base_url}/{name}/archive/{rev}.tar.gz"
  102. strip = "--strip-components=1"
  103. elif "googlesource.com" in base_url:
  104. url = f"{base_url}/{name}/+archive/{rev}.tar.gz"
  105. strip = ""
  106. elif "git.codelinaro.org" in base_url:
  107. url = f"{base_url}/{name}/-/archive/{rev}.tar.gz"
  108. strip = "--strip-components=1"
  109. else:
  110. continue
  111. linkfiles = [(lf.get('src'), lf.get('dest')) for lf in project.findall('linkfile')]
  112. copyfiles = [(cf.get('src'), cf.get('dest')) for cf in project.findall('copyfile')]
  113. sync_tasks.append((name, path, url, strip, rev, linkfiles, copyfiles))
  114. print(f"Found {len(sync_tasks)} projects to sync.")
  115. max_workers = (os.cpu_count() or 2) * 4
  116. with ThreadPoolExecutor(max_workers=max_workers) as executor:
  117. results = list(executor.map(sync_project, sync_tasks))
  118. if not all(results):
  119. print("One or more projects failed to sync!")
  120. return 1
  121. return 0
  122. if __name__ == '__main__':
  123. rc = main()
  124. sys.exit(rc)