ConfHandler.py 5.9 KB

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