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 | |