ConfHandler.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env python
  2. # ex:ts=4:sw=4:sts=4:et
  3. # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
  4. """
  5. class for handling configuration data files
  6. Reads a .conf file and obtains its metadata
  7. """
  8. # Copyright (C) 2003, 2004 Chris Larson
  9. # Copyright (C) 2003, 2004 Phil Blundell
  10. #
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License version 2 as
  13. # published by the Free Software Foundation.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License along
  21. # with this program; if not, write to the Free Software Foundation, Inc.,
  22. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  23. import errno
  24. import re
  25. import os
  26. import bb.utils
  27. from bb.parse import ParseError, resolve_file, ast, logger, handle
  28. __config_regexp__ = re.compile( r"""
  29. ^
  30. (?P<exp>export\s*)?
  31. (?P<var>[a-zA-Z0-9\-~_+.${}/]+?)
  32. (\[(?P<flag>[a-zA-Z0-9\-_+.]+)\])?
  33. \s* (
  34. (?P<colon>:=) |
  35. (?P<lazyques>\?\?=) |
  36. (?P<ques>\?=) |
  37. (?P<append>\+=) |
  38. (?P<prepend>=\+) |
  39. (?P<predot>=\.) |
  40. (?P<postdot>\.=) |
  41. =
  42. ) \s*
  43. (?!'[^']*'[^']*'$)
  44. (?!\"[^\"]*\"[^\"]*\"$)
  45. (?P<apo>['\"])
  46. (?P<value>.*)
  47. (?P=apo)
  48. $
  49. """, re.X)
  50. __include_regexp__ = re.compile( r"include\s+(.+)" )
  51. __require_regexp__ = re.compile( r"require\s+(.+)" )
  52. __export_regexp__ = re.compile( r"export\s+([a-zA-Z0-9\-_+.${}/]+)$" )
  53. def init(data):
  54. topdir = data.getVar('TOPDIR', False)
  55. if not topdir:
  56. data.setVar('TOPDIR', os.getcwd())
  57. def supports(fn, d):
  58. return fn[-5:] == ".conf"
  59. def include(parentfn, fn, lineno, data, error_out):
  60. """
  61. error_out: A string indicating the verb (e.g. "include", "inherit") to be
  62. used in a ParseError that will be raised if the file to be included could
  63. not be included. Specify False to avoid raising an error in this case.
  64. """
  65. if parentfn == fn: # prevent infinite recursion
  66. return None
  67. fn = data.expand(fn)
  68. parentfn = data.expand(parentfn)
  69. if not os.path.isabs(fn):
  70. dname = os.path.dirname(parentfn)
  71. bbpath = "%s:%s" % (dname, data.getVar("BBPATH", True))
  72. abs_fn, attempts = bb.utils.which(bbpath, fn, history=True)
  73. if abs_fn and bb.parse.check_dependency(data, abs_fn):
  74. logger.warning("Duplicate inclusion for %s in %s" % (abs_fn, data.getVar('FILE', True)))
  75. for af in attempts:
  76. bb.parse.mark_dependency(data, af)
  77. if abs_fn:
  78. fn = abs_fn
  79. elif bb.parse.check_dependency(data, fn):
  80. logger.warning("Duplicate inclusion for %s in %s" % (fn, data.getVar('FILE', True)))
  81. try:
  82. bb.parse.handle(fn, data, True)
  83. except (IOError, OSError) as exc:
  84. if exc.errno == errno.ENOENT:
  85. if error_out:
  86. raise ParseError("Could not %s file %s" % (error_out, fn), parentfn, lineno)
  87. logger.debug(2, "CONF file '%s' not found", fn)
  88. else:
  89. if error_out:
  90. raise ParseError("Could not %s file %s: %s" % (error_out, fn, exc.strerror), parentfn, lineno)
  91. else:
  92. raise ParseError("Error parsing %s: %s" % (fn, exc.strerror), parentfn, lineno)
  93. # We have an issue where a UI might want to enforce particular settings such as
  94. # an empty DISTRO variable. If configuration files do something like assigning
  95. # a weak default, it turns out to be very difficult to filter out these changes,
  96. # particularly when the weak default might appear half way though parsing a chain
  97. # of configuration files. We therefore let the UIs hook into configuration file
  98. # parsing. This turns out to be a hard problem to solve any other way.
  99. confFilters = []
  100. def handle(fn, data, include):
  101. init(data)
  102. if include == 0:
  103. oldfile = None
  104. else:
  105. oldfile = data.getVar('FILE', False)
  106. abs_fn = resolve_file(fn, data)
  107. f = open(abs_fn, 'r')
  108. if include:
  109. bb.parse.mark_dependency(data, abs_fn)
  110. statements = ast.StatementGroup()
  111. lineno = 0
  112. while True:
  113. lineno = lineno + 1
  114. s = f.readline()
  115. if not s:
  116. break
  117. w = s.strip()
  118. # skip empty lines
  119. if not w:
  120. continue
  121. s = s.rstrip()
  122. while s[-1] == '\\':
  123. s2 = f.readline().strip()
  124. lineno = lineno + 1
  125. if (not s2 or s2 and s2[0] != "#") and s[0] == "#" :
  126. bb.fatal("There is a confusing multiline, partially commented expression on line %s of file %s (%s).\nPlease clarify whether this is all a comment or should be parsed." % (lineno, fn, s))
  127. s = s[:-1] + s2
  128. # skip comments
  129. if s[0] == '#':
  130. continue
  131. feeder(lineno, s, abs_fn, statements)
  132. # DONE WITH PARSING... time to evaluate
  133. data.setVar('FILE', abs_fn)
  134. statements.eval(data)
  135. if oldfile:
  136. data.setVar('FILE', oldfile)
  137. f.close()
  138. for f in confFilters:
  139. f(fn, data)
  140. return data
  141. def feeder(lineno, s, fn, statements):
  142. m = __config_regexp__.match(s)
  143. if m:
  144. groupd = m.groupdict()
  145. ast.handleData(statements, fn, lineno, groupd)
  146. return
  147. m = __include_regexp__.match(s)
  148. if m:
  149. ast.handleInclude(statements, fn, lineno, m, False)
  150. return
  151. m = __require_regexp__.match(s)
  152. if m:
  153. ast.handleInclude(statements, fn, lineno, m, True)
  154. return
  155. m = __export_regexp__.match(s)
  156. if m:
  157. ast.handleExport(statements, fn, lineno, m)
  158. return
  159. raise ParseError("unparsed line: '%s'" % s, fn, lineno);
  160. # Add us to the handlers list
  161. from bb.parse import handlers
  162. handlers.append({'supports': supports, 'handle': handle, 'init': init})
  163. del handlers