1 # -*- coding: utf-8 -*-
2 #############################################################################
5 # Author : Frederic Lepied
6 # Created on : Tue Sep 28 00:03:24 1999
7 # Version : $Id: TagsCheck.py 1892 2011-11-23 20:21:05Z scop $
8 # Purpose : Check a package to see if some rpm tags are present
9 #############################################################################
16 from urlparse import urlparse
17 except ImportError: # Python 3
18 from urllib.parse import urlparse
22 from Filter import addDetails, printError, printInfo, printWarning
28 _use_enchant = Config.getOption("UseEnchant", None)
29 if _use_enchant or _use_enchant is None:
32 import enchant.checker
39 DEFAULT_VALID_LICENSES = (
40 # OSI approved licenses, http://www.opensource.org/licenses/ (unversioned,
41 # trailing "license" dropped based on fuzzy logic, and in well-known cases,
42 # the abbreviation used instead of the full name, but list kept sorted by
43 # the full name). Updated 2010-02-01.
44 'Academic Free License',
45 'Adaptive Public License',
46 'AGPLv3', # Affero GNU Public License
47 'AGPLv3+', # Affero GNU Public License
49 'Apache Software License',
50 'Apple Public Source License',
52 'Attribution Assurance License',
54 'Boost Software License',
55 'Computer Associates Trusted Open Source License',
56 'CDDL', # Common Development and Distribution License
57 'Common Public Attribution License',
58 'CUA Office Public License',
59 'EU DataGrid Software License',
60 'Eclipse Public License',
61 'Educational Community License',
62 'Eiffel Forum License',
63 'Entessa Public License',
64 'European Union Public License',
77 'Historical Permission Notice and Disclaimer',
81 'Lucent Public License',
82 'Microsoft Public License',
83 'Microsoft Reciprocal License',
87 'MPL', # Mozilla Public License
89 'NASA Open Source Agreement',
90 'Naumen Public License',
91 'Nethack General Public License',
92 'Nokia Open Source License',
93 'Non-profit Open Software License',
95 'OCLC Research Public License',
96 'OFL', # Open Font License
97 'Open Group Test Suite License',
98 'Open Software License',
100 'Python license', # CNRI Python License
101 'Python Software Foundation License',
102 'QPL', # Qt Public License
103 'RealNetworks Public Source License',
104 'Reciprocal Public License',
105 'Ricoh Source Code Public License',
106 'Simple Public License',
108 'Sun Public License',
109 'Sybase Open Watcom Public License',
110 'University of Illinois/NCSA Open Source License',
111 'Vovida Software License',
113 'wxWindows Library License',
115 'Zope Public License',
116 'zlib/libpng License',
117 # Creative commons licenses, http://creativecommons.org/licenses/:
118 'Creative Commons Attribution',
119 'Creative Commons Attribution-NoDerivs',
120 'Creative Commons Attribution-NonCommercial-NoDerivs',
121 'Creative Commons Attribution-NonCommercial',
122 'Creative Commons Attribution-NonCommercial-ShareAlike',
123 'Creative Commons Attribution-ShareAlike',
125 'Design Public License', # ???
126 'GFDL', # GNU Free Documentation License
127 'LaTeX Project Public License',
128 'OpenContent License',
129 'Open Publication License',
132 'SIL Open Font License',
133 # Non open source licences:
146 'accelleration': 'acceleration',
147 'accessable': 'accessible',
148 'accomodate': 'accommodate',
150 'acording': 'according',
151 'additionaly': 'additionally',
153 'adresses': 'addresses',
154 'adviced': 'advised',
156 'alegorical': 'allegorical',
157 'algorith': 'algorithm',
158 'allpication': 'application',
159 'altough': 'although',
163 'analysator': 'analyzer',
165 'appropiate': 'appropriate',
166 'arraival': 'arrival',
167 'artifical': 'artificial',
168 'artillary': 'artillery',
169 'attemps': 'attempts',
170 'automatize': 'automate',
171 'automatized': 'automated',
172 'automatizes': 'automates',
173 'auxilliary': 'auxiliary',
174 'availavility': 'availability',
175 'availble': 'available',
176 'avaliable': 'available',
177 'availiable': 'available',
178 'backgroud': 'background',
179 'baloons': 'balloons',
180 'becomming': 'becoming',
181 'becuase': 'because',
182 'cariage': 'carriage',
183 'challanges': 'challenges',
184 'changable': 'changeable',
185 'charachters': 'characters',
186 'charcter': 'character',
188 'colorfull': 'colorful',
190 'commerical': 'commercial',
191 'comminucation': 'communication',
192 'commoditiy': 'commodity',
193 'compability': 'compatibility',
194 'compatability': 'compatibility',
195 'compatable': 'compatible',
196 'compatibiliy': 'compatibility',
197 'compatibilty': 'compatibility',
198 'compleatly': 'completely',
199 'complient': 'compliant',
200 'compres': 'compress',
201 'containes': 'contains',
202 'containts': 'contains',
203 'contence': 'contents',
204 'continous': 'continuous',
205 'contraints': 'constraints',
206 'convertor': 'converter',
207 'convinient': 'convenient',
208 'cryptocraphic': 'cryptographic',
210 'debians': 'Debian\'s',
211 'decompres': 'decompress',
212 'definate': 'definite',
213 'definately': 'definitely',
214 'dependancies': 'dependencies',
215 'dependancy': 'dependency',
216 'dependant': 'dependent',
217 'developement': 'development',
218 'developped': 'developed',
219 'deveolpment': 'development',
220 'devided': 'divided',
221 'dictionnary': 'dictionary',
223 'disapeared': 'disappeared',
224 'dissapears': 'disappears',
225 'documentaion': 'documentation',
226 'docuentation': 'documentation',
227 'documantation': 'documentation',
230 'ecspecially': 'especially',
231 'edditable': 'editable',
232 'editting': 'editing',
233 'eletronic': 'electronic',
234 'enchanced': 'enhanced',
235 'encorporating': 'incorporating',
236 'enlightnment': 'enlightenment',
237 'enterily': 'entirely',
238 'enviroiment': 'environment',
239 'environement': 'environment',
240 'excellant': 'excellent',
241 'exlcude': 'exclude',
242 'exprimental': 'experimental',
243 'extention': 'extension',
244 'failuer': 'failure',
245 'familar': 'familiar',
247 'fetaures': 'features',
250 'framwork': 'framework',
251 'fuction': 'function',
252 'fuctions': 'functions',
253 'functionnality': 'functionality',
254 'functonality': 'functionality',
255 'functionaly': 'functionally',
256 'futhermore': 'furthermore',
257 'generiously': 'generously',
258 'grahical': 'graphical',
259 'grahpical': 'graphical',
263 'heirarchically': 'hierarchically',
264 'helpfull': 'helpful',
265 'hierachy': 'hierarchy',
266 'hierarchie': 'hierarchy',
268 'implemantation': 'implementation',
269 'incomming': 'incoming',
270 'incompatabilities': 'incompatibilities',
271 'indended': 'intended',
272 'indendation': 'indentation',
273 'independant': 'independent',
274 'informatiom': 'information',
275 'initalize': 'initialize',
276 'inofficial': 'unofficial',
277 'integreated': 'integrated',
278 'integrety': 'integrity',
279 'integrey': 'integrity',
280 'intendet': 'intended',
281 'interchangable': 'interchangeable',
282 'intermittant': 'intermittent',
284 'langage': 'language',
285 'langauage': 'language',
286 'langugage': 'language',
288 'lesstiff': 'lesstif',
289 'libaries': 'libraries',
290 'licenceing': 'licencing',
293 'loggging': 'logging',
294 'mandrivalinux': 'Mandriva Linux',
295 'maintainance': 'maintenance',
296 'maintainence': 'maintenance',
298 'managable': 'manageable',
299 'manoeuvering': 'maneuvering',
301 'modulues': 'modules',
302 'monochromo': 'monochrome',
303 'multidimensionnal': 'multidimensional',
304 'navagating': 'navigating',
306 'neccesary': 'necessary',
307 'neccessary': 'necessary',
308 'necesary': 'necessary',
309 'nescessary': 'necessary',
310 'noticable': 'noticeable',
311 'optionnal': 'optional',
312 'orientied': 'oriented',
313 'pacakge': 'package',
314 'pachage': 'package',
315 'packacge': 'package',
316 'packege': 'package',
319 'particularily': 'particularly',
320 'persistant': 'persistent',
321 'plattform': 'platform',
322 'ploting': 'plotting',
323 'posible': 'possible',
324 'powerfull': 'powerful',
325 'prefered': 'preferred',
326 'prefferably': 'preferably',
327 'prepaired': 'prepared',
328 'princliple': 'principle',
329 'priorty': 'priority',
330 'proccesors': 'processors',
332 'processsing': 'processing',
333 'processessing': 'processing',
334 'progams': 'programs',
335 'programers': 'programmers',
336 'programm': 'program',
337 'programms': 'programs',
339 'pronnounced': 'pronounced',
340 'prononciation': 'pronunciation',
341 'pronouce': 'pronounce',
342 'protcol': 'protocol',
343 'protocoll': 'protocol',
344 'recieve': 'receive',
345 'recieved': 'received',
346 'redircet': 'redirect',
347 'regulamentations': 'regulations',
349 'repectively': 'respectively',
350 'replacments': 'replacements',
351 'requiere': 'require',
352 'runnning': 'running',
354 'savable': 'saveable',
355 'searchs': 'searches',
356 'separatly': 'separately',
357 'seperate': 'separate',
358 'seperately': 'separately',
359 'seperatly': 'separately',
360 'serveral': 'several',
362 'similiar': 'similar',
363 'simliar': 'similar',
365 'standart': 'standard',
366 'staically': 'statically',
367 'staticly': 'statically',
368 'succesful': 'successful',
369 'succesfully': 'successfully',
370 'suplied': 'supplied',
372 'suppport': 'support',
373 'supportin': 'supporting',
374 'synchonized': 'synchronized',
375 'syncronize': 'synchronize',
376 'syncronizing': 'synchronizing',
377 'syncronus': 'synchronous',
379 'sythesis': 'synthesis',
381 'throught': 'through',
386 'utilites': 'utilities',
387 'utillities': 'utilities',
388 'utilties': 'utilities',
389 'utiltity': 'utility',
390 'utitlty': 'utility',
391 'variantions': 'variations',
392 'varient': 'variant',
394 'vicefersa': 'vice-versa',
401 DEFAULT_INVALID_REQUIRES = ('^is$', '^not$', '^owned$', '^by$', '^any$', '^package$', '^libsafe\.so\.')
403 VALID_GROUPS = Config.getOption('ValidGroups', None)
404 VALID_DOMAINS = Config.getOption('ValidDomains', None)
405 VALID_SUBDOMAINS = Config.getOption('ValidSubDomains', None)
407 if VALID_GROUPS is None: # get defaults from rpm package only if it's not set
408 VALID_GROUPS = Pkg.get_default_valid_rpmgroups()
409 VALID_LICENSES = Config.getOption('ValidLicenses', DEFAULT_VALID_LICENSES)
410 INVALID_REQUIRES = map(re.compile, Config.getOption('InvalidRequires', DEFAULT_INVALID_REQUIRES))
411 packager_regex = re.compile(Config.getOption('Packager'))
412 changelog_version_regex = re.compile('[^>]([^ >]+)\s*$')
413 changelog_text_version_regex = re.compile('^\s*-\s*((\d+:)?[\w\.]+-[\w\.]+)')
414 release_ext = Config.getOption('ReleaseExtension')
415 extension_regex = release_ext and re.compile(release_ext)
416 use_version_in_changelog = Config.getOption('UseVersionInChangelog', True)
417 devel_number_regex = re.compile('(.*?)([0-9.]+)(_[0-9.]+)?-devel')
418 lib_devel_number_regex = re.compile('^lib(.*?)([0-9.]+)(_[0-9.]+)?-devel')
419 invalid_url_regex = re.compile(Config.getOption('InvalidURL'), re.IGNORECASE)
420 lib_package_regex = re.compile('(?:^(?:compat-)?lib.*?(\.so.*)?|libs?[\d-]*)$', re.IGNORECASE)
421 leading_space_regex = re.compile('^\s+')
422 pkg_config_regex = re.compile('^/usr/(?:lib\d*|share)/pkgconfig/')
423 license_regex = re.compile('\(([^)]+)\)|\s(?:and|or)\s')
424 invalid_version_regex = re.compile('([0-9](?:rc|alpha|beta|pre).*)', re.IGNORECASE)
425 # () are here for grouping purpose in the regexp
426 forbidden_words_regex = re.compile('(' + Config.getOption('ForbiddenWords') + ')', re.IGNORECASE)
427 valid_buildhost_regex = re.compile(Config.getOption('ValidBuildHost'))
428 valid_filedep_regex=re.compile('(?:/s?bin/|^/etc/|^/usr/lib/sendmail$)')
429 use_epoch = Config.getOption('UseEpoch', False)
430 use_utf8 = Config.getOption('UseUTF8', Config.USEUTF8_DEFAULT)
431 max_line_len = Config.getOption('MaxLineLength', 79)
432 tag_regex = re.compile('^((?:Auto(?:Req|Prov|ReqProv)|Build(?:Arch(?:itectures)?|Root)|(?:Build)?Conflicts|(?:Build)?(?:Pre)?Requires|Copyright|(?:CVS|SVN)Id|Dist(?:ribution|Tag|URL)|DocDir|(?:Build)?Enhances|Epoch|Exclu(?:de|sive)(?:Arch|OS)|Group|Icon|License|Name|No(?:Patch|Source)|Obsoletes|Packager|Patch\d*|Prefix(?:es)?|Provides|(?:Build)?Recommends|Release|RHNPlatform|Serial|Source\d*|(?:Build)?Suggests|Summary|(?:Build)?Supplements|(?:Bug)?URL|Vendor|Version)(?:\([^)]+\))?:)\s*\S', re.IGNORECASE)
434 sentence_break_regex = re.compile(r'(^|[.:;!?])\s*$')
435 so_dep_regex = re.compile(r'\.so(\.[0-9a-zA-z]+)*(\([^)]*\))*$')
436 # we assume that no rpm packages existed before rpm itself existed...
437 oldest_changelog_timestamp = calendar.timegm(time.strptime("1995-01-01", "%Y-%m-%d"))
439 _enchant_checkers = {}
440 def spell_check(pkg, str, fmt, lang, ignored):
448 checker = _enchant_checkers.get(lang)
449 if not checker and lang not in _enchant_checkers:
451 checker = enchant.checker.SpellChecker(
452 lang, filters = [ enchant.tokenize.EmailFilter,
453 enchant.tokenize.URLFilter,
454 enchant.tokenize.WikiWordFilter ])
455 except enchant.DictNotFoundError:
456 printInfo(pkg, 'enchant-dictionary-not-found', lang)
458 _enchant_checkers[lang] = checker
461 # squeeze whitespace to ease leading context check
462 checker.set_text(re.sub(r'\s+', ' ', str))
463 uppername = pkg.name.upper()
464 upperparts = uppername.split('-')
465 if lang.startswith('en'):
466 ups = [x + "'S" for x in upperparts]
467 upperparts.extend(ups)
470 # Skip already warned and ignored words
471 if err.word in warned or err.word in ignored:
474 # Skip all capitalized words that do not start a sentence
475 if err.word[0].isupper() and not \
476 sentence_break_regex.search(checker.leading_context(3)):
479 upperword = err.word.upper()
481 # Skip all uppercase words
482 if err.word == upperword:
485 # Skip errors containing package name or equal to a
486 # "component" of it, case insensitively
487 if uppername in upperword or upperword in upperparts:
490 # Work around enchant's digit tokenizing behavior:
491 # http://github.com/rfk/pyenchant/issues/issue/3
492 if checker.leading_context(1).isdigit() or \
493 checker.trailing_context(1).isdigit():
497 sug = ', '.join(checker.suggest()[:3])
500 printWarning(pkg, 'spelling-error', fmt % lang, err.word, sug)
506 if not enchant or not dict_found:
507 for seq in str.split():
508 for word in re.split('[^a-z]+', seq.lower()):
511 correct = BAD_WORDS.get(word)
518 if word in warned or word in ignored:
520 printWarning(pkg, 'spelling-error', fmt % lang, word, '->',
525 class TagsCheck(AbstractCheck.AbstractCheck):
528 AbstractCheck.AbstractCheck.__init__(self, 'TagsCheck')
530 def _unexpanded_macros(self, pkg, tagname, value, is_url=False):
533 for match in AbstractCheck.macro_regex.findall(str(value)):
534 # Do not warn about %XX URL escapes
535 if is_url and re.match('^%[0-9A-F][0-9A-F]$', match, re.I):
537 printWarning(pkg, 'unexpanded-macro', tagname, match)
539 def check(self, pkg):
541 packager = pkg[rpm.RPMTAG_PACKAGER]
542 self._unexpanded_macros(pkg, 'Packager', packager)
544 printError(pkg, 'no-packager-tag')
545 elif Config.getOption('Packager') and \
546 not packager_regex.search(packager):
547 printWarning(pkg, 'invalid-packager', packager)
549 version = pkg[rpm.RPMTAG_VERSION]
550 self._unexpanded_macros(pkg, 'Version', version)
552 printError(pkg, 'no-version-tag')
554 res = invalid_version_regex.search(version)
556 printError(pkg, 'invalid-version', version)
558 release = pkg[rpm.RPMTAG_RELEASE]
559 self._unexpanded_macros(pkg, 'Release', release)
561 printError(pkg, 'no-release-tag')
562 elif release_ext and not extension_regex.search(release):
563 printWarning(pkg, 'not-standard-release-extension', release)
565 epoch = pkg[rpm.RPMTAG_EPOCH]
568 printError(pkg, 'no-epoch-tag')
571 printWarning(pkg, 'unreasonable-epoch', epoch)
575 for o in (x for x in pkg.obsoletes() if x[1] and x[2][0] is None):
576 printWarning(pkg, 'no-epoch-in-obsoletes',
577 apply(Pkg.formatRequire, o))
578 for c in (x for x in pkg.conflicts() if x[1] and x[2][0] is None):
579 printWarning(pkg, 'no-epoch-in-conflicts',
580 apply(Pkg.formatRequire, c))
581 for p in (x for x in pkg.provides() if x[1] and x[2][0] is None):
582 printWarning(pkg, 'no-epoch-in-provides',
583 apply(Pkg.formatRequire, p))
586 deps = pkg.requires() + pkg.prereq()
588 is_devel = FilesCheck.devel_regex.search(name)
589 is_source = pkg.isSource()
591 value = apply(Pkg.formatRequire, d)
592 if use_epoch and d[1] and d[2][0] is None \
593 and d[0][0:7] != 'rpmlib(':
594 printWarning(pkg, 'no-epoch-in-dependency', value)
595 for r in INVALID_REQUIRES:
597 printError(pkg, 'invalid-dependency', d[0])
599 if d[0].startswith('/usr/local/'):
600 printError(pkg, 'invalid-dependency', d[0])
602 if d[0].startswith('/') and not valid_filedep_regex.search(d[0]):
603 printWarning(pkg, 'invalid-filepath-dependency', d[0])
605 if not devel_depend and not is_devel and not is_source and \
606 FilesCheck.devel_regex.search(d[0]):
607 printError(pkg, 'devel-dependency', d[0])
609 if is_source and lib_devel_number_regex.search(d[0]):
610 printError(pkg, 'invalid-build-requires', d[0])
611 if not is_source and not is_devel:
612 res = lib_package_regex.search(d[0])
613 if res and not res.group(1) and not d[1]:
614 printError(pkg, 'explicit-lib-dependency', d[0])
615 if d[1] == rpm.RPMSENSE_EQUAL and d[2][2] is not None:
616 printWarning(pkg, 'requires-on-release', value)
617 self._unexpanded_macros(pkg, 'dependency %s' % (value,), value)
619 self._unexpanded_macros(pkg, 'Name', name)
621 printError(pkg, 'no-name-tag')
623 if is_devel and not is_source:
624 base = is_devel.group(1)
628 for fname in pkg.files():
629 if fname.endswith('.so'):
631 if pkg_config_regex.match(fname) and fname.endswith('.pc'):
634 base_or_libs = base + '*/' + base + '-libs/lib' + base + '*'
635 # try to match *%_isa as well (e.g. "(x86-64)", "(x86-32)")
636 base_or_libs_re = re.compile(
637 '^(lib)?%s(-libs)?[\d_]*(\(\w+-\d+\))?' % re.escape(base))
639 if base_or_libs_re.match(d[0]):
643 printWarning(pkg, 'no-dependency-on', base_or_libs)
645 exp = (epoch, version, None)
646 sexp = Pkg.versionToString(exp)
648 printWarning(pkg, 'no-version-dependency-on',
650 elif dep[2][:2] != exp[:2]:
652 'incoherent-version-dependency-on',
654 Pkg.versionToString((dep[2][0],
657 res = devel_number_regex.search(name)
659 printWarning(pkg, 'no-major-in-name', name)
662 prov = res.group(1) + res.group(2) + '-devel'
664 prov = res.group(1) + '-devel'
666 if prov not in (x[0] for x in pkg.provides()):
667 printWarning(pkg, 'no-provides', prov)
670 found_pkg_config_dep = False
671 for p in (x[0] for x in pkg.provides()):
672 if (p.startswith("pkgconfig(")):
673 found_pkg_config_dep = True
675 if not found_pkg_config_dep:
676 printWarning(pkg, 'no-pkg-config-provides')
678 # List of words to ignore in spell check
679 ignored_words = set()
680 for pf in pkg.files():
681 ignored_words.update(pf.split('/'))
682 ignored_words.update((x[0] for x in pkg.provides()))
683 ignored_words.update((x[0] for x in pkg.requires()))
684 ignored_words.update((x[0] for x in pkg.conflicts()))
685 ignored_words.update((x[0] for x in pkg.obsoletes()))
687 summary = pkg[rpm.RPMTAG_SUMMARY]
689 printError(pkg, 'no-summary-tag')
691 if not pkg[rpm.RPMTAG_HEADERI18NTABLE]:
692 self._unexpanded_macros(pkg, 'Summary', summary)
694 for lang in pkg[rpm.RPMTAG_HEADERI18NTABLE]:
695 self.check_summary(pkg, lang, ignored_words)
697 description = pkg[rpm.RPMTAG_DESCRIPTION]
699 printError(pkg, 'no-description-tag')
701 if len(pkg[rpm.RPMTAG_DESCRIPTION].partition('Authors:')[0])-4 < len(pkg[rpm.RPMTAG_SUMMARY]):
702 printWarning(pkg, 'description-shorter-than-summary')
704 if not pkg[rpm.RPMTAG_HEADERI18NTABLE]:
705 self._unexpanded_macros(pkg, '%description', description)
707 for lang in pkg[rpm.RPMTAG_HEADERI18NTABLE]:
708 self.check_description(pkg, lang, ignored_words)
710 valid_groups = VALID_GROUPS
712 for d in VALID_DOMAINS:
713 if d == 'Applications':
714 for dd in ['Multimedia', 'Social', 'Web', 'Telephony', 'Messaging', 'PIM', 'Network', 'Navigation', 'Other', 'Game', 'Tasks', 'Music', 'Photo', 'Video']:
715 app_groups = app_groups + ("%s/%s" %(d,dd), )
717 for sd in VALID_SUBDOMAINS:
718 valid_groups = valid_groups + ("%s/%s" %(d,sd), )
720 valid_groups = valid_groups + app_groups
722 group = pkg[rpm.RPMTAG_GROUP]
723 self._unexpanded_macros(pkg, 'Group', group)
725 for p in ['TBD', 'TO BE', 'FILLED', 'Unspecified', 'TO_BE' ]:
727 printWarning(pkg, 'group-placeholder-not-allowed', group)
729 printError(pkg, 'no-group-tag')
730 elif pkg.name.endswith('-devel') and not group.startswith('Development/') and not group.endswith('/Development'):
731 printWarning(pkg, 'devel-package-with-non-devel-group', group)
732 elif group not in valid_groups:
733 printWarning(pkg, 'non-standard-group', group)
735 buildhost = pkg[rpm.RPMTAG_BUILDHOST]
736 self._unexpanded_macros(pkg, 'BuildHost', buildhost)
738 printError(pkg, 'no-buildhost-tag')
739 elif Config.getOption('ValidBuildHost') and \
740 not valid_buildhost_regex.search(buildhost):
741 printWarning(pkg, 'invalid-buildhost', buildhost)
743 changelog = pkg[rpm.RPMTAG_CHANGELOGNAME]
745 printError(pkg, 'no-changelogname-tag')
747 clt = pkg[rpm.RPMTAG_CHANGELOGTEXT]
748 if use_version_in_changelog:
749 ret = changelog_version_regex.search(changelog[0])
751 # we also allow the version specified as the first
752 # thing on the first line of the text
753 ret = changelog_text_version_regex.search(clt[0])
755 printWarning(pkg, 'no-version-in-last-changelog')
756 elif version and release:
757 srpm = pkg[rpm.RPMTAG_SOURCERPM] or ''
758 # only check when source name correspond to name
759 if srpm[0:-8] == '%s-%s-%s' % (name, version, release):
760 expected = [version + '-' + release]
761 if epoch is not None: # regardless of use_epoch
762 expected[0] = str(epoch) + ':' + expected[0]
763 # Allow EVR in changelog without release extension,
764 # the extension is often a macro or otherwise dynamic.
767 extension_regex.sub('', expected[0]))
768 if ret.group(1) not in expected:
769 if len(expected) == 1:
770 expected = expected[0]
771 printWarning(pkg, 'incoherent-version-in-changelog',
772 ret.group(1), expected)
775 changelog = changelog + clt
776 if use_utf8 and not Pkg.is_utf8_str(' '.join(changelog)):
777 printError(pkg, 'tag-not-utf8', '%changelog')
779 clt = pkg[rpm.RPMTAG_CHANGELOGTIME][0]
781 clt -= clt % (24*3600) # roll back to 00:00:00, see #246
782 if clt < oldest_changelog_timestamp:
783 printWarning(pkg, 'changelog-time-overflow',
784 time.strftime("%Y-%m-%d", time.gmtime(clt)))
785 elif clt > time.time():
786 printError(pkg, 'changelog-time-in-future',
787 time.strftime("%Y-%m-%d", time.gmtime(clt)))
789 # for provide_name in (x[0] for x in pkg.provides()):
790 # if name == provide_name:
791 # printWarning(pkg, 'package-provides-itself')
794 def split_license(license):
795 return (x.strip() for x in
796 (l for l in license_regex.split(license) if l))
798 rpm_license = pkg[rpm.RPMTAG_LICENSE]
800 printError(pkg, 'no-license')
803 for p in ['TBD', 'TO BE', 'FILLED', 'Unspecified', 'TO_BE', 'TIZEN', 'samsung', 'Samsung' ]:
805 printWarning(pkg, 'license-placeholder-not-allowed', rpm_license)
806 valid_license = False
808 if valid_license and rpm_license not in VALID_LICENSES:
809 for l1 in split_license(rpm_license):
810 if l1 in VALID_LICENSES:
812 for l2 in split_license(l1):
813 if l2 not in VALID_LICENSES:
814 printWarning(pkg, 'invalid-license', l2)
815 valid_license = False
818 if not valid_license:
819 self._unexpanded_macros(pkg, 'License', rpm_license)
821 for tag in ('URL', 'BugURL'):
822 if hasattr(rpm, 'RPMTAG_%s' % tag.upper()):
823 url = pkg[getattr(rpm, 'RPMTAG_%s' % tag.upper())]
824 self._unexpanded_macros(pkg, tag, url, is_url = True)
826 (scheme, netloc) = urlparse(url)[0:2]
827 if not scheme or not netloc or "." not in netloc or \
828 scheme not in ('http', 'https', 'ftp') or \
829 (Config.getOption('InvalidURL') and \
830 invalid_url_regex.search(url)):
831 printWarning(pkg, 'invalid-url', tag, url)
833 self.check_url(pkg, tag, url)
835 printWarning(pkg, 'no-url-tag')
837 obs_names = [x[0] for x in pkg.obsoletes()]
838 prov_names = [x[0].split(':/')[0] for x in pkg.provides()]
839 conf_names = map(lambda x: x[0].split(':/')[0], pkg.conflicts())
841 for o in (x for x in obs_names if x not in prov_names):
842 printWarning(pkg, 'obsolete-not-provided', o)
843 for o in pkg.obsoletes():
844 value = apply(Pkg.formatRequire, o)
845 self._unexpanded_macros(pkg, 'Obsoletes %s' % (value,), value)
847 # TODO: should take versions, <, <=, =, >=, > into account here
848 # https://bugzilla.redhat.com/460872
849 useless_provides = []
852 printWarning(pkg, 'conflicts-with-provides', p)
853 if prov_names.count(p) != 1 and p not in useless_provides:
854 useless_provides.append(p)
855 for p in useless_provides:
856 printError(pkg, 'useless-provides', p)
858 for p in pkg.provides():
859 value = apply(Pkg.formatRequire, p)
860 self._unexpanded_macros(pkg, 'Provides %s' % (value,), value)
862 for c in pkg.conflicts():
863 value = apply(Pkg.formatRequire, c)
864 self._unexpanded_macros(pkg, 'Conflicts %s' % (value,), value)
866 obss = pkg.obsoletes()
868 provs = pkg.provides()
871 if Pkg.rangeCompare(obs, prov):
872 printWarning(pkg, 'self-obsoletion', '%s obsoletes %s' %
873 (apply(Pkg.formatRequire, obs),
874 apply(Pkg.formatRequire, prov)))
876 for tag in ('Distribution', 'DistTag', 'ExcludeArch', 'ExcludeOS',
878 if hasattr(rpm, 'RPMTAG_%s' % tag.upper()):
879 self._unexpanded_macros(
880 pkg, tag, pkg[getattr(rpm, 'RPMTAG_%s' % tag.upper())])
883 def check_description(self, pkg, lang, ignored_words):
884 description = pkg.langtag(rpm.RPMTAG_DESCRIPTION, lang)
885 self._unexpanded_macros(pkg, '%%description -l %s' % lang, description)
886 utf8desc = description
888 utf8desc = Pkg.to_utf8(description).decode('utf-8')
889 spell_check(pkg, utf8desc, '%%description -l %s', lang, ignored_words)
890 for l in utf8desc.splitlines():
891 if len(l) > max_line_len:
892 printError(pkg, 'description-line-too-long', lang, l)
893 res = forbidden_words_regex.search(l)
894 if res and Config.getOption('ForbiddenWords'):
895 printWarning(pkg, 'description-use-invalid-word', lang,
897 res = tag_regex.search(l)
899 printWarning(pkg, 'tag-in-description', lang, res.group(1))
900 if use_utf8 and not Pkg.is_utf8_str(description):
901 printError(pkg, 'tag-not-utf8', '%description', lang)
903 def check_summary(self, pkg, lang, ignored_words):
904 summary = pkg.langtag(rpm.RPMTAG_SUMMARY, lang)
905 self._unexpanded_macros(pkg, 'Summary(%s)' % lang, summary)
906 utf8summary = summary
908 utf8summary = Pkg.to_utf8(summary).decode('utf-8')
909 spell_check(pkg, utf8summary, 'Summary(%s)', lang, ignored_words)
911 printError(pkg, 'summary-on-multiple-lines', lang)
912 if summary[0] != summary[0].upper() and not summary.startswith("openSUSE"):
913 printWarning(pkg, 'summary-not-capitalized', lang, summary)
914 if summary[-1] == '.':
915 printWarning(pkg, 'summary-ended-with-dot', lang, summary)
916 if len(utf8summary) > max_line_len:
917 printError(pkg, 'summary-too-long', lang, summary)
918 if leading_space_regex.search(summary):
919 printError(pkg, 'summary-has-leading-spaces', lang, summary)
920 res = forbidden_words_regex.search(summary)
921 if res and Config.getOption('ForbiddenWords'):
922 printWarning(pkg, 'summary-use-invalid-word', lang, res.group(1))
924 sepchars = '[\s' + punct + ']'
925 res = re.search('(?:^|\s)(%s)(?:%s|$)' %
926 (re.escape(pkg.name), sepchars),
927 summary, re.IGNORECASE | re.UNICODE)
929 printWarning(pkg, 'name-repeated-in-summary', lang,
931 if use_utf8 and not Pkg.is_utf8_str(summary):
932 printError(pkg, 'tag-not-utf8', 'Summary', lang)
935 # Create an object to enable the auto registration of the test
938 # Add information about checks
941 'The "Summary:" must not exceed %d characters.' % max_line_len,
944 '''The version string must not contain the pre, alpha, beta or rc suffixes
945 because when the final version will be out, you will have to use an Epoch tag
946 to make the package upgradable. Instead put it in the release tag, prefixed
947 with something you have control over.''',
950 '''The value of this tag appears to be misspelled. Please double-check.''',
953 '''There is no Packager tag in your package. You have to specify a packager
954 using the Packager tag. Ex: Packager: John Doe <john.doe@example.com>.''',
957 '''The packager email must end with an email compatible with the Packager
958 option of rpmlint. Please change it and rebuild your package.''',
961 '''There is no Version tag in your package. You have to specify a version using
965 '''There is no Release tag in your package. You have to specify a release using
968 'not-standard-release-extension',
969 'Your release tag must match the regular expression ' + release_ext + '.',
972 '''There is no Name tag in your package. You have to specify a name using the
975 'conflicts-with-provides',
976 '''The same symbolic name is provided and conflicted. This package might be
977 uninstallable, if versioning matches''',
979 'non-coherent-filename',
980 '''The file which contains the package should be named
981 <NAME>-<VERSION>-<RELEASE>.<ARCH>.rpm.''',
987 'incoherent-version-dependency-on',
991 'no-version-dependency-on',
996 '''The major number of the library isn't included in the package's name.
999 'description-shorter-than-summary',
1000 '''The package description should be longer than the summary. be a bit more
1001 verbose, please.''',
1004 '''Your library package doesn't provide the -devel name without the major
1005 version included.''',
1008 '''There is no Summary tag in your package. You have to describe your package
1009 using this tag. To insert it, just insert a tag 'Summary'.''',
1011 'summary-on-multiple-lines',
1012 '''Your summary must fit on one line. Please make it shorter and rebuild the
1015 'summary-not-capitalized',
1016 '''Summary doesn't begin with a capital letter.''',
1018 'summary-ended-with-dot',
1019 '''Summary ends with a dot.''',
1021 'summary-has-leading-spaces',
1022 '''Summary begins with whitespace which will waste space when displayed.''',
1024 'no-description-tag',
1025 '''The description of the package is empty or missing. To add it, insert a
1026 %description section in your spec file, add a textual description of the
1027 package after it, and rebuild the package.''',
1029 'description-line-too-long',
1030 '''Your description lines must not exceed %d characters. If a line is exceeding
1031 this number, cut it to fit in two lines.''' % max_line_len,
1033 'tag-in-description',
1034 '''Something that looks like a tag was found in the package's description.
1035 This may indicate a problem where the tag was not actually parsed as a tag
1036 but just textual description content, thus being a no-op. Verify if this is
1037 the case, and move the tag to a place in the specfile where %description
1038 won't fool the specfile parser, and rebuild the package.''',
1041 '''There is no Group tag in your package. You have to specify a valid group
1042 in your spec file using the Group tag.''',
1044 'devel-package-with-non-devel-group',
1045 '''The package ends with -devel but does not have a RPM group starting with
1048 'non-standard-group',
1049 '''The value of the Group tag in the package is not valid. Valid groups are
1050 listed here: https://wiki.tizen.org/wiki/Packaging/Guidelines#Group_Tag''',
1052 'group-placeholder-not-allowed',
1053 '''The value of the Group tag is a placeholder, please use a proper value.
1054 Valid groups are listed here:
1055 https://wiki.tizen.org/wiki/Packaging/Guidelines#Group_Tag
1058 'no-changelogname-tag',
1059 '''There is no changelog. Please insert a '%changelog' section heading in your
1060 spec file and prepare your changes file using e.g. the 'gbs ch' command.''',
1062 'no-version-in-last-changelog',
1063 '''The latest changelog entry doesn't contain a version. Please insert the
1064 version that is coherent with the version of the package and rebuild it.''',
1066 'incoherent-version-in-changelog',
1067 '''The latest entry in %changelog contains a version identifier that is not
1068 coherent with the epoch:version-release tuple of the package.''',
1070 'changelog-time-overflow',
1071 '''The timestamp of the latest entry in %changelog is suspiciously far away in
1072 the past; it is possible that it is actually so much in the future that it
1073 has overflowed rpm's timestamp representation.''',
1075 'changelog-time-in-future',
1076 '''The timestamp of the latest entry in %changelog is in the future.''',
1079 '''There is no License tag in your spec file. You have to specify one license
1080 for your program (eg. GPL). To insert this tag, just insert a 'License' in
1084 '''The value of the License tag was not recognized. Known values are:
1085 "%s".''' % '", "'.join(VALID_LICENSES),
1087 'license-placeholder-not-allowed',
1088 '''The value of the License tag was not recognized and seems to be a placeholder. Known values are:
1089 "%s".''' % '", "'.join(VALID_LICENSES),
1091 'obsolete-not-provided',
1092 '''If a package is obsoleted by a compatible replacement, the obsoleted package
1093 should also be provided in order to not cause unnecessary dependency breakage.
1094 If the obsoleting package is not a compatible replacement for the old one,
1095 leave out the Provides.''',
1097 'invalid-dependency',
1098 '''An invalid dependency has been detected. It usually means that the build of
1099 the package was buggy.''',
1102 '''There is no Epoch tag in your package. You have to specify an epoch using the
1105 'unreasonable-epoch',
1106 '''The value of your Epoch tag is unreasonably large (> 99).''',
1108 'no-epoch-in-obsoletes',
1109 '''Your package contains a versioned Obsoletes entry without an Epoch.''',
1111 'no-epoch-in-conflicts',
1112 '''Your package contains a versioned Conflicts entry without an Epoch.''',
1114 'no-epoch-in-provides',
1115 '''Your package contains a versioned Provides entry without an Epoch.''',
1117 'no-epoch-in-dependency',
1118 '''Your package contains a versioned dependency without an Epoch.''',
1121 '''Your package has a dependency on a devel package but it's not a devel
1124 'invalid-build-requires',
1125 '''Your source package contains a dependency not compliant with the lib64
1126 naming. This BuildRequires dependency will not be resolved on lib64 platforms
1129 'explicit-lib-dependency',
1130 '''You must let rpm find the library dependencies by itself. Do not put unneeded
1131 explicit Requires: tags.''',
1134 '''This package provides 2 times the same capacity. It should only provide it
1137 'invalid-filepath-dependency',
1138 '''A package has a file or path based dependency that is not resolveable for
1139 package solvers because it is not in the whitelist for path based dependencies
1140 and therefore not available in repository metadata. Please use a symbolic requires
1141 instead or require a file in bin or /etc.''',
1144 '''The character encoding of the value of this tag is not UTF-8.''',
1146 'requires-on-release',
1147 '''This rpm requires a specific release of another package.''',
1150 '''The URL tag is missing. Please add a http or ftp link to the project location.''',
1152 'no-pkg-config-provides',
1153 '''The package installs a .pc file but does not provide pkgconfig(..) provides.
1154 The most likely reason for that is that it was built without BuildRequires: pkg-config.
1155 Please double check your build dependencies.''',
1157 'name-repeated-in-summary',
1158 '''The name of the package is repeated in its summary. This is often redundant
1159 information and looks silly in various programs' output. Make the summary
1160 brief and to the point without including redundant information in it.''',
1162 'enchant-dictionary-not-found',
1163 '''A dictionary for the Enchant spell checking library is not available for
1164 the language given in the info message. Spell checking will proceed with
1165 rpmlint's built-in implementation for localized tags in this language.
1166 For better spell checking results in this language, install the appropriate
1167 dictionary that Enchant will use for this language, often for example
1168 hunspell-* or aspell-*.''',
1171 '''The package obsoletes itself. This is known to cause errors in various
1172 tools and should thus be avoided, usually by using appropriately versioned
1173 Obsoletes and/or Provides and avoiding unversioned ones.''',
1176 '''This tag contains something that looks like an unexpanded macro; this is
1177 often the sign of a misspelling. Please check your specfile.''',
1179 'private-shared-object-provides',
1180 '''A shared object soname provides is provided by a file in a path from which
1181 other packages should not directly load shared objects from. Such shared
1182 objects should thus not be depended on and they should not result in provides
1183 in the containing package. Get rid of the provides if appropriate, for example
1184 by filtering it out during build. Note that in some cases this may require
1185 disabling rpmbuild's internal dependency generator.''',
1188 # TagsCheck.py ends here
1191 # indent-tabs-mode: nil
1192 # py-indent-offset: 4