abe_setup.py 12 KB

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