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