xref: /openbmc/openbmc/poky/bitbake/lib/bb/fetch2/svn.py (revision 73bd93f1)
1"""
2BitBake 'Fetch' implementation for svn.
3
4"""
5
6# Copyright (C) 2003, 2004  Chris Larson
7# Copyright (C) 2004        Marcin Juszkiewicz
8#
9# SPDX-License-Identifier: GPL-2.0-only
10#
11# Based on functions from the base bb module, Copyright 2003 Holger Schurig
12
13import os
14import bb
15import re
16from   bb.fetch2 import FetchMethod
17from   bb.fetch2 import FetchError
18from   bb.fetch2 import MissingParameterError
19from   bb.fetch2 import runfetchcmd
20from   bb.fetch2 import logger
21
22class Svn(FetchMethod):
23    """Class to fetch a module or modules from svn repositories"""
24    def supports(self, ud, d):
25        """
26        Check to see if a given url can be fetched with svn.
27        """
28        return ud.type in ['svn']
29
30    def urldata_init(self, ud, d):
31        """
32        init svn specific variable within url data
33        """
34        if not "module" in ud.parm:
35            raise MissingParameterError('module', ud.url)
36
37        ud.basecmd = d.getVar("FETCHCMD_svn") or "/usr/bin/env svn --non-interactive --trust-server-cert"
38
39        ud.module = ud.parm["module"]
40
41        if not "path_spec" in ud.parm:
42            ud.path_spec = ud.module
43        else:
44            ud.path_spec = ud.parm["path_spec"]
45
46        # Create paths to svn checkouts
47        svndir = d.getVar("SVNDIR") or (d.getVar("DL_DIR") + "/svn")
48        relpath = self._strip_leading_slashes(ud.path)
49        ud.pkgdir = os.path.join(svndir, ud.host, relpath)
50        ud.moddir = os.path.join(ud.pkgdir, ud.path_spec)
51        # Protects the repository from concurrent updates, e.g. from two
52        # recipes fetching different revisions at the same time
53        ud.svnlock = os.path.join(ud.pkgdir, "svn.lock")
54
55        ud.setup_revisions(d)
56
57        if 'rev' in ud.parm:
58            ud.revision = ud.parm['rev']
59
60        # Whether to use the @REV peg-revision syntax in the svn command or not
61        ud.pegrevision = True
62        if 'nopegrevision' in ud.parm:
63            ud.pegrevision = False
64
65        ud.localfile = d.expand('%s_%s_%s_%s_%s.tar.gz' % (ud.module.replace('/', '.'), ud.host, ud.path.replace('/', '.'), ud.revision, ["0", "1"][ud.pegrevision]))
66
67    def _buildsvncommand(self, ud, d, command):
68        """
69        Build up an svn commandline based on ud
70        command is "fetch", "update", "info"
71        """
72
73        proto = ud.parm.get('protocol', 'svn')
74
75        svn_ssh = None
76        if proto == "svn+ssh" and "ssh" in ud.parm:
77            svn_ssh = ud.parm["ssh"]
78
79        svnroot = ud.host + ud.path
80
81        options = []
82
83        options.append("--no-auth-cache")
84
85        if ud.user:
86            options.append("--username %s" % ud.user)
87
88        if ud.pswd:
89            options.append("--password %s" % ud.pswd)
90
91        if command == "info":
92            svncmd = "%s info %s %s://%s/%s/" % (ud.basecmd, " ".join(options), proto, svnroot, ud.module)
93        elif command == "log1":
94            svncmd = "%s log --limit 1 --quiet %s %s://%s/%s/" % (ud.basecmd, " ".join(options), proto, svnroot, ud.module)
95        else:
96            suffix = ""
97
98            # externals may be either 'allowed' or 'nowarn', but not both.  Allowed
99            # will not issue a warning, but will log to the debug buffer what has likely
100            # been downloaded by SVN.
101            if not ("externals" in ud.parm and ud.parm["externals"] == "allowed"):
102                options.append("--ignore-externals")
103
104            if ud.revision:
105                options.append("-r %s" % ud.revision)
106                if ud.pegrevision:
107                    suffix = "@%s" % (ud.revision)
108
109            if command == "fetch":
110                transportuser = ud.parm.get("transportuser", "")
111                svncmd = "%s co %s %s://%s%s/%s%s %s" % (ud.basecmd, " ".join(options), proto, transportuser, svnroot, ud.module, suffix, ud.path_spec)
112            elif command == "update":
113                svncmd = "%s update %s" % (ud.basecmd, " ".join(options))
114            else:
115                raise FetchError("Invalid svn command %s" % command, ud.url)
116
117        if svn_ssh:
118            svncmd = "SVN_SSH=\"%s\" %s" % (svn_ssh, svncmd)
119
120        return svncmd
121
122    def download(self, ud, d):
123        """Fetch url"""
124
125        logger.debug2("Fetch: checking for module directory '" + ud.moddir + "'")
126
127        lf = bb.utils.lockfile(ud.svnlock)
128
129        try:
130            if os.access(os.path.join(ud.moddir, '.svn'), os.R_OK):
131                svncmd = self._buildsvncommand(ud, d, "update")
132                logger.info("Update " + ud.url)
133                # We need to attempt to run svn upgrade first in case its an older working format
134                try:
135                    runfetchcmd(ud.basecmd + " upgrade", d, workdir=ud.moddir)
136                except FetchError:
137                    pass
138                logger.debug("Running %s", svncmd)
139                bb.fetch2.check_network_access(d, svncmd, ud.url)
140                runfetchcmd(svncmd, d, workdir=ud.moddir)
141            else:
142                svncmd = self._buildsvncommand(ud, d, "fetch")
143                logger.info("Fetch " + ud.url)
144                # check out sources there
145                bb.utils.mkdirhier(ud.pkgdir)
146                logger.debug("Running %s", svncmd)
147                bb.fetch2.check_network_access(d, svncmd, ud.url)
148                runfetchcmd(svncmd, d, workdir=ud.pkgdir)
149
150            if not ("externals" in ud.parm and ud.parm["externals"] == "nowarn"):
151                # Warn the user if this had externals (won't catch them all)
152                output = runfetchcmd("svn propget svn:externals || true", d, workdir=ud.moddir)
153                if output:
154                    if "--ignore-externals" in svncmd.split():
155                        bb.warn("%s contains svn:externals." % ud.url)
156                        bb.warn("These should be added to the recipe SRC_URI as necessary.")
157                        bb.warn("svn fetch has ignored externals:\n%s" % output)
158                        bb.warn("To disable this warning add ';externals=nowarn' to the url.")
159                    else:
160                        bb.debug(1, "svn repository has externals:\n%s" % output)
161
162            scmdata = ud.parm.get("scmdata", "")
163            if scmdata == "keep":
164                tar_flags = ""
165            else:
166                tar_flags = "--exclude='.svn'"
167
168            # tar them up to a defined filename
169            runfetchcmd("tar %s -czf %s %s" % (tar_flags, ud.localpath, ud.path_spec), d,
170                        cleanup=[ud.localpath], workdir=ud.pkgdir)
171        finally:
172            bb.utils.unlockfile(lf)
173
174    def clean(self, ud, d):
175        """ Clean SVN specific files and dirs """
176
177        bb.utils.remove(ud.localpath)
178        bb.utils.remove(ud.moddir, True)
179
180
181    def supports_srcrev(self):
182        return True
183
184    def _revision_key(self, ud, d, name):
185        """
186        Return a unique key for the url
187        """
188        return "svn:" + ud.moddir
189
190    def _latest_revision(self, ud, d, name):
191        """
192        Return the latest upstream revision number
193        """
194        bb.fetch2.check_network_access(d, self._buildsvncommand(ud, d, "log1"), ud.url)
195
196        output = runfetchcmd("LANG=C LC_ALL=C " + self._buildsvncommand(ud, d, "log1"), d, True)
197
198        # skip the first line, as per output of svn log
199        # then we expect the revision on the 2nd line
200        revision = re.search('^r([0-9]*)', output.splitlines()[1]).group(1)
201
202        return revision
203
204    def sortable_revision(self, ud, d, name):
205        """
206        Return a sortable revision number which in our case is the revision number
207        """
208
209        return False, self._build_revision(ud, d)
210
211    def _build_revision(self, ud, d):
212        return ud.revision
213