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