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