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