Add persitent data store from trunk, sync the fetcher changes to use the persistent...
[bitbake.git] / 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 data
91     pd.delDomain("BB_URLDATA")
92     # Make sure our domain exists
93     pd.addDomain("BB_URLDATA")
94
95 # Function call order is usually:
96 #   1. init
97 #   2. go
98 #   3. localpaths
99 # localpath can be called at any time
100
101 def init(urls, d, cache = True):
102     urldata = {}
103
104     if cache:
105         urldata, pd, fn = getdata(d)
106
107     for url in urls:
108         if url not in urldata:
109             ud = FetchData(url, d)
110             for m in methods:
111                 if m.supports(url, ud, d):
112                     ud.init(m, d)
113                     break
114             urldata[url] = ud
115
116     if cache:
117         pd.setValue("BB_URLDATA", fn, pickle.dumps(urldata, 0))
118
119     return urldata
120
121 def getdata(d):
122     urldata = {}
123     fn = bb.data.getVar('FILE', d, 1)
124     pd = persist_data.PersistData(d)
125     encdata = pd.getValue("BB_URLDATA", fn)
126     if encdata:
127         urldata = pickle.loads(str(encdata))
128
129     return urldata, pd, fn
130
131 def go(d, urldata = None):
132     """
133     Fetch all urls
134     """
135     if not urldata:
136         urldata, pd, fn = getdata(d)
137
138     for u in urldata:
139         ud = urldata[u]
140         m = ud.method
141         if ud.localfile and not m.forcefetch(u, ud, d) and os.path.exists(ud.md5):
142             # File already present along with md5 stamp file
143             # Touch md5 file to show activity
144             os.utime(ud.md5, None)
145             continue
146         # RP - is olddir needed?
147         # olddir = os.path.abspath(os.getcwd())
148         m.go(u, ud, d)
149         # os.chdir(olddir)
150         if ud.localfile and not m.forcefetch(u, ud, d):
151             Fetch.write_md5sum(u, ud, d)
152
153 def localpaths(d, urldata = None):
154     """
155     Return a list of the local filenames, assuming successful fetch
156     """
157     local = []
158     if not urldata:
159         urldata, pd, fn = getdata(d)
160
161     for u in urldata:
162         ud = urldata[u]      
163         local.append(ud.localpath)
164
165     return local
166
167 def localpath(url, d, cache = True):
168     """
169     Called from the parser with cache=False since the cache isn't ready 
170     at this point. Also called from classed in OE e.g. patch.bbclass
171     """
172     ud = init([url], d, cache)
173     if ud[url].method:
174         return ud[url].localpath
175     return url
176
177 def runfetchcmd(cmd, d, quiet = False):
178     """
179     Run cmd returning the command output
180     Raise an error if interrupted or cmd fails
181     Optionally echo command output to stdout
182     """
183     bb.msg.debug(1, bb.msg.domain.Fetcher, "Running %s" % cmd)
184
185     # Need to export PATH as binary could be in metadata paths
186     # rather than host provided
187     pathcmd = 'export PATH=%s; %s' % (data.expand('${PATH}', d), cmd)
188
189     stdout_handle = os.popen(pathcmd, "r")
190     output = ""
191
192     while 1:
193         line = stdout_handle.readline()
194         if not line:
195             break
196         if not quiet:
197             print line
198         output += line
199
200     status =  stdout_handle.close() or 0
201     signal = status >> 8
202     exitstatus = status & 0xff
203
204     if signal:
205         raise FetchError("Fetch command %s failed with signal %s, output:\n%s" % pathcmd, signal, output)
206     elif status != 0:
207         raise FetchError("Fetch command %s failed with exit code %s, output:\n%s" % pathcmd, status, output)
208
209     return output
210
211 class FetchData(object):
212     """Class for fetcher variable store"""
213     def __init__(self, url, d):
214         self.localfile = ""
215         (self.type, self.host, self.path, self.user, self.pswd, self.parm) = bb.decodeurl(data.expand(url, d))
216         self.date = Fetch.getSRCDate(self, d)
217         self.url = url
218         self.force = False
219
220     def init(self, method, d):
221         self.method = method
222         self.localpath = method.localpath(self.url, self, d)
223         self.md5 = self.localpath + '.md5'
224         # if user sets localpath for file, use it instead.
225         if "localpath" in self.parm:
226             self.localpath = self.parm["localpath"]
227
228 class Fetch(object):
229     """Base class for 'fetch'ing data"""
230
231     def __init__(self, urls = []):
232         self.urls = []
233
234     def supports(self, url, urldata, d):
235         """
236         Check to see if this fetch class supports a given url.
237         """
238         return 0
239
240     def localpath(self, url, urldata, d):
241         """
242         Return the local filename of a given url assuming a successful fetch.
243         Can also setup variables in urldata for use in go (saving code duplication 
244         and duplicate code execution)
245         """
246         return url
247
248     def setUrls(self, urls):
249         self.__urls = urls
250
251     def getUrls(self):
252         return self.__urls
253
254     urls = property(getUrls, setUrls, None, "Urls property")
255
256     def forcefetch(self, url, urldata, d):
257         """
258         Force a fetch, even if localpath exists?
259         """
260         return False
261
262     def go(self, url, urldata, d):
263         """
264         Fetch urls
265         Assumes localpath was called first
266         """
267         raise NoMethodError("Missing implementation for url")
268
269     def getSRCDate(urldata, d):
270         """
271         Return the SRC Date for the component
272
273         d the bb.data module
274         """
275         if "srcdate" in urldata.parm:
276             return urldata.parm['srcdate']
277
278         pn = data.getVar("PN", d, 1)
279
280         if pn:
281             return data.getVar("SRCDATE_%s" % pn, d, 1) or data.getVar("CVSDATE_%s" % pn, d, 1) or data.getVar("DATE", d, 1)
282
283         return data.getVar("SRCDATE", d, 1) or data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
284     getSRCDate = staticmethod(getSRCDate)
285
286     def try_mirror(d, tarfn):
287         """
288         Try to use a mirrored version of the sources. We do this
289         to avoid massive loads on foreign cvs and svn servers.
290         This method will be used by the different fetcher
291         implementations.
292
293         d Is a bb.data instance
294         tarfn is the name of the tarball
295         """
296         tarpath = os.path.join(data.getVar("DL_DIR", d, 1), tarfn)
297         if os.access(tarpath, os.R_OK):
298             bb.msg.debug(1, bb.msg.domain.Fetcher, "%s already exists, skipping checkout." % tarfn)
299             return True
300
301         pn = data.getVar('PN', d, True)
302         src_tarball_stash = None
303         if pn:
304             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()
305
306         for stash in src_tarball_stash:
307             fetchcmd = data.getVar("FETCHCOMMAND_mirror", d, True) or data.getVar("FETCHCOMMAND_wget", d, True)
308             uri = stash + tarfn
309             bb.msg.note(1, bb.msg.domain.Fetcher, "fetch " + uri)
310             fetchcmd = fetchcmd.replace("${URI}", uri)
311             ret = os.system(fetchcmd)
312             if ret == 0:
313                 bb.msg.note(1, bb.msg.domain.Fetcher, "Fetched %s from tarball stash, skipping checkout" % tarfn)
314                 return True
315         return False
316     try_mirror = staticmethod(try_mirror)
317
318     def verify_md5sum(ud, got_sum):
319         """
320         Verify the md5sum we wanted with the one we got
321         """
322         wanted_sum = None
323         if 'md5sum' in ud.parm:
324             wanted_sum = ud.parm['md5sum']
325         if not wanted_sum:
326             return True
327
328         return wanted_sum == got_sum
329     verify_md5sum = staticmethod(verify_md5sum)
330
331     def write_md5sum(url, ud, d):
332         if bb.which(data.getVar('PATH', d), 'md5sum'):
333             try:
334                 md5pipe = os.popen('md5sum ' + ud.localpath)
335                 md5data = (md5pipe.readline().split() or [ "" ])[0]
336                 md5pipe.close()
337             except OSError:
338                 md5data = ""
339
340         # verify the md5sum
341         if not Fetch.verify_md5sum(ud, md5data):
342             raise MD5SumError(url)
343
344         md5out = file(ud.md5, 'w')
345         md5out.write(md5data)
346         md5out.close()
347     write_md5sum = staticmethod(write_md5sum)
348
349 import cvs
350 import git
351 import local
352 import svn
353 import wget
354 import svk
355 import ssh
356 import perforce
357
358 methods.append(local.Local())
359 methods.append(wget.Wget())
360 methods.append(svn.Svn())
361 methods.append(git.Git())
362 methods.append(cvs.Cvs())
363 methods.append(svk.Svk())
364 methods.append(ssh.SSH())
365 methods.append(perforce.Perforce())