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