bitbake/fetch2: Ensure original ud is preserved in try_mirror
[bitbake.git] / lib / bb / fetch2 / __init__.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 """
4 BitBake 'Fetch' implementations
5
6 Classes for obtaining upstream sources for the
7 BitBake build tools.
8 """
9
10 # Copyright (C) 2003, 2004  Chris Larson
11 #
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License version 2 as
14 # published by the Free Software Foundation.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License along
22 # with this program; if not, write to the Free Software Foundation, Inc.,
23 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #
25 # Based on functions from the base bb module, Copyright 2003 Holger Schurig
26
27 from __future__ import absolute_import
28 from __future__ import print_function
29 import os, re
30 import logging
31 import bb
32 from   bb import data
33 from   bb import persist_data
34 from   bb import utils
35
36 __version__ = "2"
37
38 logger = logging.getLogger("BitBake.Fetcher")
39
40 class BBFetchException(Exception):
41     """Class all fetch exceptions inherit from"""
42     def __init__(self, message):
43          self.msg = message
44          Exception.__init__(self, message)
45
46     def __str__(self):
47          return self.msg
48
49 class MalformedUrl(BBFetchException):
50     """Exception raised when encountering an invalid url"""
51     def __init__(self, url):
52          msg = "The URL: '%s' is invalid and cannot be interpreted" % url
53          self.url = url
54          BBFetchException.__init__(self, msg)
55          self.args = url
56
57 class FetchError(BBFetchException):
58     """General fetcher exception when something happens incorrectly"""
59     def __init__(self, message, url = None):
60          msg = "Fetcher failure for URL: '%s'. %s" % (url, message)
61          self.url = url
62          BBFetchException.__init__(self, msg)
63          self.args = (message, url)
64
65 class UnpackError(BBFetchException):
66     """General fetcher exception when something happens incorrectly when unpacking"""
67     def __init__(self, message, url):
68          msg = "Unpack failure for URL: '%s'. %s" % (url, message)
69          self.url = url
70          BBFetchException.__init__(self, msg)
71          self.args = (message, url)
72
73 class NoMethodError(BBFetchException):
74     """Exception raised when there is no method to obtain a supplied url or set of urls"""
75     def __init__(self, url):
76          msg = "Could not find a fetcher which supports the URL: '%s'" % url
77          self.url = url
78          BBFetchException.__init__(self, msg)
79          self.args = url
80
81 class MissingParameterError(BBFetchException):
82     """Exception raised when a fetch method is missing a critical parameter in the url"""
83     def __init__(self, missing, url):
84          msg = "URL: '%s' is missing the required parameter '%s'" % (url, missing)
85          self.url = url
86          self.missing = missing
87          BBFetchException.__init__(self, msg)
88          self.args = (missing, url)
89
90 class ParameterError(BBFetchException):
91     """Exception raised when a url cannot be proccessed due to invalid parameters."""
92     def __init__(self, message, url):
93          msg = "URL: '%s' has invalid parameters. %s" % (url, message)
94          self.url = url
95          BBFetchException.__init__(self, msg)
96          self.args = (message, url)
97
98 class MD5SumError(BBFetchException):
99     """Exception raised when a MD5 checksum of a file does not match for a downloaded file"""
100     def __init__(self, path, wanted, got, url):
101          msg = "File: '%s' has md5 sum %s when %s was expected (from URL: '%s')" % (path, got, wanted, url)
102          self.url = url
103          self.path = path
104          self.wanted = wanted
105          self.got = got
106          BBFetchException.__init__(self, msg)
107          self.args = (path, wanted, got, url)
108
109 class SHA256SumError(MD5SumError):
110     """Exception raised when a SHA256 checksum of a file does not match for a downloaded file"""
111
112 def decodeurl(url):
113     """Decodes an URL into the tokens (scheme, network location, path,
114     user, password, parameters).
115     """
116
117     m = re.compile('(?P<type>[^:]*)://((?P<user>.+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
118     if not m:
119         raise MalformedUrl(url)
120
121     type = m.group('type')
122     location = m.group('location')
123     if not location:
124         raise MalformedUrl(url)
125     user = m.group('user')
126     parm = m.group('parm')
127
128     locidx = location.find('/')
129     if locidx != -1 and type.lower() != 'file':
130         host = location[:locidx]
131         path = location[locidx:]
132     else:
133         host = ""
134         path = location
135     if user:
136         m = re.compile('(?P<user>[^:]+)(:?(?P<pswd>.*))').match(user)
137         if m:
138             user = m.group('user')
139             pswd = m.group('pswd')
140     else:
141         user = ''
142         pswd = ''
143
144     p = {}
145     if parm:
146         for s in parm.split(';'):
147             s1, s2 = s.split('=')
148             p[s1] = s2
149
150     return (type, host, path, user, pswd, p)
151
152 def encodeurl(decoded):
153     """Encodes a URL from tokens (scheme, network location, path,
154     user, password, parameters).
155     """
156
157     (type, host, path, user, pswd, p) = decoded
158
159     if not path:
160         raise MissingParameterError('path', "encoded from the data %s" % str(decoded))
161     if not type:
162         raise MissingParameterError('type', "encoded from the data %s" % str(decoded))
163     url = '%s://' % type
164     if user and type != "file":
165         url += "%s" % user
166         if pswd:
167             url += ":%s" % pswd
168         url += "@"
169     if host and type != "file":
170         url += "%s" % host
171     url += "%s" % path
172     if p:
173         for parm in p:
174             url += ";%s=%s" % (parm, p[parm])
175
176     return url
177
178 def uri_replace(ud, uri_find, uri_replace, d):
179     if not ud.url or not uri_find or not uri_replace:
180         logger.debug(1, "uri_replace: passed an undefined value, not replacing")
181     uri_decoded = list(decodeurl(ud.url))
182     uri_find_decoded = list(decodeurl(uri_find))
183     uri_replace_decoded = list(decodeurl(uri_replace))
184     result_decoded = ['', '', '', '', '', {}]
185     for i in uri_find_decoded:
186         loc = uri_find_decoded.index(i)
187         result_decoded[loc] = uri_decoded[loc]
188         if isinstance(i, basestring):
189             if (re.match(i, uri_decoded[loc])):
190                 result_decoded[loc] = re.sub(i, uri_replace_decoded[loc], uri_decoded[loc])
191                 if uri_find_decoded.index(i) == 2:
192                     if ud.mirrortarball:
193                         result_decoded[loc] = os.path.join(os.path.dirname(result_decoded[loc]), os.path.basename(ud.mirrortarball))
194                     elif ud.localpath:
195                         result_decoded[loc] = os.path.join(os.path.dirname(result_decoded[loc]), os.path.basename(ud.localpath))
196             else:
197                 return ud.url
198     return encodeurl(result_decoded)
199
200 methods = []
201 urldata_cache = {}
202 saved_headrevs = {}
203
204 def fetcher_init(d):
205     """
206     Called to initialize the fetchers once the configuration data is known.
207     Calls before this must not hit the cache.
208     """
209     pd = persist_data.persist(d)
210     # When to drop SCM head revisions controlled by user policy
211     srcrev_policy = bb.data.getVar('BB_SRCREV_POLICY', d, True) or "clear"
212     if srcrev_policy == "cache":
213         logger.debug(1, "Keeping SRCREV cache due to cache policy of: %s", srcrev_policy)
214     elif srcrev_policy == "clear":
215         logger.debug(1, "Clearing SRCREV cache due to cache policy of: %s", srcrev_policy)
216         try:
217             bb.fetch2.saved_headrevs = pd['BB_URI_HEADREVS'].items()
218         except:
219             pass
220         del pd['BB_URI_HEADREVS']
221     else:
222         raise FetchError("Invalid SRCREV cache policy of: %s" % srcrev_policy)
223
224     for m in methods:
225         if hasattr(m, "init"):
226             m.init(d)
227
228 def fetcher_compare_revisions(d):
229     """
230     Compare the revisions in the persistant cache with current values and
231     return true/false on whether they've changed.
232     """
233
234     pd = persist_data.persist(d)
235     data = pd['BB_URI_HEADREVS'].items()
236     data2 = bb.fetch2.saved_headrevs
237
238     changed = False
239     for key in data:
240         if key not in data2 or data2[key] != data[key]:
241             logger.debug(1, "%s changed", key)
242             changed = True
243             return True
244         else:
245             logger.debug(2, "%s did not change", key)
246     return False
247
248 def mirror_from_string(data):
249     return [ i.split() for i in (data or "").replace('\\n','\n').split('\n') if i ]
250
251 def verify_checksum(u, ud, d):
252     """
253     verify the MD5 and SHA256 checksum for downloaded src
254
255     return value:
256         - True: checksum matched
257         - False: checksum unmatched
258
259     if checksum is missing in recipes file, "BB_STRICT_CHECKSUM" decide the return value.
260     if BB_STRICT_CHECKSUM = "1" then return false as unmatched, otherwise return true as
261     matched
262     """
263
264     if not ud.type in ["http", "https", "ftp", "ftps"]:
265         return
266
267     md5data = bb.utils.md5_file(ud.localpath)
268     sha256data = bb.utils.sha256_file(ud.localpath)
269
270     if (ud.md5_expected == None or ud.sha256_expected == None):
271         logger.warn('Missing SRC_URI checksum for %s, consider adding to the recipe:\n'
272                     'SRC_URI[%s] = "%s"\nSRC_URI[%s] = "%s"',
273                     ud.localpath, ud.md5_name, md5data,
274                     ud.sha256_name, sha256data)
275         if bb.data.getVar("BB_STRICT_CHECKSUM", d, True) == "1":
276             raise FetchError("No checksum specified for %s." % u, u)
277         return
278
279     if ud.md5_expected != md5data:
280         raise MD5SumError(ud.localpath, ud.md5_expected, md5data, u)
281
282     if ud.sha256_expected != sha256data:
283         raise SHA256SumError(ud.localpath, ud.sha256_expected, sha256data, u)
284
285 def subprocess_setup():
286     import signal
287     # Python installs a SIGPIPE handler by default. This is usually not what
288     # non-Python subprocesses expect.
289     # SIGPIPE errors are known issues with gzip/bash
290     signal.signal(signal.SIGPIPE, signal.SIG_DFL)
291
292 def get_autorev(d):
293     #  only not cache src rev in autorev case
294     if bb.data.getVar('BB_SRCREV_POLICY', d, True) != "cache":
295         bb.data.setVar('__BB_DONT_CACHE', '1', d)
296     return "AUTOINC"
297
298 def get_srcrev(d):
299     """
300     Return the version string for the current package
301     (usually to be used as PV)
302     Most packages usually only have one SCM so we just pass on the call.
303     In the multi SCM case, we build a value based on SRCREV_FORMAT which must
304     have been set.
305     """
306
307     scms = []
308     fetcher = Fetch(bb.data.getVar('SRC_URI', d, True).split(), d)
309     urldata = fetcher.ud
310     for u in urldata:
311         if urldata[u].method.supports_srcrev():
312             scms.append(u)
313
314     if len(scms) == 0:
315         raise FetchError("SRCREV was used yet no valid SCM was found in SRC_URI")
316
317     if len(scms) == 1 and len(urldata[scms[0]].names) == 1:
318         return urldata[scms[0]].method.sortable_revision(scms[0], urldata[scms[0]], d, urldata[scms[0]].names[0])
319
320     #
321     # Mutiple SCMs are in SRC_URI so we resort to SRCREV_FORMAT
322     #
323     format = bb.data.getVar('SRCREV_FORMAT', d, True)
324     if not format:
325         raise FetchError("The SRCREV_FORMAT variable must be set when multiple SCMs are used.")
326
327     for scm in scms:
328         ud = urldata[scm]
329         for name in ud.names:
330             rev = ud.method.sortable_revision(scm, ud, d, name)
331             format = format.replace(name, rev)
332
333     return format
334
335 def localpath(url, d):
336     fetcher = bb.fetch2.Fetch([url], d)
337         return fetcher.localpath(url)
338
339 def runfetchcmd(cmd, d, quiet = False, cleanup = []):
340     """
341     Run cmd returning the command output
342     Raise an error if interrupted or cmd fails
343     Optionally echo command output to stdout
344     Optionally remove the files/directories listed in cleanup upon failure
345     """
346
347     # Need to export PATH as binary could be in metadata paths
348     # rather than host provided
349     # Also include some other variables.
350     # FIXME: Should really include all export varaiables?
351     exportvars = ['PATH', 'GIT_PROXY_COMMAND', 'GIT_PROXY_HOST',
352                   'GIT_PROXY_PORT', 'GIT_CONFIG', 'http_proxy', 'ftp_proxy',
353                   'https_proxy', 'no_proxy', 'ALL_PROXY', 'all_proxy',
354                   'SSH_AUTH_SOCK', 'SSH_AGENT_PID', 'HOME']
355
356     for var in exportvars:
357         val = data.getVar(var, d, True)
358         if val:
359             cmd = 'export ' + var + '=\"%s\"; %s' % (val, cmd)
360
361     logger.debug(1, "Running %s", cmd)
362
363     # redirect stderr to stdout
364     stdout_handle = os.popen(cmd + " 2>&1", "r")
365     output = ""
366
367     while True:
368         line = stdout_handle.readline()
369         if not line:
370             break
371         if not quiet:
372             print(line, end=' ')
373         output += line
374
375     status = stdout_handle.close() or 0
376     signal = status >> 8
377     exitstatus = status & 0xff
378
379     if (signal or status != 0):
380         for f in cleanup:
381             try:
382                 bb.utils.remove(f, True)
383             except OSError:
384                 pass
385
386         if signal:
387             raise FetchError("Fetch command %s failed with signal %s, output:\n%s" % (cmd, signal, output))
388         elif status != 0:
389             raise FetchError("Fetch command %s failed with exit code %s, output:\n%s" % (cmd, status, output))
390
391     return output
392
393 def check_network_access(d, info = ""):
394     """
395     log remote network access, and error if BB_NO_NETWORK is set
396     """
397     if bb.data.getVar("BB_NO_NETWORK", d, True) == "1":
398         raise FetchError("BB_NO_NETWORK is set, but the fetcher code attempted network access with the command %s" % info)
399     else:
400         logger.debug(1, "Fetcher accessed the network with the command %s" % info)
401
402 def try_mirrors(d, origud, mirrors, check = False):
403     """
404     Try to use a mirrored version of the sources.
405     This method will be automatically called before the fetchers go.
406
407     d Is a bb.data instance
408     uri is the original uri we're trying to download
409     mirrors is the list of mirrors we're going to try
410     """
411     ld = d.createCopy()
412     for (find, replace) in mirrors:
413         newuri = uri_replace(origud, find, replace, ld)
414         if newuri == origud.url:
415             continue
416         try:
417             ud = FetchData(newuri, ld)
418             ud.setup_localpath(ld)
419
420             if check:
421                 found = ud.method.checkstatus(newuri, ud, ld)
422                 if found:
423                     return found
424             else:
425                 if not ud.method.need_update(newuri, ud, ld):
426                     return ud.localpath
427                 ud.method.download(newuri, ud, ld)
428                 if hasattr(ud.method,"build_mirror_data"):
429                     ud.method.build_mirror_data(newuri, ud, ld)
430                 return ud.localpath
431
432         except bb.fetch2.BBFetchException:
433             logger.debug(1, "Mirror fetch failure for url %s (original url: %s)" % (newuri, origud.url))
434             bb.utils.remove(ud.localpath)
435             continue
436     return None
437
438 def srcrev_internal_helper(ud, d, name):
439     """
440     Return:
441         a) a source revision if specified
442         b) latest revision if SRCREV="AUTOINC"
443         c) None if not specified
444     """
445
446     if 'rev' in ud.parm:
447         return ud.parm['rev']
448
449     if 'tag' in ud.parm:
450         return ud.parm['tag']
451
452     rev = None
453     if name != '':
454         pn = data.getVar("PN", d, True)
455         rev = data.getVar("SRCREV_%s_pn-%s" % (name, pn), d, True)
456         if not rev:
457             rev = data.getVar("SRCREV_%s" % name, d, True)
458     if not rev:
459         rev = data.getVar("SRCREV", d, True)
460     if rev == "INVALID":
461         raise FetchError("Please set SRCREV to a valid value", ud.url)
462     if rev == "AUTOINC":
463         rev = ud.method.latest_revision(ud.url, ud, d, name)
464
465     return rev
466
467 class FetchData(object):
468     """
469     A class which represents the fetcher state for a given URI.
470     """
471     def __init__(self, url, d):
472         # localpath is the location of a downloaded result. If not set, the file is local.
473         self.localfile = ""
474         self.localpath = None
475         self.lockfile = None
476         self.mirrortarball = None
477         (self.type, self.host, self.path, self.user, self.pswd, self.parm) = decodeurl(data.expand(url, d))
478         self.date = self.getSRCDate(d)
479         self.url = url
480         if not self.user and "user" in self.parm:
481             self.user = self.parm["user"]
482         if not self.pswd and "pswd" in self.parm:
483             self.pswd = self.parm["pswd"]
484         self.setup = False
485
486         if "name" in self.parm:
487             self.md5_name = "%s.md5sum" % self.parm["name"]
488             self.sha256_name = "%s.sha256sum" % self.parm["name"]
489         else:
490             self.md5_name = "md5sum"
491             self.sha256_name = "sha256sum"
492         self.md5_expected = bb.data.getVarFlag("SRC_URI", self.md5_name, d)
493         self.sha256_expected = bb.data.getVarFlag("SRC_URI", self.sha256_name, d)
494
495         self.names = self.parm.get("name",'default').split(',')
496
497         self.method = None
498         for m in methods:
499             if m.supports(url, self, d):
500                 self.method = m
501                 break                
502
503         if not self.method:
504             raise NoMethodError(url)
505
506         if self.method.supports_srcrev():
507             self.revisions = {}
508             for name in self.names:
509                 self.revisions[name] = srcrev_internal_helper(self, d, name)
510
511             # add compatibility code for non name specified case
512             if len(self.names) == 1:
513                 self.revision = self.revisions[self.names[0]]
514
515         if hasattr(self.method, "urldata_init"):
516             self.method.urldata_init(self, d)
517
518         if "localpath" in self.parm:
519             # if user sets localpath for file, use it instead.
520             self.localpath = self.parm["localpath"]
521             self.basename = os.path.basename(self.localpath)
522         elif self.localfile:
523             self.localpath = self.method.localpath(self.url, self, d)
524
525         if self.localfile and self.localpath:
526             # Note: These files should always be in DL_DIR whereas localpath may not be.
527             basepath = bb.data.expand("${DL_DIR}/%s" % os.path.basename(self.localpath), d)
528             self.donestamp = basepath + '.done'
529             self.lockfile = basepath + '.lock'
530
531     def setup_localpath(self, d):
532         if not self.localpath:
533             self.localpath = self.method.localpath(self.url, self, d)
534
535     def getSRCDate(self, d):
536         """
537         Return the SRC Date for the component
538
539         d the bb.data module
540         """
541         if "srcdate" in self.parm:
542             return self.parm['srcdate']
543
544         pn = data.getVar("PN", d, True)
545
546         if pn:
547             return data.getVar("SRCDATE_%s" % pn, d, True) or data.getVar("SRCDATE", d, True) or data.getVar("DATE", d, True)
548
549         return data.getVar("SRCDATE", d, True) or data.getVar("DATE", d, True)
550
551 class FetchMethod(object):
552     """Base class for 'fetch'ing data"""
553
554     def __init__(self, urls = []):
555         self.urls = []
556
557     def supports(self, url, urldata, d):
558         """
559         Check to see if this fetch class supports a given url.
560         """
561         return 0
562
563     def localpath(self, url, urldata, d):
564         """
565         Return the local filename of a given url assuming a successful fetch.
566         Can also setup variables in urldata for use in go (saving code duplication
567         and duplicate code execution)
568         """
569         return os.path.join(data.getVar("DL_DIR", d, True), urldata.localfile)
570
571     def _strip_leading_slashes(self, relpath):
572         """
573         Remove leading slash as os.path.join can't cope
574         """
575         while os.path.isabs(relpath):
576             relpath = relpath[1:]
577         return relpath
578
579     def setUrls(self, urls):
580         self.__urls = urls
581
582     def getUrls(self):
583         return self.__urls
584
585     urls = property(getUrls, setUrls, None, "Urls property")
586
587     def need_update(self, url, ud, d):
588         """
589         Force a fetch, even if localpath exists?
590         """
591         if os.path.exists(ud.localpath):
592             return False
593         return True
594
595     def supports_srcrev(self):
596         """
597         The fetcher supports auto source revisions (SRCREV)
598         """
599         return False
600
601     def download(self, url, urldata, d):
602         """
603         Fetch urls
604         Assumes localpath was called first
605         """
606         raise NoMethodError(url)
607
608     def unpack(self, urldata, rootdir, data):
609         import subprocess
610         file = urldata.localpath
611         dots = file.split(".")
612         if dots[-1] in ['gz', 'bz2', 'Z']:
613             efile = os.path.join(bb.data.getVar('WORKDIR', data, True),os.path.basename('.'.join(dots[0:-1])))
614         else:
615             efile = file
616         cmd = None
617         if file.endswith('.tar'):
618             cmd = 'tar x --no-same-owner -f %s' % file
619         elif file.endswith('.tgz') or file.endswith('.tar.gz') or file.endswith('.tar.Z'):
620             cmd = 'tar xz --no-same-owner -f %s' % file
621         elif file.endswith('.tbz') or file.endswith('.tbz2') or file.endswith('.tar.bz2'):
622             cmd = 'bzip2 -dc %s | tar x --no-same-owner -f -' % file
623         elif file.endswith('.gz') or file.endswith('.Z') or file.endswith('.z'):
624             cmd = 'gzip -dc %s > %s' % (file, efile)
625         elif file.endswith('.bz2'):
626             cmd = 'bzip2 -dc %s > %s' % (file, efile)
627         elif file.endswith('.tar.xz'):
628             cmd = 'xz -dc %s | tar x --no-same-owner -f -' % file
629         elif file.endswith('.xz'):
630             cmd = 'xz -dc %s > %s' % (file, efile)
631         elif file.endswith('.zip') or file.endswith('.jar'):
632             cmd = 'unzip -q -o'
633             if 'dos' in urldata.parm:
634                 cmd = '%s -a' % cmd
635             cmd = "%s '%s'" % (cmd, file)
636         elif os.path.isdir(file):
637             filesdir = os.path.realpath(bb.data.getVar("FILESDIR", data, True))
638             destdir = "."
639             if file[0:len(filesdir)] == filesdir:
640                 destdir = file[len(filesdir):file.rfind('/')]
641                 destdir = destdir.strip('/')
642                 if len(destdir) < 1:
643                     destdir = "."
644                 elif not os.access("%s/%s" % (rootdir, destdir), os.F_OK):
645                     os.makedirs("%s/%s" % (rootdir, destdir))
646             cmd = 'cp -pPR %s %s/%s/' % (file, rootdir, destdir)
647         else:
648             if not 'patch' in urldata.parm:
649                 # The "destdir" handling was specifically done for FILESPATH
650                 # items.  So, only do so for file:// entries.
651                 if urldata.type == "file" and urldata.path.find("/") != -1:
652                     destdir = urldata.path.rsplit("/", 1)[0]
653                 else:
654                     destdir = "."
655                 bb.mkdirhier("%s/%s" % (rootdir, destdir))
656                 cmd = 'cp %s %s/%s/' % (file, rootdir, destdir)
657
658         if not cmd:
659             return
660
661         dest = os.path.join(rootdir, os.path.basename(file))
662         if os.path.exists(dest):
663             if os.path.samefile(file, dest):
664                 return
665
666         # Change to subdir before executing command
667         save_cwd = os.getcwd();
668         os.chdir(rootdir)
669         if 'subdir' in urldata.parm:
670             newdir = ("%s/%s" % (rootdir, urldata.parm['subdir']))
671             bb.mkdirhier(newdir)
672             os.chdir(newdir)
673
674         cmd = "PATH=\"%s\" %s" % (bb.data.getVar('PATH', data, True), cmd)
675         bb.note("Unpacking %s to %s/" % (file, os.getcwd()))
676         ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True)
677
678         os.chdir(save_cwd)
679
680         if ret != 0:
681             raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), urldata.url)
682
683         return
684
685     def try_premirror(self, url, urldata, d):
686         """
687         Should premirrors be used?
688         """
689         return True
690
691     def checkstatus(self, url, urldata, d):
692         """
693         Check the status of a URL
694         Assumes localpath was called first
695         """
696         logger.info("URL %s could not be checked for status since no method exists.", url)
697         return True
698
699     def localcount_internal_helper(ud, d, name):
700         """
701         Return:
702             a) a locked localcount if specified
703             b) None otherwise
704         """
705
706         localcount = None
707         if name != '':
708             pn = data.getVar("PN", d, True)
709             localcount = data.getVar("LOCALCOUNT_" + name, d, True)
710         if not localcount:
711             localcount = data.getVar("LOCALCOUNT", d, True)
712         return localcount
713
714     localcount_internal_helper = staticmethod(localcount_internal_helper)
715
716     def latest_revision(self, url, ud, d, name):
717         """
718         Look in the cache for the latest revision, if not present ask the SCM.
719         """
720         if not hasattr(self, "_latest_revision"):
721             raise ParameterError("The fetcher for this URL does not support _latest_revision", url)
722
723         pd = persist_data.persist(d)
724         revs = pd['BB_URI_HEADREVS']
725         key = self.generate_revision_key(url, ud, d, name)
726         rev = revs[key]
727         if rev != None:
728             return str(rev)
729
730         revs[key] = rev = self._latest_revision(url, ud, d, name)
731         return rev
732
733     def sortable_revision(self, url, ud, d, name):
734         """
735
736         """
737         if hasattr(self, "_sortable_revision"):
738             return self._sortable_revision(url, ud, d)
739
740         pd = persist_data.persist(d)
741         localcounts = pd['BB_URI_LOCALCOUNT']
742         key = self.generate_revision_key(url, ud, d, name)
743
744         latest_rev = self._build_revision(url, ud, d, name)
745         last_rev = localcounts[key + '_rev']
746         uselocalcount = bb.data.getVar("BB_LOCALCOUNT_OVERRIDE", d, True) or False
747         count = None
748         if uselocalcount:
749             count = FetchMethod.localcount_internal_helper(ud, d, name)
750         if count is None:
751             count = localcounts[key + '_count'] or "0"
752
753         if last_rev == latest_rev:
754             return str(count + "+" + latest_rev)
755
756         buildindex_provided = hasattr(self, "_sortable_buildindex")
757         if buildindex_provided:
758             count = self._sortable_buildindex(url, ud, d, latest_rev)
759
760         if count is None:
761             count = "0"
762         elif uselocalcount or buildindex_provided:
763             count = str(count)
764         else:
765             count = str(int(count) + 1)
766
767         localcounts[key + '_rev'] = latest_rev
768         localcounts[key + '_count'] = count
769
770         return str(count + "+" + latest_rev)
771
772     def generate_revision_key(self, url, ud, d, name):
773         key = self._revision_key(url, ud, d, name)
774         return "%s-%s" % (key, bb.data.getVar("PN", d, True) or "")
775
776 class Fetch(object):
777     def __init__(self, urls, d, cache = True):
778         if len(urls) == 0:
779             urls = d.getVar("SRC_URI", True).split()
780         self.urls = urls
781         self.d = d
782         self.ud = {}
783
784         fn = bb.data.getVar('FILE', d, True)
785         if cache and fn in urldata_cache:
786             self.ud = urldata_cache[fn]
787
788         for url in urls:
789             if url not in self.ud:
790                 self.ud[url] = FetchData(url, d)
791
792         if cache:
793             urldata_cache[fn] = self.ud
794
795     def localpath(self, url):
796         if url not in self.urls:
797             self.ud[url] = FetchData(url, self.d)
798
799         self.ud[url].setup_localpath(self.d)
800         return bb.data.expand(self.ud[url].localpath, self.d)
801
802     def localpaths(self):
803         """
804         Return a list of the local filenames, assuming successful fetch
805         """
806         local = []
807
808         for u in self.urls:
809             ud = self.ud[u]
810             ud.setup_localpath(self.d)
811             local.append(ud.localpath)
812
813         return local
814
815     def download(self, urls = []):
816         """
817         Fetch all urls
818         """
819         if len(urls) == 0:
820             urls = self.urls
821
822         for u in urls:
823             ud = self.ud[u]
824             ud.setup_localpath(self.d)
825             m = ud.method
826             localpath = ""
827
828             if not ud.localfile:
829                 continue
830
831             lf = bb.utils.lockfile(ud.lockfile)
832
833             try:
834                 if not m.need_update(u, ud, self.d):
835                     localpath = ud.localpath
836                 elif m.try_premirror(u, ud, self.d):
837                     mirrors = mirror_from_string(bb.data.getVar('PREMIRRORS', self.d, True))
838                     mirrorpath = try_mirrors(self.d, ud, mirrors, False)
839                     if mirrorpath and os.path.basename(mirrorpath) == os.path.basename(ud.localpath):
840                         localpath = mirrorpath
841                     elif mirrorpath and os.path.exists(mirrorpath) and not mirrorpath.startswith(self.d.getVar("DL_DIR", True)):
842                         os.symlink(mirrorpath, os.path.join(self.d.getVar("DL_DIR", True), os.path.basename(mirrorpath)))
843
844                 if bb.data.getVar("BB_FETCH_PREMIRRORONLY", self.d, True) is None:
845                     if not localpath and m.need_update(u, ud, self.d):
846                         try:
847                             m.download(u, ud, self.d)
848                             if hasattr(m, "build_mirror_data"):
849                                 m.build_mirror_data(u, ud, self.d)
850                             localpath = ud.localpath
851
852                         except BBFetchException:
853                             # Remove any incomplete file
854                             bb.utils.remove(ud.localpath)
855                             mirrors = mirror_from_string(bb.data.getVar('MIRRORS', self.d, True))
856                             localpath = try_mirrors (self.d, ud, mirrors)
857
858                 if not localpath or not os.path.exists(localpath):
859                     raise FetchError("Unable to fetch URL %s from any source." % u, u)
860
861                 # The local fetcher can return an alternate path so we symlink
862                 if os.path.exists(localpath) and not os.path.exists(ud.localpath):
863                     os.symlink(localpath, ud.localpath)
864
865                 if os.path.exists(ud.donestamp):
866                     # Touch the done stamp file to show active use of the download
867                     try:
868                         os.utime(ud.donestamp, None)
869                     except:
870                         # Errors aren't fatal here
871                         pass
872                 else:
873                     # Only check the checksums if we've not seen this item before, then create the stamp
874                     verify_checksum(u, ud, self.d)
875                     open(ud.donestamp, 'w').close()
876
877             finally:
878                 bb.utils.unlockfile(lf)
879
880     def checkstatus(self, urls = []):
881         """
882         Check all urls exist upstream
883         """
884
885         if len(urls) == 0:
886             urls = self.urls
887
888         for u in urls:
889             ud = self.ud[u]
890             ud.setup_localpath(self.d)
891             m = ud.method
892             logger.debug(1, "Testing URL %s", u)
893             # First try checking uri, u, from PREMIRRORS
894             mirrors = mirror_from_string(bb.data.getVar('PREMIRRORS', self.d, True))
895             ret = try_mirrors(self.d, ud, mirrors, True)
896             if not ret:
897                 # Next try checking from the original uri, u
898                 try:
899                     ret = m.checkstatus(u, ud, self.d)
900                 except:
901                     # Finally, try checking uri, u, from MIRRORS
902                     mirrors = mirror_from_string(bb.data.getVar('MIRRORS', self.d, True))
903                     ret = try_mirrors (self.d, ud, mirrors, True)
904
905             if not ret:
906                 raise FetchError("URL %s doesn't work" % u, u)
907
908     def unpack(self, root, urls = []):
909         """
910         Check all urls exist upstream
911         """
912
913         if len(urls) == 0:
914             urls = self.urls
915
916         for u in urls:
917             ud = self.ud[u]
918             ud.setup_localpath(self.d)
919
920             if bb.data.expand(self.localpath, self.d) is None:
921                 continue
922
923             if ud.lockfile:
924                 lf = bb.utils.lockfile(ud.lockfile)
925
926             ud.method.unpack(ud, root, self.d)
927
928             if ud.lockfile:
929                 bb.utils.unlockfile(lf)
930
931 from . import cvs
932 from . import git
933 from . import local
934 from . import svn
935 from . import wget
936 from . import svk
937 from . import ssh
938 from . import perforce
939 from . import bzr
940 from . import hg
941 from . import osc
942 from . import repo
943
944 methods.append(local.Local())
945 methods.append(wget.Wget())
946 methods.append(svn.Svn())
947 methods.append(git.Git())
948 methods.append(cvs.Cvs())
949 methods.append(svk.Svk())
950 methods.append(ssh.SSH())
951 methods.append(perforce.Perforce())
952 methods.append(bzr.Bzr())
953 methods.append(hg.Hg())
954 methods.append(osc.Osc())
955 methods.append(repo.Repo())