Add fixes from trunk to 1.8 branch
[vuplus_bitbake] / lib / bb / fetch / __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 import os, re
28 import bb
29 from   bb import data
30 from   bb import persist_data
31
32 try:
33     import cPickle as pickle
34 except ImportError:
35     import pickle
36
37 class FetchError(Exception):
38     """Exception raised when a download fails"""
39
40 class NoMethodError(Exception):
41     """Exception raised when there is no method to obtain a supplied url or set of urls"""
42
43 class MissingParameterError(Exception):
44     """Exception raised when a fetch method is missing a critical parameter in the url"""
45
46 class ParameterError(Exception):
47     """Exception raised when a url cannot be proccessed due to invalid parameters."""
48
49 class MD5SumError(Exception):
50     """Exception raised when a MD5SUM of a file does not match the expected one"""
51
52 def uri_replace(uri, uri_find, uri_replace, d):
53 #   bb.msg.note(1, bb.msg.domain.Fetcher, "uri_replace: operating on %s" % uri)
54     if not uri or not uri_find or not uri_replace:
55         bb.msg.debug(1, bb.msg.domain.Fetcher, "uri_replace: passed an undefined value, not replacing")
56     uri_decoded = list(bb.decodeurl(uri))
57     uri_find_decoded = list(bb.decodeurl(uri_find))
58     uri_replace_decoded = list(bb.decodeurl(uri_replace))
59     result_decoded = ['','','','','',{}]
60     for i in uri_find_decoded:
61         loc = uri_find_decoded.index(i)
62         result_decoded[loc] = uri_decoded[loc]
63         import types
64         if type(i) == types.StringType:
65             import re
66             if (re.match(i, uri_decoded[loc])):
67                 result_decoded[loc] = re.sub(i, uri_replace_decoded[loc], uri_decoded[loc])
68                 if uri_find_decoded.index(i) == 2:
69                     if d:
70                         localfn = bb.fetch.localpath(uri, d)
71                         if localfn:
72                             result_decoded[loc] = os.path.dirname(result_decoded[loc]) + "/" + os.path.basename(bb.fetch.localpath(uri, d))
73 #                       bb.msg.note(1, bb.msg.domain.Fetcher, "uri_replace: matching %s against %s and replacing with %s" % (i, uri_decoded[loc], uri_replace_decoded[loc]))
74             else:
75 #               bb.msg.note(1, bb.msg.domain.Fetcher, "uri_replace: no match")
76                 return uri
77 #           else:
78 #               for j in i.keys():
79 #                   FIXME: apply replacements against options
80     return bb.encodeurl(result_decoded)
81
82 methods = []
83
84 def fetcher_init(d):
85     """
86     Called to initilize the fetchers once the configuration data is known
87     Calls before this must not hit the cache.
88     """
89     pd = persist_data.PersistData(d)
90     # Clear any cached url data
91     pd.delDomain("BB_URLDATA")
92     # When to drop SCM head revisions should be controled by user policy
93     pd.delDomain("BB_URI_HEADREVS")
94     # Make sure our domains exist
95     pd.addDomain("BB_URLDATA")
96     pd.addDomain("BB_URI_HEADREVS")
97     pd.addDomain("BB_URI_LOCALCOUNT")
98
99 # Function call order is usually:
100 #   1. init
101 #   2. go
102 #   3. localpaths
103 # localpath can be called at any time
104
105 def init(urls, d, cache = True):
106     urldata = {}
107
108     if cache:
109         urldata, pd, fn = getdata(d)
110
111     for url in urls:
112         if url not in urldata:
113             ud = FetchData(url, d)
114             for m in methods:
115                 if m.supports(url, ud, d):
116                     ud.init(m, d)
117                     break
118             urldata[url] = ud
119
120     if cache:
121         pd.setValue("BB_URLDATA", fn, pickle.dumps(urldata, 0))
122
123     return urldata
124
125 def getdata(d):
126     urldata = {}
127     fn = bb.data.getVar('FILE', d, 1)
128     pd = persist_data.PersistData(d)
129     encdata = pd.getValue("BB_URLDATA", fn)
130     if encdata:
131         urldata = pickle.loads(str(encdata))
132
133     return urldata, pd, fn
134
135 def go(d, urldata = None):
136     """
137     Fetch all urls
138     """
139     if not urldata:
140         urldata, pd, fn = getdata(d)
141
142     for u in urldata:
143         ud = urldata[u]
144         m = ud.method
145         if ud.localfile and not m.forcefetch(u, ud, d) and os.path.exists(ud.md5):
146             # File already present along with md5 stamp file
147             # Touch md5 file to show activity
148             os.utime(ud.md5, None)
149             continue
150         # RP - is olddir needed?
151         # olddir = os.path.abspath(os.getcwd())
152         m.go(u, ud, d)
153         # os.chdir(olddir)
154         if ud.localfile and not m.forcefetch(u, ud, d):
155             Fetch.write_md5sum(u, ud, d)
156
157 def localpaths(d, urldata = None):
158     """
159     Return a list of the local filenames, assuming successful fetch
160     """
161     local = []
162     if not urldata:
163         urldata, pd, fn = getdata(d)
164
165     for u in urldata:
166         ud = urldata[u]      
167         local.append(ud.localpath)
168
169     return local
170
171 def get_srcrev(d):
172     """
173     Return the version string for the current package
174     (usually to be used as PV)
175     Most packages usually only have one SCM so we just pass on the call.
176     In the multi SCM case, we build a value based on SRCREV_FORMAT which must 
177     have been set.
178     """
179     urldata, pd, fn = getdata(d)
180     if len(urldata) == 0:
181         src_uri = bb.data.getVar('SRC_URI', d, 1).split(" ")
182         urldata = init(src_uri, d, True)
183
184     scms = []
185     for u in urldata:
186         ud = urldata[u]
187         if ud.method.suppports_srcrev():
188             scms.append(u)
189
190     if len(scms) == 0:
191         bb.msg.error(bb.msg.domain.Fetcher, "SRCREV was used yet no valid SCM was found in SRC_URI")
192         raise ParameterError
193
194     if len(scms) == 1:
195         return ud.method.sortable_revision(scms[0], urldata[scms[0]], d)
196
197     bb.msg.error(bb.msg.domain.Fetcher, "Sorry, support for SRCREV_FORMAT still needs to be written")
198     raise ParameterError
199
200 def localpath(url, d, cache = True):
201     """
202     Called from the parser with cache=False since the cache isn't ready 
203     at this point. Also called from classed in OE e.g. patch.bbclass
204     """
205     ud = init([url], d, cache)
206     if ud[url].method:
207         return ud[url].localpath
208     return url
209
210 def runfetchcmd(cmd, d, quiet = False):
211     """
212     Run cmd returning the command output
213     Raise an error if interrupted or cmd fails
214     Optionally echo command output to stdout
215     """
216     bb.msg.debug(1, bb.msg.domain.Fetcher, "Running %s" % cmd)
217
218     # Need to export PATH as binary could be in metadata paths
219     # rather than host provided
220     pathcmd = 'export PATH=%s; %s' % (data.expand('${PATH}', d), cmd)
221
222     stdout_handle = os.popen(pathcmd, "r")
223     output = ""
224
225     while 1:
226         line = stdout_handle.readline()
227         if not line:
228             break
229         if not quiet:
230             print line
231         output += line
232
233     status =  stdout_handle.close() or 0
234     signal = status >> 8
235     exitstatus = status & 0xff
236
237     if signal:
238         raise FetchError("Fetch command %s failed with signal %s, output:\n%s" % (pathcmd, signal, output))
239     elif status != 0:
240         raise FetchError("Fetch command %s failed with exit code %s, output:\n%s" % (pathcmd, status, output))
241
242     return output
243
244 class FetchData(object):
245     """Class for fetcher variable store"""
246     def __init__(self, url, d):
247         self.localfile = ""
248         (self.type, self.host, self.path, self.user, self.pswd, self.parm) = bb.decodeurl(data.expand(url, d))
249         self.date = Fetch.getSRCDate(self, d)
250         self.url = url
251         self.force = False
252
253     def init(self, method, d):
254         self.method = method
255         self.localpath = method.localpath(self.url, self, d)
256         self.md5 = self.localpath + '.md5'
257         # if user sets localpath for file, use it instead.
258         if "localpath" in self.parm:
259             self.localpath = self.parm["localpath"]
260
261 class Fetch(object):
262     """Base class for 'fetch'ing data"""
263
264     def __init__(self, urls = []):
265         self.urls = []
266
267     def supports(self, url, urldata, d):
268         """
269         Check to see if this fetch class supports a given url.
270         """
271         return 0
272
273     def localpath(self, url, urldata, d):
274         """
275         Return the local filename of a given url assuming a successful fetch.
276         Can also setup variables in urldata for use in go (saving code duplication 
277         and duplicate code execution)
278         """
279         return url
280
281     def setUrls(self, urls):
282         self.__urls = urls
283
284     def getUrls(self):
285         return self.__urls
286
287     urls = property(getUrls, setUrls, None, "Urls property")
288
289     def forcefetch(self, url, urldata, d):
290         """
291         Force a fetch, even if localpath exists?
292         """
293         return False
294
295     def suppports_srcrev(self):
296         """
297         The fetcher supports auto source revisions (SRCREV)
298         """
299         return False
300
301     def go(self, url, urldata, d):
302         """
303         Fetch urls
304         Assumes localpath was called first
305         """
306         raise NoMethodError("Missing implementation for url")
307
308     def getSRCDate(urldata, d):
309         """
310         Return the SRC Date for the component
311
312         d the bb.data module
313         """
314         if "srcdate" in urldata.parm:
315             return urldata.parm['srcdate']
316
317         pn = data.getVar("PN", d, 1)
318
319         if pn:
320             return data.getVar("SRCDATE_%s" % pn, d, 1) or data.getVar("CVSDATE_%s" % pn, d, 1) or data.getVar("DATE", d, 1)
321
322         return data.getVar("SRCDATE", d, 1) or data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
323     getSRCDate = staticmethod(getSRCDate)
324
325     def try_mirror(d, tarfn):
326         """
327         Try to use a mirrored version of the sources. We do this
328         to avoid massive loads on foreign cvs and svn servers.
329         This method will be used by the different fetcher
330         implementations.
331
332         d Is a bb.data instance
333         tarfn is the name of the tarball
334         """
335         tarpath = os.path.join(data.getVar("DL_DIR", d, 1), tarfn)
336         if os.access(tarpath, os.R_OK):
337             bb.msg.debug(1, bb.msg.domain.Fetcher, "%s already exists, skipping checkout." % tarfn)
338             return True
339
340         pn = data.getVar('PN', d, True)
341         src_tarball_stash = None
342         if pn:
343             src_tarball_stash = (data.getVar('SRC_TARBALL_STASH_%s' % pn, d, True) or data.getVar('CVS_TARBALL_STASH_%s' % pn, d, True) or data.getVar('SRC_TARBALL_STASH', d, True) or data.getVar('CVS_TARBALL_STASH', d, True) or "").split()
344
345         for stash in src_tarball_stash:
346             fetchcmd = data.getVar("FETCHCOMMAND_mirror", d, True) or data.getVar("FETCHCOMMAND_wget", d, True)
347             uri = stash + tarfn
348             bb.msg.note(1, bb.msg.domain.Fetcher, "fetch " + uri)
349             fetchcmd = fetchcmd.replace("${URI}", uri)
350             ret = os.system(fetchcmd)
351             if ret == 0:
352                 bb.msg.note(1, bb.msg.domain.Fetcher, "Fetched %s from tarball stash, skipping checkout" % tarfn)
353                 return True
354         return False
355     try_mirror = staticmethod(try_mirror)
356
357     def verify_md5sum(ud, got_sum):
358         """
359         Verify the md5sum we wanted with the one we got
360         """
361         wanted_sum = None
362         if 'md5sum' in ud.parm:
363             wanted_sum = ud.parm['md5sum']
364         if not wanted_sum:
365             return True
366
367         return wanted_sum == got_sum
368     verify_md5sum = staticmethod(verify_md5sum)
369
370     def write_md5sum(url, ud, d):
371         if bb.which(data.getVar('PATH', d), 'md5sum'):
372             try:
373                 md5pipe = os.popen('md5sum ' + ud.localpath)
374                 md5data = (md5pipe.readline().split() or [ "" ])[0]
375                 md5pipe.close()
376             except OSError:
377                 md5data = ""
378
379         # verify the md5sum
380         if not Fetch.verify_md5sum(ud, md5data):
381             raise MD5SumError(url)
382
383         md5out = file(ud.md5, 'w')
384         md5out.write(md5data)
385         md5out.close()
386     write_md5sum = staticmethod(write_md5sum)
387
388     def latest_revision(self, url, ud, d):
389         """
390         Look in the cache for the latest revision, if not present ask the SCM.
391         """
392         if not self._latest_revision:
393             raise ParameterError
394
395         pd = persist_data.PersistData(d)
396         key = self._revision_key(url, ud, d)
397         rev = pd.getValue("BB_URI_HEADREVS", key)
398         if rev != None:
399             return str(rev)
400
401         rev = self._latest_revision(url, ud, d)
402         pd.setValue("BB_URI_HEADREVS", key, rev)
403         return rev
404
405     def sortable_revision(self, url, ud, d):
406         """
407         
408         """
409         if not self._sortable_revision:
410             raise ParameterError
411
412         return self._sortable_revision(url, ud, d)
413
414 import cvs
415 import git
416 import local
417 import svn
418 import wget
419 import svk
420 import ssh
421 import perforce
422
423 methods.append(local.Local())
424 methods.append(wget.Wget())
425 methods.append(svn.Svn())
426 methods.append(git.Git())
427 methods.append(cvs.Cvs())
428 methods.append(svk.Svk())
429 methods.append(ssh.SSH())
430 methods.append(perforce.Perforce())