ConfHandler.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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. __unset_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/~]+)$" )
  54. __unset_flag_regexp__ = re.compile( r"unset\s+([a-zA-Z0-9\-_+.${}/~]+)\[([a-zA-Z0-9\-_+.]+)\]$" )
  55. def init(data):
  56. topdir = data.getVar('TOPDIR', False)
  57. if not topdir:
  58. data.setVar('TOPDIR', os.getcwd())
  59. def supports(fn, d):
  60. return fn[-5:] == ".conf"
  61. def include(parentfn, fns, lineno, data, error_out):
  62. """
  63. error_out: A string indicating the verb (e.g. "include", "inherit") to be
  64. used in a ParseError that will be raised if the file to be included could
  65. not be included. Specify False to avoid raising an error in this case.
  66. """
  67. fns = data.expand(fns)
  68. parentfn = data.expand(parentfn)
  69. # "include" or "require" accept zero to n space-separated file names to include.
  70. for fn in fns.split():
  71. include_single_file(parentfn, fn, lineno, data, error_out)
  72. def include_single_file(parentfn, fn, lineno, data, error_out):
  73. """
  74. Helper function for include() which does not expand or split its parameters.
  75. """
  76. if parentfn == fn: # prevent infinite recursion
  77. return None
  78. if not os.path.isabs(fn):
  79. dname = os.path.dirname(parentfn)
  80. bbpath = "%s:%s" % (dname, data.getVar("BBPATH"))
  81. abs_fn, attempts = bb.utils.which(bbpath, fn, history=True)
  82. if abs_fn and bb.parse.check_dependency(data, abs_fn):
  83. logger.warning("Duplicate inclusion for %s in %s" % (abs_fn, data.getVar('FILE')))
  84. for af in attempts:
  85. bb.parse.mark_dependency(data, af)
  86. if abs_fn:
  87. fn = abs_fn
  88. elif bb.parse.check_dependency(data, fn):
  89. logger.warning("Duplicate inclusion for %s in %s" % (fn, data.getVar('FILE')))
  90. try:
  91. bb.parse.handle(fn, data, True)
  92. except (IOError, OSError) as exc:
  93. if exc.errno == errno.ENOENT:
  94. if error_out:
  95. raise ParseError("Could not %s file %s" % (error_out, fn), parentfn, lineno)
  96. logger.debug(2, "CONF file '%s' not found", fn)
  97. else:
  98. if error_out:
  99. raise ParseError("Could not %s file %s: %s" % (error_out, fn, exc.strerror), parentfn, lineno)
  100. else:
  101. raise ParseError("Error parsing %s: %s" % (fn, exc.strerror), parentfn, lineno)
  102. # We have an issue where a UI might want to enforce particular settings such as
  103. # an empty DISTRO variable. If configuration files do something like assigning
  104. # a weak default, it turns out to be very difficult to filter out these changes,
  105. # particularly when the weak default might appear half way though parsing a chain
  106. # of configuration files. We therefore let the UIs hook into configuration file
  107. # parsing. This turns out to be a hard problem to solve any other way.
  108. confFilters = []
  109. def handle(fn, data, include):
  110. init(data)
  111. if include == 0:
  112. oldfile = None
  113. else:
  114. oldfile = data.getVar('FILE', False)
  115. abs_fn = resolve_file(fn, data)
  116. f = open(abs_fn, 'r')
  117. statements = ast.StatementGroup()
  118. lineno = 0
  119. while True:
  120. lineno = lineno + 1
  121. s = f.readline()
  122. if not s:
  123. break
  124. w = s.strip()
  125. # skip empty lines
  126. if not w:
  127. continue
  128. s = s.rstrip()
  129. while s[-1] == '\\':
  130. s2 = f.readline().strip()
  131. lineno = lineno + 1
  132. if (not s2 or s2 and s2[0] != "#") and s[0] == "#" :
  133. 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))
  134. s = s[:-1] + s2
  135. # skip comments
  136. if s[0] == '#':
  137. continue
  138. feeder(lineno, s, abs_fn, statements)
  139. # DONE WITH PARSING... time to evaluate
  140. data.setVar('FILE', abs_fn)
  141. statements.eval(data)
  142. if oldfile:
  143. data.setVar('FILE', oldfile)
  144. f.close()
  145. for f in confFilters:
  146. f(fn, data)
  147. return data
  148. def feeder(lineno, s, fn, statements):
  149. m = __config_regexp__.match(s)
  150. if m:
  151. groupd = m.groupdict()
  152. ast.handleData(statements, fn, lineno, groupd)
  153. return
  154. m = __include_regexp__.match(s)
  155. if m:
  156. ast.handleInclude(statements, fn, lineno, m, False)
  157. return
  158. m = __require_regexp__.match(s)
  159. if m:
  160. ast.handleInclude(statements, fn, lineno, m, True)
  161. return
  162. m = __export_regexp__.match(s)
  163. if m:
  164. ast.handleExport(statements, fn, lineno, m)
  165. return
  166. m = __unset_regexp__.match(s)
  167. if m:
  168. ast.handleUnset(statements, fn, lineno, m)
  169. return
  170. m = __unset_flag_regexp__.match(s)
  171. if m:
  172. ast.handleUnsetFlag(statements, fn, lineno, m)
  173. return
  174. raise ParseError("unparsed line: '%s'" % s, fn, lineno);
  175. # Add us to the handlers list
  176. from bb.parse import handlers
  177. handlers.append({'supports': supports, 'handle': handle, 'init': init})
  178. del handlers