bitbake/fetch2: Ensure we only remove files, not directories when fetch failures...
[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             if os.path.isfile(ud.localpath):
435                 bb.utils.remove(ud.localpath)
436             continue
437     return None
438
439 def srcrev_internal_helper(ud, d, name):
440     """
441     Return:
442         a) a source revision if specified
443         b) latest revision if SRCREV="AUTOINC"
444         c) None if not specified
445     """
446
447     if 'rev' in ud.parm:
448         return ud.parm['rev']
449
450     if 'tag' in ud.parm:
451         return ud.parm['tag']
452
453     rev = None
454     if name != '':
455         pn = data.getVar("PN", d, True)
456         rev = data.getVar("SRCREV_%s_pn-%s" % (name, pn), d, True)
457         if not rev:
458             rev = data.getVar("SRCREV_%s" % name, d, True)
459     if not rev:
460         rev = data.getVar("SRCREV", d, True)
461     if rev == "INVALID":
462         raise FetchError("Please set SRCREV to a valid value", ud.url)
463     if rev == "AUTOINC":
464         rev = ud.method.latest_revision(ud.url, ud, d, name)
465
466     return rev
467
468 class FetchData(object):
469     """
470     A class which represents the fetcher state for a given URI.
471     """
472     def __init__(self, url, d):
473         # localpath is the location of a downloaded result. If not set, the file is local.
474         self.localfile = ""
475         self.localpath = None
476         self.lockfile = None
477         self.mirrortarball = None
478         (self.type, self.host, self.path, self.user, self.pswd, self.parm) = decodeurl(data.expand(url, d))
479         self.date = self.getSRCDate(d)
480         self.url = url
481         if not self.user and "user" in self.parm:
482             self.user = self.parm["user"]
483         if not self.pswd and "pswd" in self.parm:
484             self.pswd = self.parm["pswd"]
485         self.setup = False
486
487         if "name" in self.parm:
488             self.md5_name = "%s.md5sum" % self.parm["name"]
489             self.sha256_name = "%s.sha256sum" % self.parm["name"]
490         else:
491             self.md5_name = "md5sum"
492             self.sha256_name = "sha256sum"
493         self.md5_expected = bb.data.getVarFlag("SRC_URI", self.md5_name, d)
494         self.sha256_expected = bb.data.getVarFlag("SRC_URI", self.sha256_name, d)
495
496         self.names = self.parm.get("name",'default').split(',')
497
498         self.method = None
499         for m in methods:
500             if m.supports(url, self, d):
501                 self.method = m
502                 break                
503
504         if not self.method:
505             raise NoMethodError(url)
506
507         if self.method.supports_srcrev():
508             self.revisions = {}
509             for name in self.names:
510                 self.revisions[name] = srcrev_internal_helper(self, d, name)
511
512             # add compatibility code for non name specified case
513             if len(self.names) == 1:
514                 self.revision = self.revisions[self.names[0]]
515
516         if hasattr(self.method, "urldata_init"):
517             self.method.urldata_init(self, d)
518
519         if "localpath" in self.parm:
520             # if user sets localpath for file, use it instead.
521             self.localpath = self.parm["localpath"]
522             self.basename = os.path.basename(self.localpath)
523         elif self.localfile:
524             self.localpath = self.method.localpath(self.url, self, d)
525
526         if self.localfile and self.localpath:
527             # Note: These files should always be in DL_DIR whereas localpath may not be.
528             basepath = bb.data.expand("${DL_DIR}/%s" % os.path.basename(self.localpath), d)
529             self.donestamp = basepath + '.done'
530             self.lockfile = basepath + '.lock'
531
532     def setup_localpath(self, d):
533         if not self.localpath:
534             self.localpath = self.method.localpath(self.url, self, d)
535
536     def getSRCDate(self, d):
537         """
538         Return the SRC Date for the component
539
540         d the bb.data module
541         """
542         if "srcdate" in self.parm:
543             return self.parm['srcdate']
544
545         pn = data.getVar("PN", d, True)
546
547         if pn:
548             return data.getVar("SRCDATE_%s" % pn, d, True) or data.getVar("SRCDATE", d, True) or data.getVar("DATE", d, True)
549
550         return data.getVar("SRCDATE", d, True) or data.getVar("DATE", d, True)
551
552 class FetchMethod(object):
553     """Base class for 'fetch'ing data"""
554
555     def __init__(self, urls = []):
556         self.urls = []
557
558     def supports(self, url, urldata, d):
559         """
560         Check to see if this fetch class supports a given url.
561         """
562         return 0
563
564     def localpath(self, url, urldata, d):
565         """
566         Return the local filename of a given url assuming a successful fetch.
567         Can also setup variables in urldata for use in go (saving code duplication
568         and duplicate code execution)
569         """
570         return os.path.join(data.getVar("DL_DIR", d, True), urldata.localfile)
571
572     def _strip_leading_slashes(self, relpath):
573         """
574         Remove leading slash as os.path.join can't cope
575         """
576         while os.path.isabs(relpath):
577             relpath = relpath[1:]
578         return relpath
579
580     def setUrls(self, urls):
581         self.__urls = urls
582
583     def getUrls(self):
584         return self.__urls
585
586     urls = property(getUrls, setUrls, None, "Urls property")
587
588     def need_update(self, url, ud, d):
589         """
590         Force a fetch, even if localpath exists?
591         """
592         if os.path.exists(ud.localpath):
593             return False
594         return True
595
596     def supports_srcrev(self):
597         """
598         The fetcher supports auto source revisions (SRCREV)
599         """
600         return False
601
602     def download(self, url, urldata, d):
603         """
604         Fetch urls
605         Assumes localpath was called first
606         """
607         raise NoMethodError(url)
608
609     def unpack(self, urldata, rootdir, data):
610         import subprocess
611         file = urldata.localpath
612         dots = file.split(".")
613         if dots[-1] in ['gz', 'bz2', 'Z']:
614             efile = os.path.join(bb.data.getVar('WORKDIR', data, True),os.path.basename('.'.join(dots[0:-1])))
615         else:
616             efile = file
617         cmd = None
618         if file.endswith('.tar'):
619             cmd = 'tar x --no-same-owner -f %s' % file
620         elif file.endswith('.tgz') or file.endswith('.tar.gz') or file.endswith('.tar.Z'):
621             cmd = 'tar xz --no-same-owner -f %s' % file
622         elif file.endswith('.tbz') or file.endswith('.tbz2') or file.endswith('.tar.bz2'):
623             cmd = 'bzip2 -dc %s | tar x --no-same-owner -f -' % file
624         elif file.endswith('.gz') or file.endswith('.Z') or file.endswith('.z'):
625             cmd = 'gzip -dc %s > %s' % (file, efile)
626         elif file.endswith('.bz2'):
627             cmd = 'bzip2 -dc %s > %s' % (file, efile)
628         elif file.endswith('.tar.xz'):
629             cmd = 'xz -dc %s | tar x --no-same-owner -f -' % file
630         elif file.endswith('.xz'):
631             cmd = 'xz -dc %s > %s' % (file, efile)
632         elif file.endswith('.zip') or file.endswith('.jar'):
633             cmd = 'unzip -q -o'
634             if 'dos' in urldata.parm:
635                 cmd = '%s -a' % cmd
636             cmd = "%s '%s'" % (cmd, file)
637         elif os.path.isdir(file):
638             filesdir = os.path.realpath(bb.data.getVar("FILESDIR", data, True))
639             destdir = "."
640             if file[0:len(filesdir)] == filesdir:
641                 destdir = file[len(filesdir):file.rfind('/')]
642                 destdir = destdir.strip('/')
643                 if len(destdir) < 1:
644                     destdir = "."
645                 elif not os.access("%s/%s" % (rootdir, destdir), os.F_OK):
646                     os.makedirs("%s/%s" % (rootdir, destdir))
647             cmd = 'cp -pPR %s %s/%s/' % (file, rootdir, destdir)
648         else:
649             if not 'patch' in urldata.parm:
650                 # The "destdir" handling was specifically done for FILESPATH
651                 # items.  So, only do so for file:// entries.
652                 if urldata.type == "file" and urldata.path.find("/") != -1:
653                     destdir = urldata.path.rsplit("/", 1)[0]
654                 else:
655                     destdir = "."
656                 bb.mkdirhier("%s/%s" % (rootdir, destdir))
657                 cmd = 'cp %s %s/%s/' % (file, rootdir, destdir)
658
659         if not cmd:
660             return
661
662         dest = os.path.join(rootdir, os.path.basename(file))
663         if os.path.exists(dest):
664             if os.path.samefile(file, dest):
665                 return
666
667         # Change to subdir before executing command
668         save_cwd = os.getcwd();
669         os.chdir(rootdir)
670         if 'subdir' in urldata.parm:
671             newdir = ("%s/%s" % (rootdir, urldata.parm['subdir']))
672             bb.mkdirhier(newdir)
673             os.chdir(newdir)
674
675         cmd = "PATH=\"%s\" %s" % (bb.data.getVar('PATH', data, True), cmd)
676         bb.note("Unpacking %s to %s/" % (file, os.getcwd()))
677         ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True)
678
679         os.chdir(save_cwd)
680
681         if ret != 0:
682             raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), urldata.url)
683
684         return
685
686     def try_premirror(self, url, urldata, d):
687         """
688         Should premirrors be used?
689         """
690         return True
691
692     def checkstatus(self, url, urldata, d):
693         """
694         Check the status of a URL
695         Assumes localpath was called first
696         """
697         logger.info("URL %s could not be checked for status since no method exists.", url)
698         return True
699
700     def localcount_internal_helper(ud, d, name):
701         """
702         Return:
703             a) a locked localcount if specified
704             b) None otherwise
705         """
706
707         localcount = None
708         if name != '':
709             pn = data.getVar("PN", d, True)
710             localcount = data.getVar("LOCALCOUNT_" + name, d, True)
711         if not localcount:
712             localcount = data.getVar("LOCALCOUNT", d, True)
713         return localcount
714
715     localcount_internal_helper = staticmethod(localcount_internal_helper)
716
717     def latest_revision(self, url, ud, d, name):
718         """
719         Look in the cache for the latest revision, if not present ask the SCM.
720         """
721         if not hasattr(self, "_latest_revision"):
722             raise ParameterError("The fetcher for this URL does not support _latest_revision", url)
723
724         pd = persist_data.persist(d)
725         revs = pd['BB_URI_HEADREVS']
726         key = self.generate_revision_key(url, ud, d, name)
727         rev = revs[key]
728         if rev != None:
729             return str(rev)
730
731         revs[key] = rev = self._latest_revision(url, ud, d, name)
732         return rev
733
734     def sortable_revision(self, url, ud, d, name):
735         """
736
737         """
738         if hasattr(self, "_sortable_revision"):
739             return self._sortable_revision(url, ud, d)
740
741         pd = persist_data.persist(d)
742         localcounts = pd['BB_URI_LOCALCOUNT']
743         key = self.generate_revision_key(url, ud, d, name)
744
745         latest_rev = self._build_revision(url, ud, d, name)
746         last_rev = localcounts[key + '_rev']
747         uselocalcount = bb.data.getVar("BB_LOCALCOUNT_OVERRIDE", d, True) or False
748         count = None
749         if uselocalcount:
750             count = FetchMethod.localcount_internal_helper(ud, d, name)
751         if count is None:
752             count = localcounts[key + '_count'] or "0"
753
754         if last_rev == latest_rev:
755             return str(count + "+" + latest_rev)
756
757         buildindex_provided = hasattr(self, "_sortable_buildindex")
758         if buildindex_provided:
759             count = self._sortable_buildindex(url, ud, d, latest_rev)
760
761         if count is None:
762             count = "0"
763         elif uselocalcount or buildindex_provided:
764             count = str(count)
765         else:
766             count = str(int(count) + 1)
767
768         localcounts[key + '_rev'] = latest_rev
769         localcounts[key + '_count'] = count
770
771         return str(count + "+" + latest_rev)
772
773     def generate_revision_key(self, url, ud, d, name):
774         key = self._revision_key(url, ud, d, name)
775         return "%s-%s" % (key, bb.data.getVar("PN", d, True) or "")
776
777 class Fetch(object):
778     def __init__(self, urls, d, cache = True):
779         if len(urls) == 0:
780             urls = d.getVar("SRC_URI", True).split()
781         self.urls = urls
782         self.d = d
783         self.ud = {}
784
785         fn = bb.data.getVar('FILE', d, True)
786         if cache and fn in urldata_cache:
787             self.ud = urldata_cache[fn]
788
789         for url in urls:
790             if url not in self.ud:
791                 self.ud[url] = FetchData(url, d)
792
793         if cache:
794             urldata_cache[fn] = self.ud
795
796     def localpath(self, url):
797         if url not in self.urls:
798             self.ud[url] = FetchData(url, self.d)
799
800         self.ud[url].setup_localpath(self.d)
801         return bb.data.expand(self.ud[url].localpath, self.d)
802
803     def localpaths(self):
804         """
805         Return a list of the local filenames, assuming successful fetch
806         """
807         local = []
808
809         for u in self.urls:
810             ud = self.ud[u]
811             ud.setup_localpath(self.d)
812             local.append(ud.localpath)
813
814         return local
815
816     def download(self, urls = []):
817         """
818         Fetch all urls
819         """
820         if len(urls) == 0:
821             urls = self.urls
822
823         for u in urls:
824             ud = self.ud[u]
825             ud.setup_localpath(self.d)
826             m = ud.method
827             localpath = ""
828
829             if not ud.localfile:
830                 continue
831
832             lf = bb.utils.lockfile(ud.lockfile)
833
834             try:
835                 if not m.need_update(u, ud, self.d):
836                     localpath = ud.localpath
837                 elif m.try_premirror(u, ud, self.d):
838                     mirrors = mirror_from_string(bb.data.getVar('PREMIRRORS', self.d, True))
839                     mirrorpath = try_mirrors(self.d, ud, mirrors, False)
840                     if mirrorpath and os.path.basename(mirrorpath) == os.path.basename(ud.localpath):
841                         localpath = mirrorpath
842                     elif mirrorpath and os.path.exists(mirrorpath) and not mirrorpath.startswith(self.d.getVar("DL_DIR", True)):
843                         os.symlink(mirrorpath, os.path.join(self.d.getVar("DL_DIR", True), os.path.basename(mirrorpath)))
844
845                 if bb.data.getVar("BB_FETCH_PREMIRRORONLY", self.d, True) is None:
846                     if not localpath and m.need_update(u, ud, self.d):
847                         try:
848                             m.download(u, ud, self.d)
849                             if hasattr(m, "build_mirror_data"):
850                                 m.build_mirror_data(u, ud, self.d)
851                             localpath = ud.localpath
852
853                         except BBFetchException:
854                             # Remove any incomplete fetch
855                             if os.path.isfile(ud.localpath):
856                                 bb.utils.remove(ud.localpath)
857                             mirrors = mirror_from_string(bb.data.getVar('MIRRORS', self.d, True))
858                             localpath = try_mirrors (self.d, ud, mirrors)
859
860                 if not localpath or not os.path.exists(localpath):
861                     raise FetchError("Unable to fetch URL %s from any source." % u, u)
862
863                 # The local fetcher can return an alternate path so we symlink
864                 if os.path.exists(localpath) and not os.path.exists(ud.localpath):
865                     os.symlink(localpath, ud.localpath)
866
867                 if os.path.exists(ud.donestamp):
868                     # Touch the done stamp file to show active use of the download
869                     try:
870                         os.utime(ud.donestamp, None)
871                     except:
872                         # Errors aren't fatal here
873                         pass
874                 else:
875                     # Only check the checksums if we've not seen this item before, then create the stamp
876                     verify_checksum(u, ud, self.d)
877                     open(ud.donestamp, 'w').close()
878
879             finally:
880                 bb.utils.unlockfile(lf)
881
882     def checkstatus(self, urls = []):
883         """
884         Check all urls exist upstream
885         """
886
887         if len(urls) == 0:
888             urls = self.urls
889
890         for u in urls:
891             ud = self.ud[u]
892             ud.setup_localpath(self.d)
893             m = ud.method
894             logger.debug(1, "Testing URL %s", u)
895             # First try checking uri, u, from PREMIRRORS
896             mirrors = mirror_from_string(bb.data.getVar('PREMIRRORS', self.d, True))
897             ret = try_mirrors(self.d, ud, mirrors, True)
898             if not ret:
899                 # Next try checking from the original uri, u
900                 try:
901                     ret = m.checkstatus(u, ud, self.d)
902                 except:
903                     # Finally, try checking uri, u, from MIRRORS
904                     mirrors = mirror_from_string(bb.data.getVar('MIRRORS', self.d, True))
905                     ret = try_mirrors (self.d, ud, mirrors, True)
906
907             if not ret:
908                 raise FetchError("URL %s doesn't work" % u, u)
909
910     def unpack(self, root, urls = []):
911         """
912         Check all urls exist upstream
913         """
914
915         if len(urls) == 0:
916             urls = self.urls
917
918         for u in urls:
919             ud = self.ud[u]
920             ud.setup_localpath(self.d)
921
922             if bb.data.expand(self.localpath, self.d) is None:
923                 continue
924
925             if ud.lockfile:
926                 lf = bb.utils.lockfile(ud.lockfile)
927
928             ud.method.unpack(ud, root, self.d)
929
930             if ud.lockfile:
931                 bb.utils.unlockfile(lf)
932
933 from . import cvs
934 from . import git
935 from . import local
936 from . import svn
937 from . import wget
938 from . import svk
939 from . import ssh
940 from . import perforce
941 from . import bzr
942 from . import hg
943 from . import osc
944 from . import repo
945
946 methods.append(local.Local())
947 methods.append(wget.Wget())
948 methods.append(svn.Svn())
949 methods.append(git.Git())
950 methods.append(cvs.Cvs())
951 methods.append(svk.Svk())
952 methods.append(ssh.SSH())
953 methods.append(perforce.Perforce())
954 methods.append(bzr.Bzr())
955 methods.append(hg.Hg())
956 methods.append(osc.Osc())
957 methods.append(repo.Repo())