1#!/usr/bin/env python3
2
3"""\
4Sanitize a bitbake file following the OpenEmbedded style guidelines,
5see http://openembedded.org/wiki/StyleGuide
6
7(C) 2006 Cyril Romain <cyril.romain@gmail.com>
8MIT license
9
10TODO:
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
22from __future__ import print_function
23import fileinput
24import string
25import 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
31OE_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',
105    'MIPS_INSTRUCTION_SET',
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
213varRegexp = r'^([a-zA-Z_0-9${}:-]*)([ \t]*)([+.:]?=[+.]?)([ \t]*)([^\t]+)'
214routineRegexp = r'^([a-zA-Z0-9_ ${}:-]+?)\('
215
216# Variables seen in the processed .bb
217seen_vars = {}
218for 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
225
226def respect_rule0(line):
227    return line.lstrip() == line
228
229
230def conformTo_rule0(line):
231    return line.lstrip()
232
233# _Format guideline #1_:
234#   No spaces are allowed behind the line continuation symbol '\'
235
236
237def respect_rule1(line):
238    if line.rstrip().endswith('\\'):
239        return line.endswith('\\')
240    else:
241        return True
242
243
244def conformTo_rule1(line):
245    return line.rstrip()
246
247# _Format guideline #2_:
248#   Tabs should not be used (use spaces instead).
249
250
251def respect_rule2(line):
252    return line.count('\t') == 0
253
254
255def conformTo_rule2(line):
256    return line.expandtabs()
257
258# _Format guideline #3_:
259#   Comments inside bb files are allowed using the '#' character at the
260#   beginning of a line.
261
262
263def respect_rule3(line):
264    if line.lstrip().startswith('#'):
265        return line.startswith('#')
266    else:
267        return True
268
269
270def conformTo_rule3(line):
271    return line.lstrip()
272
273# _Format guideline #4_:
274#   Use quotes on the right hand side of assignments FOO = "BAR"
275
276
277def respect_rule4(line):
278    r = re.search(varRegexp, line)
279    if r is not None:
280        r2 = re.search(r'("?)([^"\\]*)(["\\]?)', r.group(5))
281        # do not test for None it because always match
282        return r2.group(1) == '"' and r2.group(3) != ''
283    return False
284
285
286def conformTo_rule4(line):
287    r = re.search(varRegexp, line)
288    return ''.join([r.group(1), ' ', r.group(3), ' "', r.group(5), r.group(5).endswith('"') and '' or '"'])
289
290# _Format guideline #5_:
291#   The correct spacing for a variable is FOO = "BAR".
292
293
294def respect_rule5(line):
295    r = re.search(varRegexp, line)
296    return r is not None and r.group(2) == " " and r.group(4) == " "
297
298
299def conformTo_rule5(line):
300    r = re.search(varRegexp, line)
301    return ''.join([r.group(1), ' ', r.group(3), ' ', r.group(5)])
302
303# _Format guideline #6_:
304#   Don't use spaces or tabs on empty lines
305
306
307def respect_rule6(line):
308    return not line.isspace() or line == "\n"
309
310
311def conformTo_rule6(line):
312    return ""
313
314# _Format guideline #7_:
315#   Indentation of multiline variables such as SRC_URI is desireable.
316
317
318def respect_rule7(line):
319    return True
320
321
322def conformTo_rule7(line):
323    return line
324
325
326rules = (
327    (respect_rule0, conformTo_rule0, "No spaces are allowed at the beginning of lines that define a variable or a do_ routine"),
328    (respect_rule1, conformTo_rule1, "No spaces are allowed behind the line continuation symbol '\\'"),
329    (respect_rule2, conformTo_rule2, "Tabs should not be used (use spaces instead)"),
330    (respect_rule3, conformTo_rule3, "Comments inside bb files are allowed using the '#' character at the beginning of a line"),
331    (respect_rule4, conformTo_rule4, "Use quotes on the right hand side of assignments FOO = \"BAR\""),
332    (respect_rule5, conformTo_rule5, "The correct spacing for a variable is FOO = \"BAR\""),
333    (respect_rule6, conformTo_rule6, "Don't use spaces or tabs on empty lines"),
334    (respect_rule7, conformTo_rule7, "Indentation of multiline variables such as SRC_URI is desireable"),
335)
336
337# Function to check that a line respects a rule. If not, it tries to conform
338# the line to the rule. Reminder or Disgression message are dump accordingly.
339
340
341def follow_rule(i, line):
342    oldline = line
343    # if the line does not respect the rule
344    if not rules[i][0](line):
345        # try to conform it to the rule
346        line = rules[i][1](line)
347        # if the line still does not respect the rule
348        if not rules[i][0](line):
349            # this is a rule disgression
350            print("## Disgression: ", rules[i][2], " in: '", oldline, "'")
351        else:
352            # just remind user about his/her errors
353            print("## Reminder: ", rules[i][2], " in : '", oldline, "'")
354    return line
355
356
357if __name__ == "__main__":
358
359    # -- retrieves the lines of the .bb file --
360    lines = []
361    for line in fileinput.input():
362        # use 'if True' to warn user about all the rule he/she breaks
363        # use 'if False' to conform to rules{2,1,6} without warnings
364        if True:
365            lines.append(line)
366        else:
367            # expandtabs on each line so that rule2 is always respected
368            # rstrip each line so that rule1 is always respected
369            line = line.expandtabs().rstrip()
370            # ignore empty lines (or line filled with spaces or tabs only)
371            # so that rule6 is always respected
372            if line != '':
373                lines.append(line)
374
375    # -- parse the file --
376    var = ""
377    in_routine = False
378    commentBloc = []
379    olines = []
380    for line in lines:
381        originalLine = line
382        # rstrip line to remove line breaks characters
383        line = line.rstrip()
384        line = follow_rule(2, line)
385        line = follow_rule(1, line)
386        line = follow_rule(6, line)
387
388        # ignore empty lines
389        if line.isspace() or line == '':
390            # flush comments into the olines
391            for c in commentBloc:
392                olines.append(c)
393            commentBloc = []
394            continue
395
396        if line.startswith('}'):
397            in_routine = False
398        keep = line.endswith('\\') or in_routine
399
400        # handles commented lines
401        if line.lstrip().startswith('#'):
402            # check and follow rule3 if not in a variables or routines
403            if not in_routine:
404                line = follow_rule(3, line)
405            commentBloc.append(line)
406            continue
407
408        if var in seen_vars:
409            for c in commentBloc:
410                seen_vars[var].append(c)
411            commentBloc = []
412            seen_vars[var].append(line)
413        else:
414            for k in OE_vars:
415                if line.startswith(k):
416                    var = k
417                    break
418            if re.match(routineRegexp, line) is not None:
419                in_routine = True
420                line = follow_rule(0, line)
421            elif re.match(varRegexp, line) is not None:
422                line = follow_rule(0, line)
423                line = follow_rule(4, line)
424                line = follow_rule(5, line)
425            if var == "":
426                if not in_routine:
427                    print("## Warning: unknown variable/routine \"%s\"" % originalLine.rstrip('\n'))
428                var = 'others'
429            for c in commentBloc:
430                seen_vars[var].append(c)
431            commentBloc = []
432            seen_vars[var].append(line)
433        if not keep and not in_routine:
434            var = ""
435
436    # -- dump the sanitized .bb file --
437    addEmptyLine = False
438    # write comments that are not related to variables nor routines
439    for c in commentBloc:
440        olines.append(c)
441    # write variables and routines
442    previourVarPrefix = "unknown"
443    for k in OE_vars:
444        if k == 'SRC_URI':
445            addEmptyLine = True
446        if seen_vars[k] != []:
447            if addEmptyLine and not k.startswith(previourVarPrefix):
448                olines.append("")
449            for s in seen_vars[k]:
450                olines.append(s)
451            previourVarPrefix = k.split('_')[0] == '' and "unknown" or k.split('_')[0]
452    for line in olines:
453        print(line)
454