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