utils.py: Improve lock file function error handling (from Poky)
[bitbake.git] / lib / bb / utils.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 Utility Functions
5 """
6
7 # Copyright (C) 2004 Michael Lauer
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License version 2 as
11 # published by the Free Software Foundation.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License along
19 # with this program; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22 digits = "0123456789"
23 ascii_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
24
25 import re, fcntl, os
26
27 def explode_version(s):
28     r = []
29     alpha_regexp = re.compile('^([a-zA-Z]+)(.*)$')
30     numeric_regexp = re.compile('^(\d+)(.*)$')
31     while (s != ''):
32         if s[0] in digits:
33             m = numeric_regexp.match(s)
34             r.append(int(m.group(1)))
35             s = m.group(2)
36             continue
37         if s[0] in ascii_letters:
38             m = alpha_regexp.match(s)
39             r.append(m.group(1))
40             s = m.group(2)
41             continue
42         s = s[1:]
43     return r
44
45 def vercmp_part(a, b):
46     va = explode_version(a)
47     vb = explode_version(b)
48     while True:
49         if va == []:
50             ca = None
51         else:
52             ca = va.pop(0)
53         if vb == []:
54             cb = None
55         else:
56             cb = vb.pop(0)
57         if ca == None and cb == None:
58             return 0
59         if ca > cb:
60             return 1
61         if ca < cb:
62             return -1
63
64 def vercmp(ta, tb):
65     (ea, va, ra) = ta
66     (eb, vb, rb) = tb
67
68     r = int(ea)-int(eb)
69     if (r == 0):
70         r = vercmp_part(va, vb)
71     if (r == 0):
72         r = vercmp_part(ra, rb)
73     return r
74
75 def explode_deps(s):
76     """
77     Take an RDEPENDS style string of format:
78     "DEPEND1 (optional version) DEPEND2 (optional version) ..."
79     and return a list of dependencies.
80     Version information is ignored.
81     """
82     r = []
83     l = s.split()
84     flag = False
85     for i in l:
86         if i[0] == '(':
87             flag = True
88             #j = []
89         if not flag:
90             r.append(i)
91         #else:
92         #    j.append(i)
93         if flag and i.endswith(')'):
94             flag = False
95             # Ignore version
96             #r[-1] += ' ' + ' '.join(j)
97     return r
98
99 def explode_dep_versions(s):
100     """
101     Take an RDEPENDS style string of format:
102     "DEPEND1 (optional version) DEPEND2 (optional version) ..."
103     and return a dictonary of dependencies and versions.
104     """
105     r = {}
106     l = s.split()
107     lastdep = None
108     lastver = ""
109     inversion = False
110     for i in l:
111         if i[0] == '(':
112             inversion = True
113             lastver = i[1:] or ""
114             #j = []
115         elif inversion and i.endswith(')'):
116             inversion = False
117             lastver = lastver + " " + (i[:-1] or "")
118             r[lastdep] = lastver
119         elif not inversion:
120             r[i] = None
121             lastdep = i
122             lastver = ""
123         elif inversion:
124             lastver = lastver + " " + i
125
126     return r
127
128 def _print_trace(body, line):
129     """
130     Print the Environment of a Text Body
131     """
132     import bb
133
134     # print the environment of the method
135     bb.msg.error(bb.msg.domain.Util, "Printing the environment of the function")
136     min_line = max(1,line-4)
137     max_line = min(line+4,len(body)-1)
138     for i in range(min_line,max_line+1):
139         bb.msg.error(bb.msg.domain.Util, "\t%.4d:%s" % (i, body[i-1]) )
140
141
142 def better_compile(text, file, realfile):
143     """
144     A better compile method. This method
145     will print  the offending lines.
146     """
147     try:
148         return compile(text, file, "exec")
149     except Exception, e:
150         import bb,sys
151
152         # split the text into lines again
153         body = text.split('\n')
154         bb.msg.error(bb.msg.domain.Util, "Error in compiling: ", realfile)
155         bb.msg.error(bb.msg.domain.Util, "The lines resulting into this error were:")
156         bb.msg.error(bb.msg.domain.Util, "\t%d:%s:'%s'" % (e.lineno, e.__class__.__name__, body[e.lineno-1]))
157
158         _print_trace(body, e.lineno)
159
160         # exit now
161         sys.exit(1)
162
163 def better_exec(code, context, text, realfile):
164     """
165     Similiar to better_compile, better_exec will
166     print the lines that are responsible for the
167     error.
168     """
169     import bb,sys
170     try:
171         exec code in context
172     except:
173         (t,value,tb) = sys.exc_info()
174
175         if t in [bb.parse.SkipPackage, bb.build.FuncFailed]:
176             raise
177
178         # print the Header of the Error Message
179         bb.msg.error(bb.msg.domain.Util, "Error in executing: %s" % realfile)
180         bb.msg.error(bb.msg.domain.Util, "Exception:%s Message:%s" % (t,value) )
181
182         # let us find the line number now
183         while tb.tb_next:
184             tb = tb.tb_next
185
186         import traceback
187         line = traceback.tb_lineno(tb)
188
189         _print_trace( text.split('\n'), line )
190         
191         raise
192
193 def Enum(*names):
194    """
195    A simple class to give Enum support
196    """
197
198    assert names, "Empty enums are not supported"
199
200    class EnumClass(object):
201       __slots__ = names
202       def __iter__(self):        return iter(constants)
203       def __len__(self):         return len(constants)
204       def __getitem__(self, i):  return constants[i]
205       def __repr__(self):        return 'Enum' + str(names)
206       def __str__(self):         return 'enum ' + str(constants)
207
208    class EnumValue(object):
209       __slots__ = ('__value')
210       def __init__(self, value): self.__value = value
211       Value = property(lambda self: self.__value)
212       EnumType = property(lambda self: EnumType)
213       def __hash__(self):        return hash(self.__value)
214       def __cmp__(self, other):
215          # C fans might want to remove the following assertion
216          # to make all enums comparable by ordinal value {;))
217          assert self.EnumType is other.EnumType, "Only values from the same enum are comparable"
218          return cmp(self.__value, other.__value)
219       def __invert__(self):      return constants[maximum - self.__value]
220       def __nonzero__(self):     return bool(self.__value)
221       def __repr__(self):        return str(names[self.__value])
222
223    maximum = len(names) - 1
224    constants = [None] * len(names)
225    for i, each in enumerate(names):
226       val = EnumValue(i)
227       setattr(EnumClass, each, val)
228       constants[i] = val
229    constants = tuple(constants)
230    EnumType = EnumClass()
231    return EnumType
232
233 def lockfile(name):
234     """
235     Use the file fn as a lock file, return when the lock has been acquired.
236     Returns a variable to pass to unlockfile().
237     """
238     path = os.path.dirname(name)
239     if not os.path.isdir(path):
240         import bb, sys
241         bb.msg.error(bb.msg.domain.Util, "Error, lockfile path does not exist!: %s" % path)
242         sys.exit(1)
243
244     while True:
245         # If we leave the lockfiles lying around there is no problem
246         # but we should clean up after ourselves. This gives potential
247         # for races though. To work around this, when we acquire the lock 
248         # we check the file we locked was still the lock file on disk. 
249         # by comparing inode numbers. If they don't match or the lockfile 
250         # no longer exists, we start again.
251
252         # This implementation is unfair since the last person to request the 
253         # lock is the most likely to win it.
254
255         try:
256             lf = open(name, "a+")
257             fcntl.flock(lf.fileno(), fcntl.LOCK_EX)
258             statinfo = os.fstat(lf.fileno())
259             if os.path.exists(lf.name):
260                statinfo2 = os.stat(lf.name)
261                if statinfo.st_ino == statinfo2.st_ino:
262                    return lf
263             # File no longer exists or changed, retry
264             lf.close
265         except Exception, e:
266             continue
267
268 def unlockfile(lf):
269     """
270     Unlock a file locked using lockfile()                               
271     """
272     os.unlink(lf.name)
273     fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
274     lf.close
275
276 def md5_file(filename):
277     """
278     Return the hex string representation of the MD5 checksum of filename.
279     """
280     try:
281         import hashlib
282         m = hashlib.md5()
283     except ImportError:
284         import md5
285         m = md5.new()
286     
287     for line in open(filename):
288         m.update(line)
289     return m.hexdigest()
290
291 def sha256_file(filename):
292     """
293     Return the hex string representation of the 256-bit SHA checksum of
294     filename.  On Python 2.4 this will return None, so callers will need to
295     handle that by either skipping SHA checks, or running a standalone sha256sum
296     binary.
297     """
298     try:
299         import hashlib
300     except ImportError:
301         return None
302
303     s = hashlib.sha256()
304     for line in open(filename):
305         s.update(line)
306     return s.hexdigest()
307
308 def preserved_envvars_list():
309     return [
310         'BBPATH',
311         'BB_PRESERVE_ENV',
312         'BB_ENV_WHITELIST',
313         'BB_ENV_EXTRAWHITE',
314         'COLORTERM',
315         'DBUS_SESSION_BUS_ADDRESS',
316         'DESKTOP_SESSION',
317         'DESKTOP_STARTUP_ID',
318         'DISPLAY',
319         'GNOME_KEYRING_PID',
320         'GNOME_KEYRING_SOCKET',
321         'GPG_AGENT_INFO',
322         'GTK_RC_FILES',
323         'HOME',
324         'LANG',
325         'LOGNAME',
326         'PATH',
327         'PWD',
328         'SESSION_MANAGER',
329         'SHELL',
330         'SSH_AUTH_SOCK',
331         'TERM',
332         'USER',
333         'USERNAME',
334         '_',
335         'XAUTHORITY',
336         'XDG_DATA_DIRS',
337         'XDG_SESSION_COOKIE',
338     ]
339
340 def filter_environment(good_vars):
341     """
342     Create a pristine environment for bitbake. This will remove variables that
343     are not known and may influence the build in a negative way.
344     """
345
346     import bb
347
348     removed_vars = []
349     for key in os.environ.keys():
350         if key in good_vars:
351             continue
352         
353         removed_vars.append(key)
354         os.unsetenv(key)
355         del os.environ[key]
356
357     if len(removed_vars):
358         bb.debug(1, "Removed the following variables from the environment:", ",".join(removed_vars))
359
360     return removed_vars
361
362 def prunedir(topdir):
363     # Delete everything reachable from the directory named in 'topdir'.
364     # CAUTION:  This is dangerous!
365     for root, dirs, files in os.walk(topdir, topdown=False):
366         for name in files:
367             os.remove(os.path.join(root, name))
368         for name in dirs:
369             if os.path.islink(os.path.join(root, name)):
370                 os.remove(os.path.join(root, name))
371             else:
372                 os.rmdir(os.path.join(root, name))
373     os.rmdir(topdir)
374