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