Update valied license list
[platform/upstream/rpmlint.git] / MenuCheck.py
1 # -*- coding: utf-8 -*-
2 #---------------------------------------------------------------
3 # Project         : Mandriva Linux
4 # Module          : rpmlint
5 # File            : MenuCheck.py
6 # Version         : $Id: MenuCheck.py 1885 2011-09-13 18:15:29Z scop $
7 # Author          : Frederic Lepied
8 # Created On      : Mon Mar 20 07:43:37 2000
9 #---------------------------------------------------------------
10
11 import re
12 import stat
13
14 import rpm
15
16 from Filter import addDetails, printError, printInfo, printWarning
17 import AbstractCheck
18 import Config
19 import Pkg
20
21
22 DEFAULT_VALID_SECTIONS = (
23     'Office/Accessories',
24     'Office/Address Books',
25     'Office/Communications/Fax',
26     'Office/Communications/PDA',
27     'Office/Communications/Phone',
28     'Office/Communications/Other',
29     'Office/Drawing',
30     'Office/Graphs',
31     'Office/Presentations',
32     'Office/Publishing',
33     'Office/Spreadsheets',
34     'Office/Tasks Management',
35     'Office/Time Management',
36     'Office/Wordprocessors',
37     'Office/Other',
38     'Internet/Chat',
39     'Internet/File Transfer',
40     'Internet/Instant Messaging',
41     'Internet/Mail',
42     'Internet/News',
43     'Internet/Remote Access',
44     'Internet/Video Conference',
45     'Internet/Web Browsers',
46     'Internet/Web Editors',
47     'Internet/Other',
48     'Multimedia/Graphics',
49     'Multimedia/Sound',
50     'Multimedia/Video',
51     'Multimedia/Other',
52     'System/Archiving/Backup',
53     'System/Archiving/CD Burning',
54     'System/Archiving/Compression',
55     'System/Archiving/Other',
56     'System/Configuration/Boot and Init',
57     'System/Configuration/GNOME',
58     'System/Configuration/Hardware',
59     'System/Configuration/KDE',
60     'System/Configuration/Networking',
61     'System/Configuration/Packaging',
62     'System/Configuration/Printing',
63     'System/Configuration/Users',
64     'System/Configuration/Other',
65     'System/File Tools',
66     'System/Monitoring',
67     'System/Session/Windowmanagers',
68     'System/Terminals',
69     'System/Text Tools',
70     'System/Other',
71     'More Applications/Accessibility',
72     'More Applications/Communications',
73     'More Applications/Databases',
74     'More Applications/Development/Code Generators',
75     'More Applications/Development/Development Environments',
76     'More Applications/Development/Interpreters',
77     'More Applications/Development/Tools',
78     'More Applications/Development/Other',
79     'More Applications/Documentation',
80     'More Applications/Editors',
81     'More Applications/Education/Economy',
82     'More Applications/Education/Geography',
83     'More Applications/Education/History',
84     'More Applications/Education/Languages',
85     'More Applications/Education/Literature',
86     'More Applications/Education/Sciences',
87     'More Applications/Education/Sports',
88     'More Applications/Education/Other',
89     'More Applications/Emulators',
90     'More Applications/Finances',
91     'More Applications/Games/Adventure',
92     'More Applications/Games/Arcade',
93     'More Applications/Games/Boards',
94     'More Applications/Games/Cards',
95     'More Applications/Games/Puzzles',
96     'More Applications/Games/Sports',
97     'More Applications/Games/Strategy',
98     'More Applications/Games/Toys',
99     'More Applications/Games/Other',
100     'More Applications/Sciences/Artificial Intelligence',
101     'More Applications/Sciences/Astronomy',
102     'More Applications/Sciences/Biology',
103     'More Applications/Sciences/Chemistry',
104     'More Applications/Sciences/Computer Science',
105     'More Applications/Sciences/Data visualization',
106     'More Applications/Sciences/Electricity',
107     'More Applications/Sciences/Geosciences',
108     'More Applications/Sciences/Image Processing',
109     'More Applications/Sciences/Mathematics',
110     'More Applications/Sciences/Numerical Analysis',
111     'More Applications/Sciences/Parallel Computing',
112     'More Applications/Sciences/Physics',
113     'More Applications/Sciences/Robotics',
114     'More Applications/Sciences/Other',
115     'More Applications/Other',
116     )
117
118 DEFAULT_EXTRA_MENU_NEEDS = (
119     'gnome',
120     'icewm',
121     'kde',
122     'wmaker',
123     )
124
125 DEFAULT_ICON_PATH = (('/usr/share/icons/', 'normal'),
126                      ('/usr/share/icons/mini/', 'mini'),
127                      ('/usr/share/icons/large/', 'large'))
128
129 DEFAULT_LAUNCHERS = (['(?:/usr/bin/)?kdesu', ('/usr/bin/kdesu', 'kdesu')],
130                      ['(?:/usr/bin/)?launch_x11_clanapp', ('/usr/bin/launch_x11_clanapp', 'clanlib', 'libclanlib0')],
131                      ['(?:/usr/bin/)?soundwrapper', None],
132                     )
133
134 menu_file_regex = re.compile('^/usr/lib/menu/([^/]+)$')
135 old_menu_file_regex = re.compile('^/usr/share/(gnome/apps|applnk)/([^/]+)$')
136 package_regex = re.compile('\?package\((.*)\):')
137 needs_regex = re.compile('needs=(\"([^\"]+)\"|([^ \t\"]+))')
138 section_regex = re.compile('section=(\"([^\"]+)\"|([^ \t\"]+))')
139 title_regex = re.compile('[\"\s]title=(\"([^\"]+)\"|([^ \t\"]+))')
140 longtitle_regex = re.compile('longtitle=(\"([^\"]+)\"|([^ \t\"]+))')
141 command_regex = re.compile('command=(?:\"([^\"]+)\"|([^ \t\"]+))')
142 icon_regex = re.compile('icon=\"?([^\" ]+)')
143 valid_sections = Config.getOption('ValidMenuSections', DEFAULT_VALID_SECTIONS)
144 update_menus_regex = re.compile('^[^#]*update-menus', re.MULTILINE)
145 standard_needs = Config.getOption('ExtraMenuNeeds', DEFAULT_EXTRA_MENU_NEEDS)
146 icon_paths = Config.getOption('IconPath', DEFAULT_ICON_PATH)
147 xpm_ext_regex = re.compile('/usr/share/icons/(mini/|large/).*\.xpm$')
148 icon_ext_regex = re.compile(Config.getOption('IconFilename', '.*\.png$'))
149 version_regex = re.compile('([0-9.][0-9.]+)($|\s)')
150 launchers = Config.getOption('MenuLaunchers', DEFAULT_LAUNCHERS)
151 xdg_migrated_regex = re.compile('xdg=\"?([^\" ]+)')
152
153 # compile regexps
154 for l in launchers:
155     l[0] = re.compile(l[0])
156 del l
157
158 class MenuCheck(AbstractCheck.AbstractCheck):
159
160     def __init__(self):
161         AbstractCheck.AbstractCheck.__init__(self, 'MenuCheck')
162
163     def check(self, pkg):
164         # Check only binary package
165         if pkg.isSource():
166             return
167
168         files = pkg.files()
169         menus = []
170
171         for fname, pkgfile in files.items():
172             # Check menu files
173             res = menu_file_regex.search(fname)
174             mode = pkgfile.mode
175             if res:
176                 basename = res.group(1)
177                 if not stat.S_ISREG(mode):
178                     printError(pkg, 'non-file-in-menu-dir', fname)
179                 else:
180                     if basename != pkg.name:
181                         printWarning(pkg, 'non-coherent-menu-filename', fname)
182                     if mode & 0444 != 0444:
183                         printError(pkg, 'non-readable-menu-file', fname)
184                     if mode & 0111 != 0:
185                         printError(pkg, 'executable-menu-file', fname)
186                     menus.append(fname)
187             else:
188                 # Check old menus from KDE and GNOME
189                 res = old_menu_file_regex.search(fname)
190                 if res:
191                     if stat.S_ISREG(mode):
192                         printError(pkg, 'old-menu-entry', fname)
193                 else:
194                     # Check non transparent xpm files
195                     res = xpm_ext_regex.search(fname)
196                     if res:
197                         if stat.S_ISREG(mode) and not pkg.grep('None",', fname):
198                             printWarning(pkg, 'non-transparent-xpm', fname)
199                 if fname.startswith('/usr/lib64/menu'):
200                     printError(pkg, 'menu-in-wrong-dir', fname)
201
202         if menus:
203             postin = pkg[rpm.RPMTAG_POSTIN] or \
204                 pkg.scriptprog(rpm.RPMTAG_POSTINPROG)
205             if not postin:
206                 printError(pkg, 'menu-without-postin')
207             elif not update_menus_regex.search(postin):
208                 printError(pkg, 'postin-without-update-menus')
209
210             postun = pkg[rpm.RPMTAG_POSTUN] or \
211                 pkg.scriptprog(rpm.RPMTAG_POSTUNPROG)
212             if not postun:
213                 printError(pkg, 'menu-without-postun')
214             elif not update_menus_regex.search(postun):
215                 printError(pkg, 'postun-without-update-menus')
216
217             directory = pkg.dirName()
218             for f in menus:
219                 # remove comments and handle cpp continuation lines
220                 cmd = Pkg.getstatusoutput(('/lib/cpp', directory + f), True)[1]
221
222                 for line in cmd.splitlines():
223                     if not line.startswith('?'):
224                         continue
225                     res = package_regex.search(line)
226                     if res:
227                         package = res.group(1)
228                         if package != pkg.name:
229                             printWarning(pkg,
230                                          'incoherent-package-value-in-menu',
231                                          package, f)
232                     else:
233                         printInfo(pkg, 'unable-to-parse-menu-entry', line)
234
235                     command = True
236                     res = command_regex.search(line)
237                     if res:
238                         command_line = (res.group(1) or res.group(2)).split()
239                         command = command_line[0]
240                         for launcher in launchers:
241                             if not launcher[0].search(command):
242                                 continue
243                             found = False
244                             if launcher[1]:
245                                 if ('/bin/' + command_line[0] in files or
246                                     '/usr/bin/' + command_line[0] in files
247                                     or '/usr/X11R6/bin/' + command_line[0]
248                                     in files):
249                                     found = True
250                                 else:
251                                     for l in launcher[1]:
252                                         if l in pkg.req_names():
253                                             found = True
254                                             break
255                                 if not found:
256                                     printError(pkg,
257                                                'use-of-launcher-in-menu-but-no-requires-on',
258                                                launcher[1][0])
259                             command = command_line[1]
260                             break
261
262                         if command[0] == '/':
263                             if command not in files:
264                                 printWarning(pkg, 'menu-command-not-in-package',
265                                              command)
266                         elif not ('/bin/' + command in files or
267                                   '/usr/bin/' + command in files or
268                                   '/usr/X11R6/bin/' + command in files):
269                             printWarning(pkg, 'menu-command-not-in-package',
270                                          command)
271                     else:
272                         printWarning(pkg, 'missing-menu-command')
273                         command = False
274
275                     res = longtitle_regex.search(line)
276                     if res:
277                         grp = res.groups()
278                         title = grp[1] or grp[2]
279                         if title[0] != title[0].upper():
280                             printWarning(pkg, 'menu-longtitle-not-capitalized',
281                                          title)
282                         res = version_regex.search(title)
283                         if res:
284                             printWarning(pkg, 'version-in-menu-longtitle',
285                                          title)
286                     else:
287                         printError(pkg, 'no-longtitle-in-menu', f)
288                         title = None
289
290                     res = title_regex.search(line)
291                     if res:
292                         grp = res.groups()
293                         title = grp[1] or grp[2]
294                         if title[0] != title[0].upper():
295                             printWarning(pkg, 'menu-title-not-capitalized',
296                                          title)
297                         res = version_regex.search(title)
298                         if res:
299                             printWarning(pkg, 'version-in-menu-title', title)
300                         if '/' in title:
301                             printError(pkg, 'invalid-title', title)
302                     else:
303                         printError(pkg, 'no-title-in-menu', f)
304                         title = None
305
306                     res = needs_regex.search(line)
307                     if res:
308                         grp = res.groups()
309                         needs = (grp[1] or grp[2]).lower()
310                         if needs in ('x11', 'text' ,'wm'):
311                             res = section_regex.search(line)
312                             if res:
313                                 grp = res.groups()
314                                 section = grp[1] or grp[2]
315                                 # don't warn entries for sections
316                                 if command and section not in valid_sections:
317                                     printError(pkg, 'invalid-menu-section',
318                                                section, f)
319                             else:
320                                 printInfo(pkg, 'unable-to-parse-menu-section',
321                                           line)
322                         elif needs not in standard_needs:
323                             printInfo(pkg, 'strange-needs', needs, f)
324                     else:
325                         printInfo(pkg, 'unable-to-parse-menu-needs', line)
326
327                     res = icon_regex.search(line)
328                     if res:
329                         icon = res.group(1)
330                         if not icon_ext_regex.search(icon):
331                             printWarning(pkg, 'invalid-menu-icon-type', icon)
332                         if icon[0] == '/' and needs == 'x11':
333                             printWarning(pkg, 'hardcoded-path-in-menu-icon',
334                                          icon)
335                         else:
336                             for path in icon_paths:
337                                 if (path[0] + icon) not in files:
338                                     printError(pkg,
339                                                path[1] + '-icon-not-in-package',
340                                                icon, f)
341                     else:
342                         printWarning(pkg, 'no-icon-in-menu', title)
343
344                     res = xdg_migrated_regex.search(line)
345                     if res:
346                         if not res.group(1).lower() == "true":
347                             printError(pkg, 'non-xdg-migrated-menu')
348                     else:
349                         printError(pkg, 'non-xdg-migrated-menu')
350
351
352 # Create an object to enable the auto registration of the test
353 check = MenuCheck()
354
355 addDetails(
356 'non-file-in-menu-dir',
357 '''/usr/lib/menu must not contain anything else than normal files.''',
358
359 'non-coherent-menu-filename',
360 '''The menu file name should be /usr/lib/menu/<package>.''',
361
362 'non-readable-menu-file',
363 '''The menu file isn't readable. Check the permissions.''',
364
365 'old-menu-entry',
366 '''
367 ''',
368
369 'non-transparent-xpm',
370 '''xpm icon should be transparent for use in menus.''',
371
372 'menu-without-postin',
373 '''A menu file exists in the package but no %post scriptlet is present to call
374 update-menus.''',
375
376 'postin-without-update-menus',
377 '''A menu file exists in the package but its %post scriptlet doesn't call
378 update-menus.''',
379
380 'menu-without-postun',
381 '''A menu file exists in the package but no %postun scriptlet is present to call
382 update-menus.''',
383
384 'postun-without-update-menus',
385 '''A menu file exists in the package but its %postun scriptlet doesn't call
386 update-menus.''',
387
388 'incoherent-package-value-in-menu',
389 '''The package field of the menu entry isn't the same as the package name.''',
390
391 'use-of-launcher-in-menu-but-no-requires-on',
392 '''The menu command uses a launcher but there is no dependency in the package
393 that contains it.''',
394
395 'menu-command-not-in-package',
396 '''The command used in the menu isn't included in the package.''',
397
398 'menu-longtitle-not-capitalized',
399 '''The longtitle field of the menu doesn't start with a capital letter.''',
400
401 'version-in-menu-longtitle',
402 '''The longtitle filed of the menu entry contains a version. This is bad
403 because it will be prone to error when the version of the package changes.''',
404
405 'no-longtitle-in-menu',
406 '''The longtitle field isn't present in the menu entry.''',
407
408 'menu-title-not-capitalized',
409 '''The title field of the menu entry doesn't start with a capital letter.''',
410
411 'version-in-menu-title',
412 '''The title filed of the menu entry contains a version. This is bad
413 because it will be prone to error when the version of the package changes.''',
414
415 'no-title-in-menu',
416 '''The title field isn't present in the menu entry.''',
417
418 'invalid-menu-section',
419 '''The section field of the menu entry isn't standard.''',
420
421 'unable-to-parse-menu-section',
422 '''rpmlint wasn't able to parse the menu section. Please report.''',
423
424 'hardcoded-path-in-menu-icon',
425 '''The path of the icon is hardcoded in the menu entry. This prevent multiple
426 sizes of the icon from being found.''',
427
428 'normal-icon-not-in-package',
429 '''The normal icon isn't present in the package.''',
430
431 'mini-icon-not-in-package',
432 '''The mini icon isn't present in the package.''',
433
434 'large-icon-not-in-package',
435 '''The large icon isn't present in the package.''',
436
437 'no-icon-in-menu',
438 '''The menu entry doesn't contain an icon field.''',
439
440 'invalid-title',
441 '''The menu title contains invalid characters like /.''',
442
443 'missing-menu-command',
444 '''The menu file doesn't contain a command.''',
445
446 'menu-in-wrong-directory',
447 '''The menu files must be under /usr/lib/menu.''',
448
449 'non-xdg-migrated-menu',
450 '''The menu file has not been migrated to new XDG menu system.''',
451
452 )
453
454 # MenuCheck.py ends here
455
456 # Local variables:
457 # indent-tabs-mode: nil
458 # py-indent-offset: 4
459 # End:
460 # ex: ts=4 sw=4 et