gitsm.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. # ex:ts=4:sw=4:sts=4:et
  2. # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
  3. """
  4. BitBake 'Fetch' git submodules implementation
  5. Inherits from and extends the Git fetcher to retrieve submodules of a git repository
  6. after cloning.
  7. SRC_URI = "gitsm://<see Git fetcher for syntax>"
  8. See the Git fetcher, git://, for usage documentation.
  9. NOTE: Switching a SRC_URI from "git://" to "gitsm://" requires a clean of your recipe.
  10. """
  11. # Copyright (C) 2013 Richard Purdie
  12. #
  13. # This program is free software; you can redistribute it and/or modify
  14. # it under the terms of the GNU General Public License version 2 as
  15. # published by the Free Software Foundation.
  16. #
  17. # This program is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. # GNU General Public License for more details.
  21. #
  22. # You should have received a copy of the GNU General Public License along
  23. # with this program; if not, write to the Free Software Foundation, Inc.,
  24. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  25. import os
  26. import bb
  27. import copy
  28. from bb.fetch2.git import Git
  29. from bb.fetch2 import runfetchcmd
  30. from bb.fetch2 import logger
  31. from bb.fetch2 import Fetch
  32. from bb.fetch2 import BBFetchException
  33. class GitSM(Git):
  34. def supports(self, ud, d):
  35. """
  36. Check to see if a given url can be fetched with git.
  37. """
  38. return ud.type in ['gitsm']
  39. @staticmethod
  40. def parse_gitmodules(gitmodules):
  41. modules = {}
  42. module = ""
  43. for line in gitmodules.splitlines():
  44. if line.startswith('[submodule'):
  45. module = line.split('"')[1]
  46. modules[module] = {}
  47. elif module and line.strip().startswith('path'):
  48. path = line.split('=')[1].strip()
  49. modules[module]['path'] = path
  50. elif module and line.strip().startswith('url'):
  51. url = line.split('=')[1].strip()
  52. modules[module]['url'] = url
  53. return modules
  54. def update_submodules(self, ud, d):
  55. submodules = []
  56. paths = {}
  57. uris = {}
  58. local_paths = {}
  59. for name in ud.names:
  60. try:
  61. gitmodules = runfetchcmd("%s show %s:.gitmodules" % (ud.basecmd, ud.revisions[name]), d, quiet=True, workdir=ud.clonedir)
  62. except:
  63. # No submodules to update
  64. continue
  65. for m, md in self.parse_gitmodules(gitmodules).items():
  66. submodules.append(m)
  67. paths[m] = md['path']
  68. uris[m] = md['url']
  69. if uris[m].startswith('..'):
  70. newud = copy.copy(ud)
  71. newud.path = os.path.realpath(os.path.join(newud.path, md['url']))
  72. uris[m] = Git._get_repo_url(self, newud)
  73. for module in submodules:
  74. module_hash = runfetchcmd("%s ls-tree -z -d %s %s" % (ud.basecmd, ud.revisions[name], paths[module]), d, quiet=True, workdir=ud.clonedir)
  75. module_hash = module_hash.split()[2]
  76. # Build new SRC_URI
  77. proto = uris[module].split(':', 1)[0]
  78. url = uris[module].replace('%s:' % proto, 'gitsm:', 1)
  79. url += ';protocol=%s' % proto
  80. url += ";name=%s" % module
  81. url += ";bareclone=1;nocheckout=1;nobranch=1"
  82. ld = d.createCopy()
  83. # Not necessary to set SRC_URI, since we're passing the URI to
  84. # Fetch.
  85. #ld.setVar('SRC_URI', url)
  86. ld.setVar('SRCREV_%s' % module, module_hash)
  87. # Workaround for issues with SRCPV/SRCREV_FORMAT errors
  88. # error refer to 'multiple' repositories. Only the repository
  89. # in the original SRC_URI actually matters...
  90. ld.setVar('SRCPV', d.getVar('SRCPV'))
  91. ld.setVar('SRCREV_FORMAT', module)
  92. newfetch = Fetch([url], ld, cache=False)
  93. newfetch.download()
  94. local_paths[module] = newfetch.localpath(url)
  95. # Correct the submodule references to the local download version...
  96. runfetchcmd("%(basecmd)s config submodule.%(module)s.url %(url)s" % {'basecmd': ud.basecmd, 'module': module, 'url' : local_paths[module]}, d, workdir=ud.clonedir)
  97. symlink_path = os.path.join(ud.clonedir, 'modules', paths[module])
  98. if not os.path.exists(symlink_path):
  99. try:
  100. os.makedirs(os.path.dirname(symlink_path), exist_ok=True)
  101. except OSError:
  102. pass
  103. os.symlink(local_paths[module], symlink_path)
  104. return True
  105. def need_update(self, ud, d):
  106. main_repo_needs_update = Git.need_update(self, ud, d)
  107. # First check that the main repository has enough history fetched. If it doesn't, then we don't
  108. # even have the .gitmodules and gitlinks for the submodules to attempt asking whether the
  109. # submodules' histories are recent enough.
  110. if main_repo_needs_update:
  111. return True
  112. # Now check that the submodule histories are new enough. The git-submodule command doesn't have
  113. # any clean interface for doing this aside from just attempting the checkout (with network
  114. # fetched disabled).
  115. return not self.update_submodules(ud, d)
  116. def download(self, ud, d):
  117. Git.download(self, ud, d)
  118. if not ud.shallow or ud.localpath != ud.fullshallow:
  119. self.update_submodules(ud, d)
  120. def copy_submodules(self, submodules, ud, destdir, d):
  121. if ud.bareclone:
  122. repo_conf = destdir
  123. else:
  124. repo_conf = os.path.join(destdir, '.git')
  125. if submodules and not os.path.exists(os.path.join(repo_conf, 'modules')):
  126. os.mkdir(os.path.join(repo_conf, 'modules'))
  127. for module, md in submodules.items():
  128. srcpath = os.path.join(ud.clonedir, 'modules', md['path'])
  129. modpath = os.path.join(repo_conf, 'modules', md['path'])
  130. if os.path.exists(srcpath):
  131. if os.path.exists(os.path.join(srcpath, '.git')):
  132. srcpath = os.path.join(srcpath, '.git')
  133. target = modpath
  134. if os.path.exists(modpath):
  135. target = os.path.dirname(modpath)
  136. os.makedirs(os.path.dirname(target), exist_ok=True)
  137. runfetchcmd("cp -fpLR %s %s" % (srcpath, target), d)
  138. elif os.path.exists(modpath):
  139. # Module already exists, likely unpacked from a shallow mirror clone
  140. pass
  141. else:
  142. # This is fatal, as we do NOT want git-submodule to hit the network
  143. raise bb.fetch2.FetchError('Submodule %s does not exist in %s or %s.' % (module, srcpath, modpath))
  144. def clone_shallow_local(self, ud, dest, d):
  145. super(GitSM, self).clone_shallow_local(ud, dest, d)
  146. # Copy over the submodules' fetched histories too.
  147. repo_conf = os.path.join(dest, '.git')
  148. submodules = []
  149. for name in ud.names:
  150. try:
  151. gitmodules = runfetchcmd("%s show %s:.gitmodules" % (ud.basecmd, ud.revision), d, quiet=True, workdir=dest)
  152. except:
  153. # No submodules to update
  154. continue
  155. submodules = self.parse_gitmodules(gitmodules)
  156. self.copy_submodules(submodules, ud, dest, d)
  157. def unpack(self, ud, destdir, d):
  158. Git.unpack(self, ud, destdir, d)
  159. # Copy over the submodules' fetched histories too.
  160. if ud.bareclone:
  161. repo_conf = ud.destdir
  162. else:
  163. repo_conf = os.path.join(ud.destdir, '.git')
  164. update_submodules = False
  165. paths = {}
  166. uris = {}
  167. local_paths = {}
  168. for name in ud.names:
  169. try:
  170. gitmodules = runfetchcmd("%s show HEAD:.gitmodules" % (ud.basecmd), d, quiet=True, workdir=ud.destdir)
  171. except:
  172. # No submodules to update
  173. continue
  174. submodules = self.parse_gitmodules(gitmodules)
  175. self.copy_submodules(submodules, ud, ud.destdir, d)
  176. submodules_queue = [(module, os.path.join(repo_conf, 'modules', md['path'])) for module, md in submodules.items()]
  177. while len(submodules_queue) != 0:
  178. module, modpath = submodules_queue.pop()
  179. # add submodule children recursively
  180. try:
  181. gitmodules = runfetchcmd("%s show HEAD:.gitmodules" % (ud.basecmd), d, quiet=True, workdir=modpath)
  182. for m, md in self.parse_gitmodules(gitmodules).items():
  183. submodules_queue.append([m, os.path.join(modpath, 'modules', md['path'])])
  184. except:
  185. # no children
  186. pass
  187. # There are submodules to update
  188. update_submodules = True
  189. # Determine (from the submodule) the correct url to reference
  190. try:
  191. output = runfetchcmd("%(basecmd)s config remote.origin.url" % {'basecmd': ud.basecmd}, d, workdir=modpath)
  192. except bb.fetch2.FetchError as e:
  193. # No remote url defined in this submodule
  194. continue
  195. local_paths[module] = output
  196. # Setup the local URL properly (like git submodule init or sync would do...)
  197. runfetchcmd("%(basecmd)s config submodule.%(module)s.url %(url)s" % {'basecmd': ud.basecmd, 'module': module, 'url' : local_paths[module]}, d, workdir=ud.destdir)
  198. # Ensure the submodule repository is NOT set to bare, since we're checking it out...
  199. runfetchcmd("%s config core.bare false" % (ud.basecmd), d, quiet=True, workdir=modpath)
  200. if update_submodules:
  201. # Run submodule update, this sets up the directories -- without touching the config
  202. runfetchcmd("%s submodule update --recursive --no-fetch" % (ud.basecmd), d, quiet=True, workdir=ud.destdir)