1""" 2 class for handling .bb files 3 4 Reads a .bb file and obtains its metadata 5 6""" 7 8 9# Copyright (C) 2003, 2004 Chris Larson 10# Copyright (C) 2003, 2004 Phil Blundell 11# 12# SPDX-License-Identifier: GPL-2.0-only 13# 14 15import re, bb, os 16import logging 17import bb.build, bb.utils 18from bb import data 19 20from . import ConfHandler 21from .. import resolve_file, ast, logger, ParseError 22from .ConfHandler import include, init 23 24# For compatibility 25bb.deprecate_import(__name__, "bb.parse", ["vars_from_file"]) 26 27__func_start_regexp__ = re.compile(r"(((?P<py>python)|(?P<fr>fakeroot))\s*)*(?P<func>[\w\.\-\+\{\}\$]+)?\s*\(\s*\)\s*{$" ) 28__inherit_regexp__ = re.compile(r"inherit\s+(.+)" ) 29__export_func_regexp__ = re.compile(r"EXPORT_FUNCTIONS\s+(.+)" ) 30__addtask_regexp__ = re.compile(r"addtask\s+(?P<func>\w+)\s*((before\s*(?P<before>((.*(?=after))|(.*))))|(after\s*(?P<after>((.*(?=before))|(.*)))))*") 31__deltask_regexp__ = re.compile(r"deltask\s+(?P<func>\w+)(?P<ignores>.*)") 32__addhandler_regexp__ = re.compile(r"addhandler\s+(.+)" ) 33__def_regexp__ = re.compile(r"def\s+(\w+).*:" ) 34__python_func_regexp__ = re.compile(r"(\s+.*)|(^$)|(^#)" ) 35__python_tab_regexp__ = re.compile(r" *\t") 36 37__infunc__ = [] 38__inpython__ = False 39__body__ = [] 40__classname__ = "" 41 42cached_statements = {} 43 44def supports(fn, d): 45 """Return True if fn has a supported extension""" 46 return os.path.splitext(fn)[-1] in [".bb", ".bbclass", ".inc"] 47 48def inherit(files, fn, lineno, d): 49 __inherit_cache = d.getVar('__inherit_cache', False) or [] 50 files = d.expand(files).split() 51 for file in files: 52 if not os.path.isabs(file) and not file.endswith(".bbclass"): 53 file = os.path.join('classes', '%s.bbclass' % file) 54 55 if not os.path.isabs(file): 56 bbpath = d.getVar("BBPATH") 57 abs_fn, attempts = bb.utils.which(bbpath, file, history=True) 58 for af in attempts: 59 if af != abs_fn: 60 bb.parse.mark_dependency(d, af) 61 if abs_fn: 62 file = abs_fn 63 64 if not file in __inherit_cache: 65 logger.debug(1, "Inheriting %s (from %s:%d)" % (file, fn, lineno)) 66 __inherit_cache.append( file ) 67 d.setVar('__inherit_cache', __inherit_cache) 68 include(fn, file, lineno, d, "inherit") 69 __inherit_cache = d.getVar('__inherit_cache', False) or [] 70 71def get_statements(filename, absolute_filename, base_name): 72 global cached_statements 73 74 try: 75 return cached_statements[absolute_filename] 76 except KeyError: 77 with open(absolute_filename, 'r') as f: 78 statements = ast.StatementGroup() 79 80 lineno = 0 81 while True: 82 lineno = lineno + 1 83 s = f.readline() 84 if not s: break 85 s = s.rstrip() 86 feeder(lineno, s, filename, base_name, statements) 87 88 if __inpython__: 89 # add a blank line to close out any python definition 90 feeder(lineno, "", filename, base_name, statements, eof=True) 91 92 if filename.endswith(".bbclass") or filename.endswith(".inc"): 93 cached_statements[absolute_filename] = statements 94 return statements 95 96def handle(fn, d, include): 97 global __func_start_regexp__, __inherit_regexp__, __export_func_regexp__, __addtask_regexp__, __addhandler_regexp__, __infunc__, __body__, __residue__, __classname__ 98 __body__ = [] 99 __infunc__ = [] 100 __classname__ = "" 101 __residue__ = [] 102 103 base_name = os.path.basename(fn) 104 (root, ext) = os.path.splitext(base_name) 105 init(d) 106 107 if ext == ".bbclass": 108 __classname__ = root 109 __inherit_cache = d.getVar('__inherit_cache', False) or [] 110 if not fn in __inherit_cache: 111 __inherit_cache.append(fn) 112 d.setVar('__inherit_cache', __inherit_cache) 113 114 if include != 0: 115 oldfile = d.getVar('FILE', False) 116 else: 117 oldfile = None 118 119 abs_fn = resolve_file(fn, d) 120 121 # actual loading 122 statements = get_statements(fn, abs_fn, base_name) 123 124 # DONE WITH PARSING... time to evaluate 125 if ext != ".bbclass" and abs_fn != oldfile: 126 d.setVar('FILE', abs_fn) 127 128 try: 129 statements.eval(d) 130 except bb.parse.SkipRecipe: 131 d.setVar("__SKIPPED", True) 132 if include == 0: 133 return { "" : d } 134 135 if __infunc__: 136 raise ParseError("Shell function %s is never closed" % __infunc__[0], __infunc__[1], __infunc__[2]) 137 if __residue__: 138 raise ParseError("Leftover unparsed (incomplete?) data %s from %s" % __residue__, fn) 139 140 if ext != ".bbclass" and include == 0: 141 return ast.multi_finalize(fn, d) 142 143 if ext != ".bbclass" and oldfile and abs_fn != oldfile: 144 d.setVar("FILE", oldfile) 145 146 return d 147 148def feeder(lineno, s, fn, root, statements, eof=False): 149 global __func_start_regexp__, __inherit_regexp__, __export_func_regexp__, __addtask_regexp__, __addhandler_regexp__, __def_regexp__, __python_func_regexp__, __inpython__, __infunc__, __body__, bb, __residue__, __classname__ 150 151 # Check tabs in python functions: 152 # - def py_funcname(): covered by __inpython__ 153 # - python(): covered by '__anonymous' == __infunc__[0] 154 # - python funcname(): covered by __infunc__[3] 155 if __inpython__ or (__infunc__ and ('__anonymous' == __infunc__[0] or __infunc__[3])): 156 tab = __python_tab_regexp__.match(s) 157 if tab: 158 bb.warn('python should use 4 spaces indentation, but found tabs in %s, line %s' % (root, lineno)) 159 160 if __infunc__: 161 if s == '}': 162 __body__.append('') 163 ast.handleMethod(statements, fn, lineno, __infunc__[0], __body__, __infunc__[3], __infunc__[4]) 164 __infunc__ = [] 165 __body__ = [] 166 else: 167 __body__.append(s) 168 return 169 170 if __inpython__: 171 m = __python_func_regexp__.match(s) 172 if m and not eof: 173 __body__.append(s) 174 return 175 else: 176 ast.handlePythonMethod(statements, fn, lineno, __inpython__, 177 root, __body__) 178 __body__ = [] 179 __inpython__ = False 180 181 if eof: 182 return 183 184 if s and s[0] == '#': 185 if len(__residue__) != 0 and __residue__[0][0] != "#": 186 bb.fatal("There is a comment on line %s of file %s (%s) which is in the middle of a multiline expression.\nBitbake used to ignore these but no longer does so, please fix your metadata as errors are likely as a result of this change." % (lineno, fn, s)) 187 188 if len(__residue__) != 0 and __residue__[0][0] == "#" and (not s or s[0] != "#"): 189 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)) 190 191 if s and s[-1] == '\\': 192 __residue__.append(s[:-1]) 193 return 194 195 s = "".join(__residue__) + s 196 __residue__ = [] 197 198 # Skip empty lines 199 if s == '': 200 return 201 202 # Skip comments 203 if s[0] == '#': 204 return 205 206 m = __func_start_regexp__.match(s) 207 if m: 208 __infunc__ = [m.group("func") or "__anonymous", fn, lineno, m.group("py") is not None, m.group("fr") is not None] 209 return 210 211 m = __def_regexp__.match(s) 212 if m: 213 __body__.append(s) 214 __inpython__ = m.group(1) 215 216 return 217 218 m = __export_func_regexp__.match(s) 219 if m: 220 ast.handleExportFuncs(statements, fn, lineno, m, __classname__) 221 return 222 223 m = __addtask_regexp__.match(s) 224 if m: 225 if len(m.group().split()) == 2: 226 # Check and warn for "addtask task1 task2" 227 m2 = re.match(r"addtask\s+(?P<func>\w+)(?P<ignores>.*)", s) 228 if m2 and m2.group('ignores'): 229 logger.warning('addtask ignored: "%s"' % m2.group('ignores')) 230 231 # Check and warn for "addtask task1 before task2 before task3", the 232 # similar to "after" 233 taskexpression = s.split() 234 for word in ('before', 'after'): 235 if taskexpression.count(word) > 1: 236 logger.warning("addtask contained multiple '%s' keywords, only one is supported" % word) 237 238 ast.handleAddTask(statements, fn, lineno, m) 239 return 240 241 m = __deltask_regexp__.match(s) 242 if m: 243 # Check and warn "for deltask task1 task2" 244 if m.group('ignores'): 245 logger.warning('deltask ignored: "%s"' % m.group('ignores')) 246 ast.handleDelTask(statements, fn, lineno, m) 247 return 248 249 m = __addhandler_regexp__.match(s) 250 if m: 251 ast.handleBBHandlers(statements, fn, lineno, m) 252 return 253 254 m = __inherit_regexp__.match(s) 255 if m: 256 ast.handleInherit(statements, fn, lineno, m) 257 return 258 259 return ConfHandler.feeder(lineno, s, fn, statements) 260 261# Add us to the handlers list 262from .. import handlers 263handlers.append({'supports': supports, 'handle': handle, 'init': init}) 264del handlers 265