abe_setup.py 13 KB

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