Add bb.utils.lockfile() and bb.utils.unlockfile() from Poky. Use these functions...
[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, 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:
139             if not m.forcefetch(u, ud, d) and os.path.exists(ud.md5):
140                 # File already present along with md5 stamp file
141                 # Touch md5 file to show activity
142                 os.utime(ud.md5, None)
143                 continue
144             lf = bb.utils.lockfile(ud.lockfile)
145             if 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                 bb.utils.unlockfile(lf)
150                 continue
151         m.go(u, ud, d)
152         if ud.localfile:
153             if not m.forcefetch(u, ud, d):
154                 Fetch.write_md5sum(u, ud, d)
155             bb.utils.unlockfile(lf)
156
157 def localpaths(d):
158     """
159     Return a list of the local filenames, assuming successful fetch
160     """
161     local = []
162     urldata = init([], d, True)
163
164     for u in urldata:
165         ud = urldata[u]      
166         local.append(ud.localpath)
167
168     return local
169
170 srcrev_internal_call = False
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
181     #
182     # Ugly code alert. localpath in the fetchers will try to evaluate SRCREV which 
183     # could translate into a call to here. If it does, we need to catch this
184     # and provide some way so it knows get_srcrev is active instead of being
185     # some number etc. hence the srcrev_internal_call tracking and the magic  
186     # "SRCREVINACTION" return value.
187     #
188     # Neater solutions welcome!
189     #
190     if bb.fetch.srcrev_internal_call:
191         return "SRCREVINACTION"
192
193     scms = []
194
195     # Only call setup_localpath on URIs which suppports_srcrev() 
196     urldata = init(bb.data.getVar('SRC_URI', d, 1).split(), d, False)
197     for u in urldata:
198         ud = urldata[u]
199         if ud.method.suppports_srcrev():
200             if not ud.setup:
201                 ud.setup_localpath(d)
202             scms.append(u)
203
204     if len(scms) == 0:
205         bb.msg.error(bb.msg.domain.Fetcher, "SRCREV was used yet no valid SCM was found in SRC_URI")
206         raise ParameterError
207
208     if len(scms) == 1:
209         return urldata[scms[0]].method.sortable_revision(scms[0], urldata[scms[0]], d)
210
211     #
212     # Mutiple SCMs are in SRC_URI so we resort to SRCREV_FORMAT
213     #
214     format = bb.data.getVar('SRCREV_FORMAT', d, 1)
215     if not format:
216         bb.msg.error(bb.msg.domain.Fetcher, "The SRCREV_FORMAT variable must be set when multiple SCMs are used.")
217         raise ParameterError
218
219     for scm in scms:
220         if 'name' in urldata[scm].parm:
221             name = urldata[scm].parm["name"]
222             rev = urldata[scm].method.sortable_revision(scm, urldata[scm], d)
223             format = format.replace(name, rev)
224
225     return format
226
227 def localpath(url, d, cache = True):
228     """
229     Called from the parser with cache=False since the cache isn't ready 
230     at this point. Also called from classed in OE e.g. patch.bbclass
231     """
232     ud = init([url], d)
233     if ud[url].method:
234         return ud[url].localpath
235     return url
236
237 def runfetchcmd(cmd, d, quiet = False):
238     """
239     Run cmd returning the command output
240     Raise an error if interrupted or cmd fails
241     Optionally echo command output to stdout
242     """
243     bb.msg.debug(1, bb.msg.domain.Fetcher, "Running %s" % cmd)
244
245     # Need to export PATH as binary could be in metadata paths
246     # rather than host provided
247     pathcmd = 'export PATH=%s; %s' % (data.expand('${PATH}', d), cmd)
248
249     stdout_handle = os.popen(pathcmd, "r")
250     output = ""
251
252     while 1:
253         line = stdout_handle.readline()
254         if not line:
255             break
256         if not quiet:
257             print line,
258         output += line
259
260     status =  stdout_handle.close() or 0
261     signal = status >> 8
262     exitstatus = status & 0xff
263
264     if signal:
265         raise FetchError("Fetch command %s failed with signal %s, output:\n%s" % (pathcmd, signal, output))
266     elif status != 0:
267         raise FetchError("Fetch command %s failed with exit code %s, output:\n%s" % (pathcmd, status, output))
268
269     return output
270
271 class FetchData(object):
272     """
273     A class which represents the fetcher state for a given URI.
274     """
275     def __init__(self, url, d):
276         self.localfile = ""
277         (self.type, self.host, self.path, self.user, self.pswd, self.parm) = bb.decodeurl(data.expand(url, d))
278         self.date = Fetch.getSRCDate(self, d)
279         self.url = url
280         self.setup = False
281         for m in methods:
282             if m.supports(url, self, d):
283                 self.method = m
284                 break
285
286     def setup_localpath(self, d):
287         self.setup = True
288         if "localpath" in self.parm:
289             # if user sets localpath for file, use it instead.
290             self.localpath = self.parm["localpath"]
291         else:
292             bb.fetch.srcrev_internal_call = True
293             self.localpath = self.method.localpath(self.url, self, d)
294             bb.fetch.srcrev_internal_call = False
295             # We have to clear data's internal caches since the cached value of SRCREV is now wrong.
296             # Horrible...
297             bb.data.delVar("ISHOULDNEVEREXIST", d)
298         self.md5 = self.localpath + '.md5'
299         self.lockfile = self.localpath + '.lock'
300
301
302 class Fetch(object):
303     """Base class for 'fetch'ing data"""
304
305     def __init__(self, urls = []):
306         self.urls = []
307
308     def supports(self, url, urldata, d):
309         """
310         Check to see if this fetch class supports a given url.
311         """
312         return 0
313
314     def localpath(self, url, urldata, d):
315         """
316         Return the local filename of a given url assuming a successful fetch.
317         Can also setup variables in urldata for use in go (saving code duplication 
318         and duplicate code execution)
319         """
320         return url
321
322     def setUrls(self, urls):
323         self.__urls = urls
324
325     def getUrls(self):
326         return self.__urls
327
328     urls = property(getUrls, setUrls, None, "Urls property")
329
330     def forcefetch(self, url, urldata, d):
331         """
332         Force a fetch, even if localpath exists?
333         """
334         return False
335
336     def suppports_srcrev(self):
337         """
338         The fetcher supports auto source revisions (SRCREV)
339         """
340         return False
341
342     def go(self, url, urldata, d):
343         """
344         Fetch urls
345         Assumes localpath was called first
346         """
347         raise NoMethodError("Missing implementation for url")
348
349     def getSRCDate(urldata, d):
350         """
351         Return the SRC Date for the component
352
353         d the bb.data module
354         """
355         if "srcdate" in urldata.parm:
356             return urldata.parm['srcdate']
357
358         pn = data.getVar("PN", d, 1)
359
360         if pn:
361             return data.getVar("SRCDATE_%s" % pn, d, 1) or data.getVar("CVSDATE_%s" % pn, d, 1) or data.getVar("SRCDATE", d, 1) or data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
362
363         return data.getVar("SRCDATE", d, 1) or data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
364     getSRCDate = staticmethod(getSRCDate)
365
366     def srcrev_internal_helper(ud, d):
367         """
368         Return:
369             a) a source revision if specified
370             b) True if auto srcrev is in action
371             c) False otherwise
372         """
373
374         if 'rev' in ud.parm:
375             return ud.parm['rev']
376
377         if 'tag' in ud.parm:
378             return ud.parm['tag']
379
380         rev = None
381         if 'name' in ud.parm:
382             pn = data.getVar("PN", d, 1)
383             rev = data.getVar("SRCREV_pn-" + pn + "_" + ud.parm['name'], d, 1)
384         if not rev:
385             rev = data.getVar("SRCREV", d, 1)
386         if not rev:
387             return False
388         if rev is "SRCREVINACTION":
389             return True
390         return rev
391
392     srcrev_internal_helper = staticmethod(srcrev_internal_helper)
393
394     def try_mirror(d, tarfn):
395         """
396         Try to use a mirrored version of the sources. We do this
397         to avoid massive loads on foreign cvs and svn servers.
398         This method will be used by the different fetcher
399         implementations.
400
401         d Is a bb.data instance
402         tarfn is the name of the tarball
403         """
404         tarpath = os.path.join(data.getVar("DL_DIR", d, 1), tarfn)
405         if os.access(tarpath, os.R_OK):
406             bb.msg.debug(1, bb.msg.domain.Fetcher, "%s already exists, skipping checkout." % tarfn)
407             return True
408
409         pn = data.getVar('PN', d, True)
410         src_tarball_stash = None
411         if pn:
412             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()
413
414         for stash in src_tarball_stash:
415             fetchcmd = data.getVar("FETCHCOMMAND_mirror", d, True) or data.getVar("FETCHCOMMAND_wget", d, True)
416             uri = stash + tarfn
417             bb.msg.note(1, bb.msg.domain.Fetcher, "fetch " + uri)
418             fetchcmd = fetchcmd.replace("${URI}", uri)
419             ret = os.system(fetchcmd)
420             if ret == 0:
421                 bb.msg.note(1, bb.msg.domain.Fetcher, "Fetched %s from tarball stash, skipping checkout" % tarfn)
422                 return True
423         return False
424     try_mirror = staticmethod(try_mirror)
425
426     def verify_md5sum(ud, got_sum):
427         """
428         Verify the md5sum we wanted with the one we got
429         """
430         wanted_sum = None
431         if 'md5sum' in ud.parm:
432             wanted_sum = ud.parm['md5sum']
433         if not wanted_sum:
434             return True
435
436         return wanted_sum == got_sum
437     verify_md5sum = staticmethod(verify_md5sum)
438
439     def write_md5sum(url, ud, d):
440         if bb.which(data.getVar('PATH', d), 'md5sum'):
441             try:
442                 md5pipe = os.popen('md5sum ' + ud.localpath)
443                 md5data = (md5pipe.readline().split() or [ "" ])[0]
444                 md5pipe.close()
445             except OSError:
446                 md5data = ""
447
448         # verify the md5sum
449         if not Fetch.verify_md5sum(ud, md5data):
450             raise MD5SumError(url)
451
452         md5out = file(ud.md5, 'w')
453         md5out.write(md5data)
454         md5out.close()
455     write_md5sum = staticmethod(write_md5sum)
456
457     def latest_revision(self, url, ud, d):
458         """
459         Look in the cache for the latest revision, if not present ask the SCM.
460         """
461         if not hasattr(self, "_latest_revision"):
462             raise ParameterError
463
464         pd = persist_data.PersistData(d)
465         key = self._revision_key(url, ud, d)
466         rev = pd.getValue("BB_URI_HEADREVS", key)
467         if rev != None:
468             return str(rev)
469
470         rev = self._latest_revision(url, ud, d)
471         pd.setValue("BB_URI_HEADREVS", key, rev)
472         return rev
473
474     def sortable_revision(self, url, ud, d):
475         """
476         
477         """
478         if hasattr(self, "_sortable_revision"):
479             return self._sortable_revision(url, ud, d)
480
481         pd = persist_data.PersistData(d)
482         key = self._revision_key(url, ud, d)
483         latest_rev = self._build_revision(url, ud, d)
484         last_rev = pd.getValue("BB_URI_LOCALCOUNT", key + "_rev")
485         count = pd.getValue("BB_URI_LOCALCOUNT", key + "_count")
486
487         if last_rev == latest_rev:
488             return str(count + "+" + latest_rev)
489
490         if count is None:
491             count = "0"
492         else:
493             count = str(int(count) + 1)
494
495         pd.setValue("BB_URI_LOCALCOUNT", key + "_rev", latest_rev)
496         pd.setValue("BB_URI_LOCALCOUNT", key + "_count", count)
497
498         return str(count + "+" + latest_rev)
499
500
501 import cvs
502 import git
503 import local
504 import svn
505 import wget
506 import svk
507 import ssh
508 import perforce
509 import bzr
510 import hg
511
512 methods.append(local.Local())
513 methods.append(wget.Wget())
514 methods.append(svn.Svn())
515 methods.append(git.Git())
516 methods.append(cvs.Cvs())
517 methods.append(svk.Svk())
518 methods.append(ssh.SSH())
519 methods.append(perforce.Perforce())
520 methods.append(bzr.Bzr())
521 methods.append(hg.Hg())