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