action.yml 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. name: 'Download Kernel Repository'
  2. description: 'Initialize and sync Android kernel source repository'
  3. inputs:
  4. android_version:
  5. description: 'Android version (e.g., android14)'
  6. required: true
  7. kernel_version:
  8. description: 'Kernel version (e.g., 5.15, 6.1)'
  9. required: true
  10. os_patch_level:
  11. description: 'OS patch level (e.g., 2024-01)'
  12. required: true
  13. use_repo:
  14. description: 'Use repo for downloading kernel source'
  15. required: false
  16. outputs:
  17. deprecated_branch:
  18. description: 'Whether the branch is deprecated'
  19. value: ${{ steps.sync.outputs.deprecated }}
  20. runs:
  21. using: composite
  22. steps:
  23. - name: Initialize and Sync Kernel Repository
  24. if: ${{ inputs.use_repo }}
  25. id: sync
  26. shell: bash
  27. working-directory: ${{ github.workspace }}/kernel
  28. run: |
  29. FORMATTED_BRANCH="${{ inputs.android_version }}-${{ inputs.kernel_version }}-${{ inputs.os_patch_level }}"
  30. repo init -u https://android.googlesource.com/kernel/manifest -b common-${FORMATTED_BRANCH} --depth=1 --partial-clone --clone-filter=blob:limit=10M
  31. REMOTE_BRANCH=$(git ls-remote https://android.googlesource.com/kernel/common ${FORMATTED_BRANCH})
  32. DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml
  33. DEPRECATED=false
  34. if grep -q deprecated <<< $REMOTE_BRANCH; then
  35. sed -i "s/\"${FORMATTED_BRANCH}\"/\"deprecated\/${FORMATTED_BRANCH}\"/g" $DEFAULT_MANIFEST_PATH
  36. echo "⚠ Note: Branch ${FORMATTED_BRANCH} is considered deprecated."
  37. DEPRECATED=true
  38. fi
  39. echo "deprecated=$DEPRECATED" >> $GITHUB_OUTPUT
  40. repo sync -c -j$(nproc --all) --fail-fast --no-tags --force-sync --no-clone-bundle
  41. - name: Download manifest.xml for branch (with deprecated fallback)
  42. shell: bash
  43. if: ${{ !inputs.use_repo }}
  44. working-directory: ${{ github.workspace }}/kernel
  45. run: |
  46. set -e
  47. FORMATTED_BRANCH="${{ inputs.android_version }}-${{ inputs.kernel_version }}-${{ inputs.os_patch_level }}"
  48. MAIN_MANIFEST_URL="https://android.googlesource.com/kernel/manifest/+/refs/heads/common-${FORMATTED_BRANCH}/default.xml?format=TEXT"
  49. DEPRECATED_MANIFEST_URL="https://android.googlesource.com/kernel/manifest/+/refs/heads/deprecated/common-${FORMATTED_BRANCH}/default.xml?format=TEXT"
  50. echo "Trying to fetch manifest from $MAIN_MANIFEST_URL"
  51. if curl -fsSL "$MAIN_MANIFEST_URL" | base64 -d > manifest.xml; then
  52. echo "Fetched manifest from $MAIN_MANIFEST_URL"
  53. else
  54. echo "Main manifest fetch failed, trying deprecated branch."
  55. echo "⚠ Note: Branch common-${FORMATTED_BRANCH} is considered deprecated."
  56. if curl -fsSL "$DEPRECATED_MANIFEST_URL" | base64 -d > manifest.xml; then
  57. echo "Fetched manifest from $DEPRECATED_MANIFEST_URL"
  58. else
  59. echo "ERROR: Neither main nor deprecated branch exists for $FORMATTED_BRANCH" >&2
  60. exit 22
  61. fi
  62. fi
  63. - name: Debug Show manifest.xml
  64. shell: bash
  65. if: ${{ !inputs.use_repo }}
  66. working-directory: ${{ github.workspace }}/kernel
  67. run: cat manifest.xml
  68. - name: Fast Parallel Archive Download
  69. shell: python
  70. if: ${{ !inputs.use_repo }}
  71. working-directory: ${{ github.workspace }}/kernel
  72. run: |
  73. import xml.etree.ElementTree as ET
  74. import subprocess
  75. import os, glob, shutil
  76. from concurrent.futures import ThreadPoolExecutor
  77. MAX_WORKERS = (os.cpu_count() or 2) * 4
  78. NPROC = int(subprocess.check_output("nproc", shell=True).strip())
  79. import traceback
  80. def sync_project(task):
  81. name, path, url, strip, rev, linkfiles, copyfiles = task
  82. if path not in ["./", "."]:
  83. os.makedirs(path, exist_ok=True)
  84. print(f"Syncing: {name} -> {path}")
  85. print(f" Download URL: {url}")
  86. try:
  87. import time
  88. def try_download(url, name):
  89. aria_cmd = f"aria2c -x16 -s16 -k1M -j5 --file-allocation=none -o {name}.tar.gz '{url}'"
  90. print(f" Trying download: {url}")
  91. result = subprocess.run(aria_cmd, shell=True)
  92. if result.returncode == 0:
  93. return True
  94. print(f" Download failed, retrying in 10 seconds...")
  95. time.sleep(10)
  96. result = subprocess.run(aria_cmd, shell=True)
  97. return result.returncode == 0
  98. downloaded = False
  99. # Only apply deprecated fallback for googlesource URLs
  100. if "googlesource.com" in url:
  101. # Try main branch first
  102. downloaded = try_download(url, name)
  103. if not downloaded:
  104. # Try deprecated branch
  105. if "+archive/" in url:
  106. parts = url.split("+archive/")
  107. branch = parts[1].split(".tar.gz")[0]
  108. dep_url = f"{parts[0]}+archive/deprecated/{branch}.tar.gz"
  109. print(f" Main branch failed, trying deprecated branch: {dep_url}")
  110. downloaded = try_download(dep_url, name)
  111. else:
  112. downloaded = try_download(url, name)
  113. if not downloaded:
  114. print(f"Failed to download {name} from all attempted URLs.")
  115. return False
  116. tar_cmd = f"tar -I 'pigz -p {NPROC} -b 256' -x --record-size=1M -C {path} {strip} -f {name}.tar.gz"
  117. subprocess.run(tar_cmd, shell=True, check=True)
  118. os.remove(f"{name}.tar.gz")
  119. # Handle linkfiles and copyfiles
  120. top_dir = os.getcwd()
  121. for src_rel, dest_rel in linkfiles:
  122. src_path = os.path.join(top_dir, path, src_rel)
  123. dest_path = os.path.join(top_dir, dest_rel)
  124. os.makedirs(os.path.dirname(dest_path), exist_ok=True)
  125. if os.path.lexists(dest_path): os.remove(dest_path)
  126. rel_target = os.path.relpath(src_path, os.path.dirname(dest_path))
  127. os.symlink(rel_target, dest_path)
  128. print(f" [Link] {dest_rel} -> {src_rel}")
  129. for src_rel, dest_rel in copyfiles:
  130. src_path = os.path.join(top_dir, path, src_rel)
  131. dest_path = os.path.join(top_dir, dest_rel)
  132. os.makedirs(os.path.dirname(dest_path), exist_ok=True)
  133. shutil.copy2(src_path, dest_path)
  134. print(f" [Copy] {dest_rel} from {src_rel}")
  135. print(f"Synced {name} successfully!")
  136. return True
  137. except Exception as e:
  138. print(f"Failed to sync {name}: {e}")
  139. traceback.print_exc()
  140. return False
  141. with open('manifest.xml', 'r') as f:
  142. manifest_content = f.read()
  143. root = ET.fromstring(manifest_content)
  144. # Resolve fetch paths: '..' means 'https://android.googlesource.com'
  145. remotes = {}
  146. for r in root.findall('remote'):
  147. fetch = r.get('fetch').rstrip('/')
  148. if fetch == '..':
  149. fetch = 'https://android.googlesource.com'
  150. remotes[r.get('name')] = fetch
  151. default = root.find('default')
  152. def_remote = default.get('remote') if default is not None else None
  153. def_rev = default.get('revision') if default is not None else None
  154. sync_tasks = []
  155. for project in root.findall('project'):
  156. name = project.get('name')
  157. path = project.get('path', name)
  158. remote_name = project.get('remote', def_remote)
  159. rev = project.get('revision', def_rev)
  160. base_url = remotes.get(remote_name)
  161. if not base_url:
  162. continue
  163. if "github.com" in base_url:
  164. url = f"{base_url}/{name}/archive/{rev}.tar.gz"
  165. strip = "--strip-components=1"
  166. elif "googlesource.com" in base_url:
  167. url = f"{base_url}/{name}/+archive/{rev}.tar.gz"
  168. strip = ""
  169. elif "git.codelinaro.org" in base_url:
  170. url = f"{base_url}/{name}/-/archive/{rev}.tar.gz"
  171. strip = "--strip-components=1"
  172. else:
  173. continue
  174. linkfiles = [(lf.get('src'), lf.get('dest')) for lf in project.findall('linkfile')]
  175. copyfiles = [(cf.get('src'), cf.get('dest')) for cf in project.findall('copyfile')]
  176. sync_tasks.append((name, path, url, strip, rev, linkfiles, copyfiles))
  177. print(f"Found {len(sync_tasks)} projects to sync.")
  178. with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
  179. results = list(executor.map(sync_project, sync_tasks))
  180. if not all(results):
  181. print("One or more projects failed to sync!")
  182. exit(1)