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