| Patrick Williams | b48b7b4 | 2016-08-17 15:04:38 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python | 
|  | 2 |  | 
|  | 3 | """\ | 
|  | 4 | Sanitize a bitbake file following the OpenEmbedded style guidelines, | 
|  | 5 | see http://openembedded.org/wiki/StyleGuide | 
|  | 6 |  | 
|  | 7 | (C) 2006 Cyril Romain <cyril.romain@gmail.com> | 
|  | 8 | MIT license | 
|  | 9 |  | 
|  | 10 | TODO: | 
|  | 11 | - add the others OpenEmbedded variables commonly used: | 
|  | 12 | - parse command arguments and print usage on misuse | 
|  | 13 | . prevent giving more than one .bb file in arguments | 
|  | 14 | - write result to a file | 
|  | 15 | - backup the original .bb file | 
|  | 16 | - make a diff and ask confirmation for patching ? | 
|  | 17 | - do not use startswith only: | 
|  | 18 | /!\ startswith('SOMETHING') is not taken into account due to the previous startswith('S'). | 
|  | 19 | - count rule breaks and displays them in the order frequence | 
|  | 20 | """ | 
|  | 21 |  | 
|  | 22 | from __future__ import print_function | 
|  | 23 | import fileinput | 
|  | 24 | import string | 
|  | 25 | import re | 
|  | 26 |  | 
|  | 27 | __author__ = "Cyril Romain <cyril.romain@gmail.com>" | 
|  | 28 | __version__ = "$Revision: 0.5 $" | 
|  | 29 |  | 
|  | 30 | # The standard set of variables often found in .bb files in the preferred order | 
|  | 31 | OE_vars = [ | 
|  | 32 | 'SUMMARY', | 
|  | 33 | 'DESCRIPTION', | 
|  | 34 | 'AUTHOR', | 
|  | 35 | 'HOMEPAGE', | 
|  | 36 | 'SECTION', | 
|  | 37 | 'LICENSE', | 
|  | 38 | 'LIC_FILES_CHKSUM', | 
|  | 39 | 'DEPENDS', | 
|  | 40 | 'PROVIDES', | 
|  | 41 | 'SRCREV', | 
|  | 42 | 'SRCDATE', | 
|  | 43 | 'PE', | 
|  | 44 | 'PV', | 
|  | 45 | 'PR', | 
|  | 46 | 'INC_PR', | 
|  | 47 | 'SRC_URI', | 
|  | 48 | 'S', | 
|  | 49 | 'GPE_TARBALL_SUFFIX', | 
|  | 50 | 'inherit', | 
|  | 51 | 'EXTRA_', | 
|  | 52 | 'export', | 
|  | 53 | 'do_fetch', | 
|  | 54 | 'do_unpack', | 
|  | 55 | 'do_patch', | 
|  | 56 | 'WORKDIR', | 
|  | 57 | 'acpaths', | 
|  | 58 | 'do_configure', | 
|  | 59 | 'do_compile', | 
|  | 60 | 'do_install', | 
|  | 61 | 'PACKAGES', | 
|  | 62 | 'PACKAGE_ARCH', | 
|  | 63 | 'RDEPENDS', | 
|  | 64 | 'RRECOMMENDS', | 
|  | 65 | 'RSUGGESTS', | 
|  | 66 | 'RPROVIDES', | 
|  | 67 | 'RCONFLICTS', | 
|  | 68 | 'FILES', | 
|  | 69 | 'do_package', | 
|  | 70 | 'do_stage', | 
|  | 71 | 'addhandler', | 
|  | 72 | 'addtask', | 
|  | 73 | 'bindir', | 
|  | 74 | 'headers', | 
|  | 75 | 'include', | 
|  | 76 | 'includedir', | 
|  | 77 | 'python', | 
|  | 78 | 'qtopiadir', | 
|  | 79 | 'pkg_preins', | 
|  | 80 | 'pkg_prerm', | 
|  | 81 | 'pkg_postins', | 
|  | 82 | 'pkg_postrm', | 
|  | 83 | 'require', | 
|  | 84 | 'sbindir', | 
|  | 85 | 'basesysconfdir', | 
|  | 86 | 'sysconfdir', | 
|  | 87 | 'ALLOW_EMPTY', | 
|  | 88 | 'ALTERNATIVE_NAME', | 
|  | 89 | 'ALTERNATIVE_PATH', | 
|  | 90 | 'ALTERNATIVE_LINK', | 
|  | 91 | 'ALTERNATIVE_PRIORITY', | 
|  | 92 | 'ALTNAME', | 
|  | 93 | 'AMD_DRIVER_LABEL', | 
|  | 94 | 'AMD_DRIVER_VERSION', | 
|  | 95 | 'ANGSTROM_EXTRA_INSTALL', | 
|  | 96 | 'APPDESKTOP', | 
|  | 97 | 'APPIMAGE', | 
|  | 98 | 'APPNAME', | 
|  | 99 | 'APPTYPE', | 
|  | 100 | 'APPWEB_BUILD', | 
|  | 101 | 'APPWEB_HOST', | 
|  | 102 | 'AR', | 
|  | 103 | 'ARCH', | 
|  | 104 | 'ARM_INSTRUCTION_SET', | 
| Patrick Williams | ddad1a1 | 2017-02-23 20:36:32 -0600 | [diff] [blame] | 105 | 'MIPS_INSTRUCTION_SET', | 
| Patrick Williams | b48b7b4 | 2016-08-17 15:04:38 -0500 | [diff] [blame] | 106 | 'ARM_MUTEX', | 
|  | 107 | 'ART_CONFIG', | 
|  | 108 | 'B', | 
|  | 109 | 'BJAM_OPTS', | 
|  | 110 | 'BJAM_TOOLS', | 
|  | 111 | 'BONOBO_HEADERS', | 
|  | 112 | 'BOOTSCRIPTS', | 
|  | 113 | 'BROKEN', | 
|  | 114 | 'BUILD_CPPFLAGS', | 
|  | 115 | 'CFLAGS', | 
|  | 116 | 'CCFLAGS', | 
|  | 117 | 'CMDLINE', | 
|  | 118 | 'COLLIE_MEMORY_SIZE', | 
|  | 119 | 'COMPATIBLE_HOST', | 
|  | 120 | 'COMPATIBLE_MACHINE', | 
|  | 121 | 'COMPILE_HERMES', | 
|  | 122 | 'CONFFILES', | 
|  | 123 | 'CONFLICTS', | 
|  | 124 | 'CORE_EXTRA_D', | 
|  | 125 | 'CORE_IMAGE_EXTRA_INSTALL', | 
|  | 126 | 'CORE_PACKAGES_D', | 
|  | 127 | 'CORE_PACKAGES_RD', | 
|  | 128 | 'CPPFLAGS', | 
|  | 129 | 'CVSDATE', | 
|  | 130 | 'CXXFLAGS', | 
|  | 131 | 'DEBIAN_NOAUTONAME', | 
|  | 132 | 'DEBUG_APPS', | 
|  | 133 | 'DEFAULT_PREFERENCE', | 
|  | 134 | 'DB4_CONFIG', | 
|  | 135 | 'EXCLUDE_FROM_SHLIBS', | 
|  | 136 | 'EXCLUDE_FROM_WORLD', | 
|  | 137 | 'FIXEDSRCDATE', | 
|  | 138 | 'GLIBC_ADDONS', | 
|  | 139 | 'GLIBC_EXTRA_OECONF', | 
|  | 140 | 'GNOME_VFS_HEADERS', | 
|  | 141 | 'HEADERS', | 
|  | 142 | 'INHIBIT_DEFAULT_DEPS', | 
|  | 143 | 'INITSCRIPT_PACKAGES', | 
|  | 144 | 'INITSCRIPT_NAME', | 
|  | 145 | 'INITSCRIPT_PARAMS', | 
|  | 146 | 'INSANE_SKIP', | 
|  | 147 | 'PACKAGE_INSTALL', | 
|  | 148 | 'KERNEL_IMAGETYPE', | 
|  | 149 | 'KERNEL_IMAGEDEST', | 
|  | 150 | 'KERNEL_OUTPUT', | 
|  | 151 | 'KERNEL_RELEASE', | 
|  | 152 | 'KERNEL_PRIORITY', | 
|  | 153 | 'KERNEL_SOURCE', | 
|  | 154 | 'KERNEL_SUFFIX', | 
|  | 155 | 'KERNEL_VERSION', | 
|  | 156 | 'K_MAJOR', | 
|  | 157 | 'K_MICRO', | 
|  | 158 | 'K_MINOR', | 
|  | 159 | 'HHV', | 
|  | 160 | 'KV', | 
|  | 161 | 'LDFLAGS', | 
|  | 162 | 'LD', | 
|  | 163 | 'LD_SO', | 
|  | 164 | 'LDLIBS', | 
|  | 165 | 'LEAD_SONAME', | 
|  | 166 | 'LIBTOOL', | 
|  | 167 | 'LIBBDB_EXTRA', | 
|  | 168 | 'LIBV', | 
|  | 169 | 'MACHINE_ESSENTIAL_EXTRA_RDEPENDS', | 
|  | 170 | 'MACHINE_ESSENTIAL_EXTRA_RRECOMMENDS', | 
|  | 171 | 'MACHINE_EXTRA_RDEPENDS', | 
|  | 172 | 'MACHINE_EXTRA_RRECOMMENDS', | 
|  | 173 | 'MACHINE_FEATURES', | 
|  | 174 | 'MACHINE_TASKS', | 
|  | 175 | 'MACHINE', | 
|  | 176 | 'MACHTYPE', | 
|  | 177 | 'MAKE_TARGETS', | 
|  | 178 | 'MESSAGEUSER', | 
|  | 179 | 'MESSAGEHOME', | 
|  | 180 | 'MIRRORS', | 
|  | 181 | 'MUTEX', | 
|  | 182 | 'OE_QMAKE_INCDIR_QT', | 
|  | 183 | 'OE_QMAKE_CXXFLAGS', | 
|  | 184 | 'ORBIT_IDL_SRC', | 
|  | 185 | 'PARALLEL_MAKE', | 
|  | 186 | 'PAKCAGE_ARCH', | 
|  | 187 | 'PCMCIA_MANAGER', | 
|  | 188 | 'PKG_BASENAME', | 
|  | 189 | 'PKG', | 
|  | 190 | 'QEMU', | 
|  | 191 | 'QMAKE_PROFILES', | 
|  | 192 | 'QPEDIR', | 
|  | 193 | 'QPF_DESCRIPTION', | 
|  | 194 | 'QPF_PKGPATTERN', | 
|  | 195 | 'QT_CONFIG_FLAGS', | 
|  | 196 | 'QT_LIBRARY', | 
|  | 197 | 'ROOTFS_POSTPROCESS_COMMAND', | 
|  | 198 | 'RREPLACES', | 
|  | 199 | 'TARGET_CFLAGS', | 
|  | 200 | 'TARGET_CPPFLAGS', | 
|  | 201 | 'TARGET_LDFLAGS', | 
|  | 202 | 'UBOOT_MACHINE', | 
|  | 203 | 'UCLIBC_BASE', | 
|  | 204 | 'UCLIBC_PATCHES', | 
|  | 205 | 'USERADD_PACKAGES', | 
|  | 206 | 'USERADD_PARAM', | 
|  | 207 | 'VIRTUAL_NAME', | 
|  | 208 | 'XORG_PN', | 
|  | 209 | 'XSERVER', | 
|  | 210 | 'others' | 
|  | 211 | ] | 
|  | 212 |  | 
|  | 213 | varRegexp = r'^([a-zA-Z_0-9${}-]*)([ \t]*)([+.:]?=[+.]?)([ \t]*)([^\t]+)' | 
|  | 214 | routineRegexp = r'^([a-zA-Z0-9_ ${}-]+?)\(' | 
|  | 215 |  | 
|  | 216 | # Variables seen in the processed .bb | 
|  | 217 | seen_vars = {} | 
|  | 218 | for v in OE_vars: | 
|  | 219 | seen_vars[v] = [] | 
|  | 220 |  | 
|  | 221 | # _Format guideline #0_: | 
|  | 222 | #   No spaces are allowed at the beginning of lines that define a variable or | 
|  | 223 | #   a do_ routine | 
|  | 224 | def respect_rule0(line): | 
|  | 225 | return line.lstrip()==line | 
|  | 226 | def conformTo_rule0(line): | 
|  | 227 | return line.lstrip() | 
|  | 228 |  | 
|  | 229 | # _Format guideline #1_: | 
|  | 230 | #   No spaces are allowed behind the line continuation symbol '\' | 
|  | 231 | def respect_rule1(line): | 
|  | 232 | if line.rstrip().endswith('\\'): | 
|  | 233 | return line.endswith('\\') | 
|  | 234 | else: | 
|  | 235 | return True | 
|  | 236 | def conformTo_rule1(line): | 
|  | 237 | return line.rstrip() | 
|  | 238 |  | 
|  | 239 | # _Format guideline #2_: | 
|  | 240 | #   Tabs should not be used (use spaces instead). | 
|  | 241 | def respect_rule2(line): | 
|  | 242 | return line.count('\t')==0 | 
|  | 243 | def conformTo_rule2(line): | 
|  | 244 | return line.expandtabs() | 
|  | 245 |  | 
|  | 246 | # _Format guideline #3_: | 
|  | 247 | #   Comments inside bb files are allowed using the '#' character at the | 
|  | 248 | #   beginning of a line. | 
|  | 249 | def respect_rule3(line): | 
|  | 250 | if line.lstrip().startswith('#'): | 
|  | 251 | return line.startswith('#') | 
|  | 252 | else: | 
|  | 253 | return True | 
|  | 254 | def conformTo_rule3(line): | 
|  | 255 | return line.lstrip() | 
|  | 256 |  | 
|  | 257 | # _Format guideline #4_: | 
|  | 258 | #   Use quotes on the right hand side of assignments FOO = "BAR" | 
|  | 259 | def respect_rule4(line): | 
|  | 260 | r = re.search(varRegexp, line) | 
|  | 261 | if r is not None: | 
|  | 262 | r2 = re.search(r'("?)([^"\\]*)(["\\]?)', r.group(5)) | 
|  | 263 | # do not test for None it because always match | 
|  | 264 | return r2.group(1)=='"' and r2.group(3)!='' | 
|  | 265 | return False | 
|  | 266 | def conformTo_rule4(line): | 
|  | 267 | r = re.search(varRegexp, line) | 
|  | 268 | return ''.join([r.group(1), ' ', r.group(3), ' "', r.group(5), r.group(5).endswith('"') and '' or '"']) | 
|  | 269 |  | 
|  | 270 | # _Format guideline #5_: | 
|  | 271 | #   The correct spacing for a variable is FOO = "BAR". | 
|  | 272 | def respect_rule5(line): | 
|  | 273 | r = re.search(varRegexp, line) | 
|  | 274 | return r is not None and r.group(2)==" " and r.group(4)==" " | 
|  | 275 | def conformTo_rule5(line): | 
|  | 276 | r = re.search(varRegexp, line) | 
|  | 277 | return ''.join([r.group(1), ' ', r.group(3), ' ', r.group(5)]) | 
|  | 278 |  | 
|  | 279 | # _Format guideline #6_: | 
|  | 280 | #   Don't use spaces or tabs on empty lines | 
|  | 281 | def respect_rule6(line): | 
|  | 282 | return not line.isspace() or line=="\n" | 
|  | 283 | def conformTo_rule6(line): | 
|  | 284 | return "" | 
|  | 285 |  | 
|  | 286 | # _Format guideline #7_: | 
|  | 287 | #   Indentation of multiline variables such as SRC_URI is desireable. | 
|  | 288 | def respect_rule7(line): | 
|  | 289 | return True | 
|  | 290 | def conformTo_rule7(line): | 
|  | 291 | return line | 
|  | 292 |  | 
|  | 293 | rules = ( | 
|  | 294 | (respect_rule0, conformTo_rule0, "No spaces are allowed at the beginning of lines that define a variable or a do_ routine"), | 
|  | 295 | (respect_rule1, conformTo_rule1, "No spaces are allowed behind the line continuation symbol '\\'"), | 
|  | 296 | (respect_rule2, conformTo_rule2, "Tabs should not be used (use spaces instead)"), | 
|  | 297 | (respect_rule3, conformTo_rule3, "Comments inside bb files are allowed using the '#' character at the beginning of a line"), | 
|  | 298 | (respect_rule4, conformTo_rule4, "Use quotes on the right hand side of assignments FOO = \"BAR\""), | 
|  | 299 | (respect_rule5, conformTo_rule5, "The correct spacing for a variable is FOO = \"BAR\""), | 
|  | 300 | (respect_rule6, conformTo_rule6, "Don't use spaces or tabs on empty lines"), | 
|  | 301 | (respect_rule7, conformTo_rule7, "Indentation of multiline variables such as SRC_URI is desireable"), | 
|  | 302 | ) | 
|  | 303 |  | 
|  | 304 | # Function to check that a line respects a rule. If not, it tries to conform | 
|  | 305 | # the line to the rule. Reminder or Disgression message are dump accordingly. | 
|  | 306 | def follow_rule(i, line): | 
|  | 307 | oldline = line | 
|  | 308 | # if the line does not respect the rule | 
|  | 309 | if not rules[i][0](line): | 
|  | 310 | # try to conform it to the rule | 
|  | 311 | line = rules[i][1](line) | 
|  | 312 | # if the line still does not respect the rule | 
|  | 313 | if not rules[i][0](line): | 
|  | 314 | # this is a rule disgression | 
|  | 315 | print ("## Disgression: ", rules[i][2], " in: '", oldline, "'") | 
|  | 316 | else: | 
|  | 317 | # just remind user about his/her errors | 
|  | 318 | print ("## Reminder: ", rules[i][2], " in : '", oldline, "'") | 
|  | 319 | return line | 
|  | 320 |  | 
|  | 321 |  | 
|  | 322 | if __name__ == "__main__": | 
|  | 323 |  | 
|  | 324 | # -- retrieves the lines of the .bb file -- | 
|  | 325 | lines = [] | 
|  | 326 | for line in fileinput.input(): | 
|  | 327 | # use 'if True' to warn user about all the rule he/she breaks | 
|  | 328 | # use 'if False' to conform to rules{2,1,6} without warnings | 
|  | 329 | if True: | 
|  | 330 | lines.append(line) | 
|  | 331 | else: | 
|  | 332 | # expandtabs on each line so that rule2 is always respected | 
|  | 333 | # rstrip each line so that rule1 is always respected | 
|  | 334 | line = line.expandtabs().rstrip() | 
|  | 335 | # ignore empty lines (or line filled with spaces or tabs only) | 
|  | 336 | # so that rule6 is always respected | 
|  | 337 | if line is not '': | 
|  | 338 | lines.append(line) | 
|  | 339 |  | 
|  | 340 | # -- parse the file -- | 
|  | 341 | var = "" | 
|  | 342 | in_routine = False | 
|  | 343 | commentBloc = [] | 
|  | 344 | olines = [] | 
|  | 345 | for line in lines: | 
|  | 346 | originalLine = line | 
|  | 347 | # rstrip line to remove line breaks characters | 
|  | 348 | line = line.rstrip() | 
|  | 349 | line = follow_rule(2, line) | 
|  | 350 | line = follow_rule(1, line) | 
|  | 351 | line = follow_rule(6, line) | 
|  | 352 |  | 
|  | 353 | # ignore empty lines | 
|  | 354 | if line.isspace() or line is '': | 
|  | 355 | # flush comments into the olines | 
|  | 356 | for c in commentBloc: olines.append(c) | 
|  | 357 | commentBloc = [] | 
|  | 358 | continue | 
|  | 359 |  | 
|  | 360 | if line.startswith('}'): | 
|  | 361 | in_routine=False | 
|  | 362 | keep = line.endswith('\\') or in_routine | 
|  | 363 |  | 
|  | 364 | # handles commented lines | 
|  | 365 | if line.lstrip().startswith('#'): | 
|  | 366 | # check and follow rule3 if not in a variables or routines | 
|  | 367 | if not in_routine: | 
|  | 368 | line = follow_rule(3, line) | 
|  | 369 | commentBloc.append(line) | 
|  | 370 | continue | 
|  | 371 |  | 
|  | 372 | if var in seen_vars: | 
|  | 373 | for c in commentBloc: seen_vars[var].append(c) | 
|  | 374 | commentBloc = [] | 
|  | 375 | seen_vars[var].append(line) | 
|  | 376 | else: | 
|  | 377 | for k in OE_vars: | 
|  | 378 | if line.startswith(k): | 
|  | 379 | var = k | 
|  | 380 | break | 
|  | 381 | if re.match(routineRegexp, line) is not None: | 
|  | 382 | in_routine=True | 
|  | 383 | line = follow_rule(0, line) | 
|  | 384 | elif re.match(varRegexp, line) is not None: | 
|  | 385 | line = follow_rule(0, line) | 
|  | 386 | line = follow_rule(4, line) | 
|  | 387 | line = follow_rule(5, line) | 
|  | 388 | if var == "": | 
|  | 389 | if not in_routine: | 
|  | 390 | print ("## Warning: unknown variable/routine \"%s\"" % originalLine.rstrip('\n')) | 
|  | 391 | var = 'others' | 
|  | 392 | for c in commentBloc: seen_vars[var].append(c) | 
|  | 393 | commentBloc = [] | 
|  | 394 | seen_vars[var].append(line) | 
|  | 395 | if not keep and not in_routine: var = "" | 
|  | 396 |  | 
|  | 397 | # -- dump the sanitized .bb file -- | 
|  | 398 | addEmptyLine = False | 
|  | 399 | # write comments that are not related to variables nor routines | 
|  | 400 | for l in commentBloc: olines.append(l) | 
|  | 401 | # write variables and routines | 
|  | 402 | previourVarPrefix = "unknown" | 
|  | 403 | for k in OE_vars: | 
|  | 404 | if k=='SRC_URI': addEmptyLine = True | 
|  | 405 | if seen_vars[k] != []: | 
|  | 406 | if addEmptyLine and not k.startswith(previourVarPrefix): | 
|  | 407 | olines.append("") | 
|  | 408 | for l in seen_vars[k]: | 
|  | 409 | olines.append(l) | 
|  | 410 | previourVarPrefix = k.split('_')[0]=='' and "unknown" or k.split('_')[0] | 
|  | 411 | for line in olines: print(line) | 
|  | 412 |  |