gitsm.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. """
  2. BitBake 'Fetch' git submodules implementation
  3. Inherits from and extends the Git fetcher to retrieve submodules of a git repository
  4. after cloning.
  5. SRC_URI = "gitsm://<see Git fetcher for syntax>"
  6. See the Git fetcher, git://, for usage documentation.
  7. NOTE: Switching a SRC_URI from "git://" to "gitsm://" requires a clean of your recipe.
  8. """
  9. # Copyright (C) 2013 Richard Purdie
  10. #
  11. # SPDX-License-Identifier: GPL-2.0-only
  12. #
  13. import os
  14. import bb
  15. import copy
  16. import shutil
  17. import tempfile
  18. from bb.fetch2.git import Git
  19. from bb.fetch2 import runfetchcmd
  20. from bb.fetch2 import logger
  21. from bb.fetch2 import Fetch
  22. class GitSM(Git):
  23. def supports(self, ud, d):
  24. """
  25. Check to see if a given url can be fetched with git.
  26. """
  27. return ud.type in ['gitsm']
  28. def process_submodules(self, ud, workdir, function, d):
  29. """
  30. Iterate over all of the submodules in this repository and execute
  31. the 'function' for each of them.
  32. """
  33. submodules = []
  34. paths = {}
  35. revision = {}
  36. uris = {}
  37. subrevision = {}
  38. def parse_gitmodules(gitmodules):
  39. """
  40. Parse .gitmodules and return a dictionary of submodule paths to dictionaries with path and url members.
  41. """
  42. import configparser
  43. cp = configparser.ConfigParser()
  44. cp.read_string(gitmodules)
  45. modules = {}
  46. for section in [s for s in cp.sections() if s.startswith("submodule ")]:
  47. module = section.split('"')[1]
  48. modules[module] = {
  49. 'path': cp.get(section, 'path'),
  50. 'url': cp.get(section, 'url')
  51. }
  52. return modules
  53. # Collect the defined submodules, and their attributes
  54. for name in ud.names:
  55. try:
  56. gitmodules = runfetchcmd("%s show %s:.gitmodules" % (ud.basecmd, ud.revisions[name]), d, quiet=True, workdir=workdir)
  57. except:
  58. # No submodules to update
  59. continue
  60. for m, md in parse_gitmodules(gitmodules).items():
  61. try:
  62. module_hash = runfetchcmd("%s ls-tree -z -d %s %s" % (ud.basecmd, ud.revisions[name], md['path']), d, quiet=True, workdir=workdir)
  63. except:
  64. # If the command fails, we don't have a valid file to check. If it doesn't
  65. # fail -- it still might be a failure, see next check...
  66. module_hash = ""
  67. if not module_hash:
  68. logger.debug("submodule %s is defined, but is not initialized in the repository. Skipping", m)
  69. continue
  70. submodules.append(m)
  71. paths[m] = md['path']
  72. revision[m] = ud.revisions[name]
  73. uris[m] = md['url']
  74. subrevision[m] = module_hash.split()[2]
  75. # Convert relative to absolute uri based on parent uri
  76. if uris[m].startswith('..') or uris[m].startswith('./'):
  77. newud = copy.copy(ud)
  78. newud.path = os.path.normpath(os.path.join(newud.path, uris[m]))
  79. uris[m] = Git._get_repo_url(self, newud)
  80. for module in submodules:
  81. # Translate the module url into a SRC_URI
  82. if "://" in uris[module]:
  83. # Properly formated URL already
  84. proto = uris[module].split(':', 1)[0]
  85. url = uris[module].replace('%s:' % proto, 'gitsm:', 1)
  86. else:
  87. if ":" in uris[module]:
  88. # Most likely an SSH style reference
  89. proto = "ssh"
  90. if ":/" in uris[module]:
  91. # Absolute reference, easy to convert..
  92. url = "gitsm://" + uris[module].replace(':/', '/', 1)
  93. else:
  94. # Relative reference, no way to know if this is right!
  95. logger.warning("Submodule included by %s refers to relative ssh reference %s. References may fail if not absolute." % (ud.url, uris[module]))
  96. url = "gitsm://" + uris[module].replace(':', '/', 1)
  97. else:
  98. # This has to be a file reference
  99. proto = "file"
  100. url = "gitsm://" + uris[module]
  101. if url.endswith("{}{}".format(ud.host, ud.path)):
  102. raise bb.fetch2.FetchError("Submodule refers to the parent repository. This will cause deadlock situation in current version of Bitbake." \
  103. "Consider using git fetcher instead.")
  104. url += ';protocol=%s' % proto
  105. url += ";name=%s" % module
  106. url += ";subpath=%s" % module
  107. url += ";nobranch=1"
  108. url += ";lfs=%s" % self._need_lfs(ud)
  109. # Note that adding "user=" here to give credentials to the
  110. # submodule is not supported. Since using SRC_URI to give git://
  111. # URL a password is not supported, one have to use one of the
  112. # recommended way (eg. ~/.netrc or SSH config) which does specify
  113. # the user (See comment in git.py).
  114. # So, we will not take patches adding "user=" support here.
  115. ld = d.createCopy()
  116. # Not necessary to set SRC_URI, since we're passing the URI to
  117. # Fetch.
  118. #ld.setVar('SRC_URI', url)
  119. ld.setVar('SRCREV_%s' % module, subrevision[module])
  120. # Workaround for issues with SRCPV/SRCREV_FORMAT errors
  121. # error refer to 'multiple' repositories. Only the repository
  122. # in the original SRC_URI actually matters...
  123. ld.setVar('SRCPV', d.getVar('SRCPV'))
  124. ld.setVar('SRCREV_FORMAT', module)
  125. function(ud, url, module, paths[module], workdir, ld)
  126. return submodules != []
  127. def need_update(self, ud, d):
  128. if Git.need_update(self, ud, d):
  129. return True
  130. need_update_list = []
  131. def need_update_submodule(ud, url, module, modpath, workdir, d):
  132. url += ";bareclone=1;nobranch=1"
  133. try:
  134. newfetch = Fetch([url], d, cache=False)
  135. new_ud = newfetch.ud[url]
  136. if new_ud.method.need_update(new_ud, d):
  137. need_update_list.append(modpath)
  138. except Exception as e:
  139. logger.error('gitsm: submodule update check failed: %s %s' % (type(e).__name__, str(e)))
  140. need_update_result = True
  141. # If we're using a shallow mirror tarball it needs to be unpacked
  142. # temporarily so that we can examine the .gitmodules file
  143. if ud.shallow and os.path.exists(ud.fullshallow) and not os.path.exists(ud.clonedir):
  144. tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
  145. runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=tmpdir)
  146. self.process_submodules(ud, tmpdir, need_update_submodule, d)
  147. shutil.rmtree(tmpdir)
  148. else:
  149. self.process_submodules(ud, ud.clonedir, need_update_submodule, d)
  150. if need_update_list:
  151. logger.debug('gitsm: Submodules requiring update: %s' % (' '.join(need_update_list)))
  152. return True
  153. return False
  154. def download(self, ud, d):
  155. def download_submodule(ud, url, module, modpath, workdir, d):
  156. url += ";bareclone=1;nobranch=1"
  157. # Is the following still needed?
  158. #url += ";nocheckout=1"
  159. try:
  160. newfetch = Fetch([url], d, cache=False)
  161. newfetch.download()
  162. except Exception as e:
  163. logger.error('gitsm: submodule download failed: %s %s' % (type(e).__name__, str(e)))
  164. raise
  165. Git.download(self, ud, d)
  166. # If we're using a shallow mirror tarball it needs to be unpacked
  167. # temporarily so that we can examine the .gitmodules file
  168. if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
  169. tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
  170. runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=tmpdir)
  171. self.process_submodules(ud, tmpdir, download_submodule, d)
  172. shutil.rmtree(tmpdir)
  173. else:
  174. self.process_submodules(ud, ud.clonedir, download_submodule, d)
  175. def unpack(self, ud, destdir, d):
  176. def unpack_submodules(ud, url, module, modpath, workdir, d):
  177. url += ";bareclone=1;nobranch=1"
  178. # Figure out where we clone over the bare submodules...
  179. if ud.bareclone:
  180. repo_conf = ud.destdir
  181. else:
  182. repo_conf = os.path.join(ud.destdir, '.git')
  183. try:
  184. newfetch = Fetch([url], d, cache=False)
  185. # modpath is needed by unpack tracer to calculate submodule
  186. # checkout dir
  187. new_ud = newfetch.ud[url]
  188. new_ud.modpath = modpath
  189. newfetch.unpack(root=os.path.dirname(os.path.join(repo_conf, 'modules', module)))
  190. except Exception as e:
  191. logger.error('gitsm: submodule unpack failed: %s %s' % (type(e).__name__, str(e)))
  192. raise
  193. local_path = newfetch.localpath(url)
  194. # Correct the submodule references to the local download version...
  195. runfetchcmd("%(basecmd)s config submodule.%(module)s.url %(url)s" % {'basecmd': ud.basecmd, 'module': module, 'url' : local_path}, d, workdir=ud.destdir)
  196. if ud.shallow:
  197. runfetchcmd("%(basecmd)s config submodule.%(module)s.shallow true" % {'basecmd': ud.basecmd, 'module': module}, d, workdir=ud.destdir)
  198. # Ensure the submodule repository is NOT set to bare, since we're checking it out...
  199. try:
  200. runfetchcmd("%s config core.bare false" % (ud.basecmd), d, quiet=True, workdir=os.path.join(repo_conf, 'modules', module))
  201. except:
  202. logger.error("Unable to set git config core.bare to false for %s" % os.path.join(repo_conf, 'modules', module))
  203. raise
  204. Git.unpack(self, ud, destdir, d)
  205. ret = self.process_submodules(ud, ud.destdir, unpack_submodules, d)
  206. if not ud.bareclone and ret:
  207. # All submodules should already be downloaded and configured in the tree. This simply
  208. # sets up the configuration and checks out the files. The main project config should
  209. # remain unmodified, and no download from the internet should occur. As such, lfs smudge
  210. # should also be skipped as these files were already smudged in the fetch stage if lfs
  211. # was enabled.
  212. runfetchcmd("GIT_LFS_SKIP_SMUDGE=1 %s submodule update --recursive --no-fetch" % (ud.basecmd), d, quiet=True, workdir=ud.destdir)
  213. def implicit_urldata(self, ud, d):
  214. import shutil, subprocess, tempfile
  215. urldata = []
  216. def add_submodule(ud, url, module, modpath, workdir, d):
  217. url += ";bareclone=1;nobranch=1"
  218. newfetch = Fetch([url], d, cache=False)
  219. urldata.extend(newfetch.expanded_urldata())
  220. # If we're using a shallow mirror tarball it needs to be unpacked
  221. # temporarily so that we can examine the .gitmodules file
  222. if ud.shallow and os.path.exists(ud.fullshallow) and ud.method.need_update(ud, d):
  223. tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
  224. subprocess.check_call("tar -xzf %s" % ud.fullshallow, cwd=tmpdir, shell=True)
  225. self.process_submodules(ud, tmpdir, add_submodule, d)
  226. shutil.rmtree(tmpdir)
  227. else:
  228. self.process_submodules(ud, ud.clonedir, add_submodule, d)
  229. return urldata