ConfHandler.py 6.6 KB

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