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