abe_setup.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. #!/usr/bin/env python3
  2. #
  3. # A script to setup a Working tree from abe git submodules
  4. DEBUG = 1
  5. def main():
  6. global DEBUG
  7. p = parser()
  8. args = p.parse_args()
  9. DEBUG = args.debug
  10. DEBUG = DEBUG - (1 if args.quiet else 0)
  11. print(args) if DEBUG>1 else None
  12. count_submodules = 0
  13. last_parent = None
  14. jobs = []
  15. if args.abe_command:
  16. executeAbeCommand( args.output_dir, args.abe_command )
  17. else:
  18. for sub in abe_submodules(
  19. args.output_dir, args.source_url, args.tag,
  20. recurse_bsps=args.recurse_bsps,
  21. recurse_bootloaders=args.recurse_bootloaders ):
  22. count_submodules += 1
  23. if DEBUG > 0:
  24. print("Count:",count_submodules)
  25. if args.execute_command:
  26. from subprocess import run, CalledProcessError
  27. import sys
  28. try:
  29. run(args.execute_command, shell=True,check=True, cwd=sub.fullpath)
  30. except CalledProcessError as err:
  31. print(f"Command: \"{args.execute_command}\" in \"{sub.fullpath}\" returned exitcode \"{err.returncode}\"")
  32. sys.exit(err.returncode)
  33. else:
  34. from threading import Thread
  35. import time
  36. job = Thread(target=sub.clone_repo, kwargs={'cache_dir':args.cache_dir, 'fetch_origin':(not args.no_fetch_origin)})
  37. job.start()
  38. jobs.append(job)
  39. while len(jobs) >= args.jobs:
  40. to_remove = None
  41. for j in jobs:
  42. if not j.is_alive():
  43. j.join()
  44. to_remove = j
  45. if to_remove:
  46. jobs.remove(to_remove)
  47. else:
  48. time.sleep(1)
  49. if sub.last:
  50. while len(jobs):
  51. jobs.pop().join()
  52. def parser():
  53. import argparse
  54. p = argparse.ArgumentParser()
  55. p.add_argument('-d', '--debug', action='count', default=1)
  56. p.add_argument('-j', '--jobs',
  57. default=1,
  58. type=int,
  59. help='use threaded clone with n jobs')
  60. p.add_argument('-q', '--quiet', action='store_true')
  61. p.add_argument('-o', '--output_dir',
  62. default='.',
  63. help='local directory to operate on')
  64. p.add_argument('-c', '--cache_dir',
  65. default=None,
  66. help='use a separate local directory to cache git repositories')
  67. p.add_argument('-n', '--no_fetch_origin',
  68. action='store_true',
  69. help='prevent fetching from origin')
  70. p.add_argument('-t', '--tag',
  71. default='master',
  72. help='commit/branch/tag to be checked out')
  73. p.add_argument('-s', '--source_url',
  74. default=None,
  75. help='url to be used for origin remote')
  76. p.add_argument('--recurse_bootloaders',
  77. action='store_true',
  78. help='also recurse into .abe/bootloaders')
  79. p.add_argument('--recurse_bsps',
  80. action='store_true',
  81. help='also recurse into .abe/bsps')
  82. p.add_argument('-a', '--abe_command',
  83. default=None,
  84. help="execute an ABE command in output_dir")
  85. p.add_argument('-x', '--execute_command',
  86. default=None,
  87. help='execute command in subdirs recursively')
  88. return p
  89. def executeAbeCommand( repo_path, command_name ):
  90. import os
  91. from subprocess import run, CalledProcessError
  92. commandFileRel = os.path.join('.abe', 'commands', command_name + ".cmd")
  93. commandFile = os.path.join(repo_path, commandFileRel)
  94. if os.path.isfile(commandFile):
  95. try:
  96. proc = run([ 'bash', '-c', f"set -e;./{commandFileRel}" ], check=True, cwd=repo_path)
  97. except CalledProcessError as err:
  98. print(commandFileRel, 'in', repo_path, 'returned exitcode', err.returncode)
  99. def abe_submodules(path, remote, ref, **kwargs):
  100. url = sane_origin_url( remote, path )
  101. sub = AbeSubmodule([path, url.geturl(), ref], AbeSubType.SUBMODULE)
  102. yield sub
  103. for s in recurse_abe_submodules(sub, **kwargs):
  104. yield s
  105. def recurse_abe_submodules(parent, **kwargs):
  106. subm = get_abe_subtree( parent.fullpath, **kwargs)
  107. acc = []
  108. last = None
  109. for su in subm:
  110. su.parent = parent
  111. print(f"Repo: \"{parent.fullpath}\" has Submodule: \"{su.fullpath}\" Type: \"{su.subtype}\" From: \"{su.url.geturl()}\" Ref: \"{su.ref}\"") if DEBUG else None
  112. acc.append(su)
  113. if last != None:
  114. last.last = False
  115. yield last
  116. last = su
  117. if last != None:
  118. last.last = True
  119. yield last
  120. for sub in acc:
  121. for ret in recurse_abe_submodules( sub ):
  122. yield ret
  123. def sane_origin_url(input_url = None, path = None):
  124. from urllib.parse import urlparse
  125. print("Source Url:", input_url) if DEBUG>1 else None
  126. if input_url:
  127. if hasattr(input_url, "geturl"):
  128. return input_url
  129. if input_url.find('://') < 0 and input_url.find( ':' ) > 0:
  130. source_url = 'ssh://' + input_url.replace( ':', '/', 1)
  131. else:
  132. source_url = input_url
  133. else:
  134. source_url = origin_url( path )
  135. return urlparse(source_url)
  136. def origin_url(path):
  137. from git import Repo
  138. from urllib.parse import urlparse
  139. repo = Repo(path)
  140. for url in repo.remotes.origin.urls:
  141. return url
  142. from git import RemoteProgress
  143. import sys
  144. class ProgressPrinter(RemoteProgress):
  145. def update(self,op_code,cur_count,max_count=None,message=''):
  146. if DEBUG:
  147. cur_count_int = round(cur_count)
  148. if not max_count:
  149. div = f"{cur_count_int}/"+ ''.join(['?' for _ in range(len(str(cur_count_int)))])
  150. perc = '???'
  151. else:
  152. max_count_int = round(max_count)
  153. div = f"{cur_count_int}/{max_count_int}"
  154. perc = round(100*cur_count / (max_count or 100))
  155. print(f"\033[2K" + div, f"{perc:>3}%", message, end='\r')
  156. sys.stdout.flush()
  157. def clone_repo_cached( sub, cache_dir=None, bare=False, fetch_origin=True):
  158. print('Repo:', sub.path) if DEBUG>1 else None
  159. from git import Repo
  160. ref = sub.ref
  161. url = sub.url.geturl()
  162. repo = Repo.init(sub.fullpath, bare=bare)
  163. if 'origin' in repo.remotes:
  164. origin = repo.remotes['origin']
  165. else:
  166. origin = repo.create_remote('origin', url)
  167. if cache_dir != None:
  168. cache_repo_path = cache_path(cache_dir, origin.urls)
  169. cache_link = real_relpath( cache_repo_path, sub.fullpath)
  170. print(f"Cacheing from: {cache_repo_path}, origin: {url}") if DEBUG else None
  171. cache_repo = AbeSubmodule((cache_repo_path, url, sub.ref), AbeSubType.SUBMODULE, parent=None)
  172. clone_repo_cached(
  173. cache_repo,
  174. cache_dir = None,
  175. bare = True,
  176. fetch_origin = fetch_origin)
  177. if 'cache' in repo.remotes:
  178. cache = repo.remotes['cache']
  179. else:
  180. print(f"Setting up cache: {cache_link}, for Repo: {path}") if DEBUG>2 else None
  181. cache = repo.create_remote('cache', cache_link)
  182. # cache.set_url("no_push" , '--push')
  183. cache.fetch(refspec="+refs/remotes/origin/*:refs/remotes/origin/*",progress=ProgressPrinter())
  184. print()
  185. elif fetch_origin or (
  186. (ref not in origin.refs) and
  187. (ref not in repo.tags)):
  188. origin.fetch(progress=ProgressPrinter())
  189. print()
  190. if not bare:
  191. print('Refs:', origin.refs) if DEBUG>1 else None
  192. print('Heads:', repo.heads) if DEBUG>1 else None
  193. if ref in repo.tags:
  194. tracking_branch_name = 'local_tag_branch/'+ref
  195. tracking_ref = repo.tags[ref]
  196. if tracking_branch_name in repo.heads:
  197. active_branch = repo.heads[tracking_branch_name]
  198. else:
  199. active_branch = repo.create_head(tracking_branch_name, tracking_ref)
  200. elif ref in repo.heads:
  201. tracking_ref = origin.refs[ref]
  202. active_branch = repo.heads[ref]
  203. active_branch.set_tracking_branch(tracking_ref)
  204. elif ref in origin.refs:
  205. tracking_ref = origin.refs[ref]
  206. active_branch = repo.create_head(ref, tracking_ref)
  207. active_branch.set_tracking_branch(tracking_ref)
  208. else:
  209. tracking_ref = ref
  210. tracking_branch_name = 'local_commit_branch/'+ref
  211. if tracking_branch_name in repo.heads:
  212. active_branch = repo.heads[tracking_branch_name]
  213. else:
  214. try:
  215. active_branch = repo.create_head(tracking_branch_name, tracking_ref)
  216. except Exception:
  217. raise Exception(f"Branch/Tag/Commit \"{ref}\" not found")
  218. print('Active Branch:', active_branch) if DEBUG else None
  219. print('Tracking Ref:', tracking_ref, '\n') if DEBUG else None
  220. active_branch.checkout()
  221. repo.head.reset( tracking_ref, index=True, working_tree=False)
  222. return repo
  223. def cache_path(cache, remote):
  224. import os.path
  225. from urllib.parse import urlparse
  226. for url in remote:
  227. sane_url = urlparse(url)
  228. sane_url = sane_url._replace(
  229. path = str(sane_url.path).lstrip('/'),
  230. netloc = str(sane_url.netloc).replace('/','_').replace( '@', '_').replace(':', '_')
  231. )
  232. return os.path.join( cache, sane_url.netloc, sane_url.path)
  233. def real_relpath(dest, source='.'):
  234. import os.path
  235. real_dest = os.path.realpath(dest)
  236. real_source = os.path.realpath(source)
  237. return os.path.relpath(real_dest, real_source)
  238. from enum import Enum
  239. class AbeSubType(Enum):
  240. SUBMODULE = 1
  241. BOOTLOADER = 2
  242. BSP = 3
  243. class AbeSubmodule():
  244. subtype = AbeSubType.SUBMODULE
  245. last = True
  246. def __init__(self, sub_info, subtype=None, parent=None):
  247. if subtype != None:
  248. self.subtype = subtype
  249. self.path = sub_info[0]
  250. self.remote = sub_info[1]
  251. self.ref = sub_info[2]
  252. self.parent = parent
  253. if self.subtype == AbeSubType.BSP:
  254. self.remote = 'bsp/' + self.remote
  255. if self.subtype == AbeSubType.BOOTLOADER:
  256. self.remote = 'bootloader/' + self.remote
  257. def __repr__(self):
  258. return f"AbeSubmodule(['{self.path}','{self.remote}','{self.ref}'], {self.subtype}, {self.parent})"
  259. @property
  260. def url(self):
  261. from urllib.parse import urlparse
  262. if not self.parent:
  263. return urlparse(self.remote)
  264. surl = urlparse(self.remote)
  265. newurl = self.parent.url._replace(path=surl.path)
  266. if surl.netloc:
  267. newurl = newurl._replace(netloc=surl.netloc)
  268. if surl.scheme:
  269. newurl = newurl._replace(scheme=surl.scheme)
  270. return newurl
  271. @property
  272. def fullpath(self):
  273. if not self.parent:
  274. return self.path
  275. import os.path
  276. return os.path.normpath(os.path.join( self.parent.fullpath, self.path))
  277. def clone_repo(self, cache_dir=None, fetch_origin=True):
  278. return clone_repo_cached( self, cache_dir=cache_dir, fetch_origin=fetch_origin)
  279. def get_abe_subtree(repo_dir, recurse_bsps=False, recurse_bootloaders=False):
  280. import itertools
  281. subfile_generators = [
  282. get_abe_submodules(repo_dir),
  283. ]
  284. if recurse_bootloaders:
  285. subfile_generators.append(get_abe_bootloaders(repo_dir))
  286. if recurse_bsps:
  287. subfile_generators.append(get_abe_bsps(repo_dir))
  288. return itertools.chain(*subfile_generators)
  289. def get_abe_bsps(repo_dir):
  290. import os
  291. bspfile_path = os.path.join(repo_dir, '.abe', 'bsps')
  292. if os.path.isfile(bspfile_path):
  293. return parse_abe_subfile(bspfile_path, subtype=AbeSubType.BSP)
  294. return iter(())
  295. def get_abe_bootloaders(repo_dir):
  296. import os
  297. bootloaderfile_path = os.path.join(repo_dir, '.abe', 'bootloaders')
  298. if os.path.isfile(bootloaderfile_path):
  299. return parse_abe_subfile(bootloaderfile_path, subtype=AbeSubType.BOOTLOADER)
  300. return iter(())
  301. def get_abe_submodules(repo_dir):
  302. import os
  303. import itertools
  304. subfile_generators = []
  305. subfile_path = os.path.join(repo_dir, '.abe', 'submodules')
  306. if os.path.isfile(subfile_path):
  307. return parse_abe_subfile(subfile_path, subtype=AbeSubType.SUBMODULE)
  308. return iter(())
  309. def parse_abe_subfile(subfile_path, subtype=AbeSubType.SUBMODULE):
  310. with open(subfile_path) as subfile:
  311. for line in subfile.readlines():
  312. strline = line.strip()
  313. if strline.startswith('#'):
  314. continue
  315. sline = strline.split()
  316. if len(sline) < 3:
  317. continue
  318. print('Submodule:', sline) if DEBUG>1 else None
  319. sub = AbeSubmodule(sline, subtype)
  320. yield sub
  321. if __name__=='__main__':
  322. main()