def check_binary(self, pkg):
return
- def check_spec(self, pkg, spec_file, spec_lines=[]):
+ def check_spec(self, pkg, spec_file):
return
def check_url(self, pkg, tag, url):
from Filter import addDetails, printError
from Pkg import getstatusoutput
import AbstractCheck
+import Config
STANDARD_BIN_DIRS = ['/bin/', '/sbin/', '/usr/bin/', '/usr/sbin/']
+DEFAULT_APPDATA_CHECKER = ('appstream-util', 'validate-relax')
+
+appdata_checker = Config.getOption("AppDataChecker", DEFAULT_APPDATA_CHECKER)
class AppDataCheck(AbstractCheck.AbstractFilesCheck):
root = pkg.dirName()
f = root + filename
try:
- st = getstatusoutput(('appdata-validate', f), True)
+ st = getstatusoutput(appdata_checker + (f,))
except OSError:
- # ignore the check if appdata-validate is not install
+ # ignore if the checker is not installed
return
if st[0]:
- for line in st[1].splitlines():
- if 'problems detected' in line:
- printError(pkg, 'invalid-appdata-file', filename)
+ printError(pkg, 'invalid-appdata-file', filename)
check = AppDataCheck()
addDetails(
'invalid-appdata-file',
-'''appdata file is not valid, check with appdata-validate''',
+'''appdata file is not valid, check with %s''' % (" ".join(appdata_checker)),
)
# Local variables:
multi_pkg = False
srpm = pkg[rpm.RPMTAG_SOURCERPM]
if srpm:
- res = srcname_regex.search(Pkg.b2s(srpm))
+ res = srcname_regex.search(srpm)
if res:
multi_pkg = (pkg.name != res.group(1))
import rpm
from Filter import addDetails, printError, printWarning
-from Pkg import b2s, catcmd, getstatusoutput, is_utf8, is_utf8_str
+from Pkg import b2s, catcmd, getstatusoutput, is_utf8, is_utf8_bytestr
import AbstractCheck
import Config
sofile_regex = re.compile('/lib(64)?/(.+/)?lib[^/]+\.so$')
devel_regex = re.compile('(.*)-(debug(info)?|devel|headers|source|static)$')
debuginfo_package_regex = re.compile('-debug(info)?$')
-lib_regex = re.compile('lib(64)?/lib[^/]*\.so\..*')
+lib_regex = re.compile('lib(64)?/lib[^/]*(\.so\..*|-[0-9.]+\.so)')
ldconfig_regex = re.compile('^[^#]*ldconfig', re.MULTILINE)
depmod_regex = re.compile('^[^#]*depmod', re.MULTILINE)
install_info_regex = re.compile('^[^#]*install-info', re.MULTILINE)
return (chunk, istext)
# See Python sources for a full list of the values here.
-# http://hg.python.org/cpython/file/tip/Lib/importlib/_bootstrap.py
+# http://hg.python.org/cpython/file/tip/Lib/importlib/_bootstrap_external.py
# http://hg.python.org/cpython/file/2.7/Python/import.c
_python_magic_values = {
'2.2': 60717,
'3.2': 3180,
'3.3': 3230,
'3.4': 3310,
+ '3.5': 3350,
}
def check(self, pkg):
- files = pkg.files()
-
if use_utf8:
- for filename in files:
- if not is_utf8_str(filename):
- printError(pkg, 'filename-not-utf8', filename)
+ for filename in pkg.header[rpm.RPMTAG_FILENAMES] or ():
+ if not is_utf8_bytestr(filename):
+ printError(pkg, 'filename-not-utf8', b2s(filename))
# Rest of the checks are for binary packages only
if pkg.isSource():
return
+ files = pkg.files()
+
# Check if the package is a development package
devel_pkg = devel_regex.search(pkg.name)
python_dep_error = False
lib_file = False
non_lib_file = None
- log_file = None
+ log_files = []
logrotate_file = False
debuginfo_srcs = False
debuginfo_debugs = False
mode_is_exec = mode & int("111", 8)
if log_regex.search(f):
- log_file = f
+ log_files.append(f)
# Hardlink check
- hardlink = hardlinks.get((rdev, inode))
- if hardlink and os.path.dirname(hardlink) != os.path.dirname(f):
- printWarning(pkg, 'cross-directory-hard-link', f, hardlink)
- hardlinks[(rdev, inode)] = f
+ for hardlink in hardlinks.get((rdev, inode), ()):
+ if os.path.dirname(hardlink) != os.path.dirname(f):
+ printWarning(pkg, 'cross-directory-hard-link', f, hardlink)
+ hardlinks.setdefault((rdev, inode), []).append(f)
# normal file check
if stat.S_ISREG(mode):
# set[ug]id bit check
if stat.S_ISGID & mode or stat.S_ISUID & mode:
if stat.S_ISUID & mode:
- printError(pkg, 'setuid-binary', f, user, oct(perm))
+ printError(pkg, 'setuid-binary', f, user, "%o" % perm)
if stat.S_ISGID & mode:
if not (group == 'games' and
(games_path_regex.search(f) or
games_group_regex.search(
pkg[rpm.RPMTAG_GROUP]))):
printError(pkg, 'setgid-binary', f, group,
- oct(perm))
+ "%o" % perm)
if mode & int("777", 8) != int("755", 8):
printError(pkg, 'non-standard-executable-perm', f,
- oct(perm))
+ "%o" % perm)
# Prefetch scriptlets, strip quotes from them (#169)
- postin = b2s(pkg[rpm.RPMTAG_POSTIN]) or \
+ postin = pkg[rpm.RPMTAG_POSTIN] or \
pkg.scriptprog(rpm.RPMTAG_POSTINPROG)
if postin:
postin = quotes_regex.sub('', postin)
- postun = b2s(pkg[rpm.RPMTAG_POSTUN]) or \
+ postun = pkg[rpm.RPMTAG_POSTUN] or \
pkg.scriptprog(rpm.RPMTAG_POSTUNPROG)
if postun:
postun = quotes_regex.sub('', postun)
# check ldconfig call in %post and %postun
if lib_regex.search(f):
+ if devel_pkg:
+ printError(pkg, 'non-devel-file-in-devel-package', f)
if not postin:
printError(pkg, 'library-without-ldconfig-postin', f)
else:
elif not install_info_regex.search(postin):
printError(pkg, 'postin-without-install-info', f)
- preun = b2s(pkg[rpm.RPMTAG_PREUN]) or \
+ preun = pkg[rpm.RPMTAG_PREUN] or \
pkg.scriptprog(rpm.RPMTAG_PREUNPROG)
if not postun and not preun:
printError(pkg,
if perl_temp_file_regex.search(f):
printWarning(pkg, 'perl-temp-file', f)
- is_buildconfig = buildconfigfile_regex.search(f) and True
+ is_buildconfig = istext and buildconfigfile_regex.search(f)
# check rpaths in buildconfig files
if is_buildconfig:
if res:
if not mode_is_exec:
printWarning(pkg, 'non-executable-in-bin', f,
- oct(perm))
+ "%o" % perm)
else:
exe = res.group(1)
if "/" not in exe:
ok_nonreadable = True
break
if not ok_nonreadable:
- printError(pkg, 'non-readable', f, oct(perm))
+ printError(pkg, 'non-readable', f, "%o" % perm)
if size == 0 and not normal_zero_length_regex.search(f) and \
f not in ghost_files:
printError(pkg, 'zero-length', f)
if mode & stat.S_IWOTH:
- printError(pkg, 'world-writable', f, oct(perm))
+ printError(pkg, 'world-writable', f, "%o" % perm)
if not perl_dep_error:
res = perl_regex.search(f)
# normal executable check
if mode & stat.S_IXUSR and perm != int("755", 8):
printError(pkg, 'non-standard-executable-perm',
- f, oct(perm))
+ f, "%o" % perm)
if mode_is_exec:
if f in config_files:
printError(pkg, 'executable-marked-as-config-file', f)
interpreter)
if mode_is_exec:
printError(pkg, 'executable-sourced-script',
- f, oct(perm))
+ f, "%o" % perm)
# ...but executed ones should
elif interpreter or mode_is_exec or script_regex.search(f):
if interpreter:
if not mode_is_exec and not is_doc:
printError(pkg, 'non-executable-script', f,
- oct(perm), interpreter)
+ "%o" % perm, interpreter)
if b'\r' in chunk:
printError(
pkg, 'wrong-script-end-of-line-encoding', f)
# normal dir check
elif stat.S_ISDIR(mode):
if mode & int("1002", 8) == 2: # world writable w/o sticky bit
- printError(pkg, 'world-writable', f, oct(perm))
+ printError(pkg, 'world-writable', f, "%o" % perm)
if perm != int("755", 8):
- printError(pkg, 'non-standard-dir-perm', f, oct(perm))
+ printError(pkg, 'non-standard-dir-perm', f, "%o" % perm)
if pkg.name not in filesys_packages and f in STANDARD_DIRS:
printError(pkg, 'standard-dir-owned-by-package', f)
if hidden_file_regex.search(f):
if stat.S_IWGRP & mode or stat.S_IWOTH & mode:
printError(pkg, 'non-owner-writeable-only-crontab-file', f)
- if log_file and not logrotate_file:
- printWarning(pkg, 'log-files-without-logrotate', log_file)
+ if len(log_files) and not logrotate_file:
+ printWarning(pkg, 'log-files-without-logrotate', sorted(log_files))
if lib_package and lib_file and non_lib_file:
printError(pkg, 'outside-libdir-files', non_lib_file)
package. If you want to include source code in your package, be sure to
create a development package.''',
+'non-devel-file-in-devel-package',
+'''A non-development file is located in a devel package.''',
+
'non-standard-dir-perm',
'''A standard directory should have permission set to 0755. If you get this
message, it means that you have wrong directory permissions in some dirs
for packages to install files in this directory.''' % i)
-
# FilesCheck.py ends here
# Local variables:
# Purpose : filter the output of rpmlint to allow exceptions.
#############################################################################
+from __future__ import print_function
import locale
import sys
import textwrap
def __print(s):
print(s)
else:
+ __stdout = sys.stdout
+ if not __stdout.encoding: # Python < 3 only?
+ import codecs
+ if hasattr(__stdout, "buffer"):
+ __stdout = __stdout.buffer
+ __stdout = codecs.getwriter(
+ locale.getpreferredencoding())(__stdout, "replace")
+
def __print(s):
- if isinstance(s, unicode):
- s = s.encode(locale.getpreferredencoding(), "replace")
- print(s)
+ print(s, file=__stdout)
def printInfo(pkg, reason, *details):
Testing.addOutput(s)
else:
if _rawout:
- print >>_rawout, s.encode(locale.getpreferredencoding(), "replace")
+ print(s.encode(locale.getpreferredencoding(), "replace"),
+ file=_rawout)
if not Config.isFiltered(s):
printed_messages[msgtype] += 1
_badness_score += badness
You need the following utilities to run rpmlint:
- o python 2.x (x >= 6)
+ o python 2.6 or newer
o rpm 4.4.2.2 or newer and its python bindings
o libmagic and its python bindings (optional)
o readelf
o gzip, bzip2 and xz
o groff and gtbl (optional)
o enchant and its python bindings (optional)
- o appdata-validate (optional)
+ o appstream-util, part of appstream-glib (optional)
and the following programs to install it:
- o python 2.x (x >= 6)
+ o python 2.6 or newer
o rpm python bindings 4.4 or newer
o sed
o pytest (for running the test suite)
printError(pkg, 'init-script-name-with-dot', fname)
# check chkconfig call in %post and %preun
- postin = Pkg.b2s(pkg[rpm.RPMTAG_POSTIN]) or \
+ postin = pkg[rpm.RPMTAG_POSTIN] or \
pkg.scriptprog(rpm.RPMTAG_POSTINPROG)
if not postin:
printError(pkg, 'init-script-without-chkconfig-postin', fname)
elif not chkconfig_regex.search(postin):
printError(pkg, 'postin-without-chkconfig', fname)
- preun = Pkg.b2s(pkg[rpm.RPMTAG_PREUN]) or \
+ preun = pkg[rpm.RPMTAG_PREUN] or \
pkg.scriptprog(rpm.RPMTAG_PREUNPROG)
if not preun:
printError(pkg, 'init-script-without-chkconfig-preun', fname)
# check common error in file content
content = None
try:
- content = Pkg.readlines(pkgfile.path)
+ content = [x for x in Pkg.readlines(pkgfile.path)]
except Exception:
e = sys.exc_info()[1]
printWarning(pkg, 'read-error', e)
GENERATED = ChangeLog __version__.py
PACKAGE = rpmlint
-PYTHON = python
+PYTHON = /usr/bin/python
# update this variable to create a new release
-VERSION := 1.6
+VERSION := 1.7
# for the [A-Z]* part
LC_ALL:=C
export LC_ALL
all: __version__.py __isocodes__.py
- if [ "x${COMPILE_PYC}" = "x1" ] ; then \
- $(PYTHON) -m py_compile [A-Z]*.py __*__.py ; \
- fi
- $(PYTHON) -O -m py_compile [A-Z]*.py __*__.py
clean:
- rm -f *~ *.pyc *.pyo $(GENERATED)
+ rm -rf *~ *.py[co] */*.py[co] __pycache__ */__pycache__ $(GENERATED)
install: all
mkdir -p $(DESTDIR)$(LIBDIR) $(DESTDIR)$(BINDIR) $(DESTDIR)$(ETCDIR)/$(PACKAGE) $(DESTDIR)$(MANDIR)/man1
- -cp -p *.pyc $(DESTDIR)$(LIBDIR)
- cp -p *.py *.pyo $(DESTDIR)$(LIBDIR)
- cp -p rpmlint rpmdiff $(DESTDIR)$(BINDIR)
+ cp -p *.py $(DESTDIR)$(LIBDIR)
+ if [ "x${COMPILE_PYC}" = "x1" ] ; then \
+ $(PYTHON) -m py_compile \
+ $(DESTDIR)$(LIBDIR)/[A-Z]*.py \
+ $(DESTDIR)$(LIBDIR)/__*__.py ; \
+ fi
+ $(PYTHON) -O -m py_compile \
+ $(DESTDIR)$(LIBDIR)/[A-Z]*.py \
+ $(DESTDIR)$(LIBDIR)/__*__.py ; \
+ for file in rpmlint rpmdiff ; do \
+ sed -e "s,#!/usr/bin/python ,#!$(PYTHON) ," $$file > $(DESTDIR)$(BINDIR)/$$file ; \
+ chmod +x $(DESTDIR)$(BINDIR)/$$file ; \
+ done
cp -p config $(DESTDIR)$(ETCDIR)/$(PACKAGE)
compdir=`pkg-config --variable=completionsdir bash-completion 2>/dev/null` ; \
if [ "x$$compdir" = "x" ] ; then \
else \
mkdir -p $(DESTDIR)$$compdir ; \
cp -p rpmlint.bash-completion $(DESTDIR)$$compdir/rpmlint ; \
- ln -s rpmlint $(DESTDIR)$$compdir/rpmdiff ; \
+ ln -sf rpmlint $(DESTDIR)$$compdir/rpmdiff ; \
fi
cp -p rpmdiff.1 rpmlint.1 $(DESTDIR)$(MANDIR)/man1
printError(pkg, 'menu-in-wrong-dir', fname)
if menus:
- postin = Pkg.b2s(pkg[rpm.RPMTAG_POSTIN]) or \
+ postin = pkg[rpm.RPMTAG_POSTIN] or \
pkg.scriptprog(rpm.RPMTAG_POSTINPROG)
if not postin:
printError(pkg, 'menu-without-postin')
elif not update_menus_regex.search(postin):
printError(pkg, 'postin-without-update-menus')
- postun = Pkg.b2s(pkg[rpm.RPMTAG_POSTUN]) or \
+ postun = pkg[rpm.RPMTAG_POSTUN] or \
pkg.scriptprog(rpm.RPMTAG_POSTUNPROG)
if not postun:
printError(pkg, 'menu-without-postun')
import subprocess
import sys
import tempfile
-import unicodedata
try:
from urlparse import urljoin
except:
# Blows up with Python < 3 without the exec() hack
exec('def warn(s): print (s, file=sys.stderr)')
long = int
+ unicode = str
def b2s(b):
if b is None:
rpm.RPMSENSE_SCRIPT_PREUN | \
rpm.RPMSENSE_SCRIPT_POSTUN
+SCRIPT_TAGS = [
+ (rpm.RPMTAG_PREIN, rpm.RPMTAG_PREINPROG, '%pre'),
+ (rpm.RPMTAG_POSTIN, rpm.RPMTAG_POSTINPROG, '%post'),
+ (rpm.RPMTAG_PREUN, rpm.RPMTAG_PREUNPROG, '%preun'),
+ (rpm.RPMTAG_POSTUN, rpm.RPMTAG_POSTUNPROG, '%postun'),
+ (rpm.RPMTAG_TRIGGERSCRIPTS, rpm.RPMTAG_TRIGGERSCRIPTPROG, '%trigger'),
+ (rpm.RPMTAG_PRETRANS, rpm.RPMTAG_PRETRANSPROG, '%pretrans'),
+ (rpm.RPMTAG_POSTTRANS, rpm.RPMTAG_POSTTRANSPROG, '%posttrans'),
+ (rpm.RPMTAG_VERIFYSCRIPT, rpm.RPMTAG_VERIFYSCRIPTPROG, '%verifyscript'),
+ ]
+
var_regex = re.compile('^(.*)\${?(\w+)}?(.*)$')
return val
-def getstatusoutput(cmd, stdoutonly=False, shell=False):
+def getstatusoutput(cmd, stdoutonly=False, shell=False, raw=False):
'''A version of commands.getstatusoutput() which can take cmd as a
sequence, thus making it potentially more secure.'''
if stdoutonly:
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, close_fds=True)
proc.stdin.close()
- text = b2s(proc.stdout.read())
+ with proc.stdout:
+ text = proc.stdout.read()
sts = proc.wait()
+ if not raw:
+ text = b2s(text)
+ if text.endswith('\n'):
+ text = text[:-1]
if sts is None:
sts = 0
- if text.endswith('\n'):
- text = text[:-1]
return sts, text
bz2_regex = re.compile('\.t?bz2?$')
def is_utf8(fname):
- (sts, text) = getstatusoutput(catcmd(fname).split() + [fname])
- return not sts and is_utf8_str(text)
-
-REPLACEMENT_CHAR = unicodedata.lookup('REPLACEMENT CHARACTER')
+ (sts, output) = getstatusoutput(catcmd(fname).split() + [fname], raw=True)
+ return not sts and is_utf8_bytestr(output)
-def is_utf8_str(s):
- if hasattr(s, 'decode'):
- # byte string
- try:
- s.decode('UTF-8')
- except:
- return False
- return True
- # unicode string
- return REPLACEMENT_CHAR not in s
+def is_utf8_bytestr(s):
+ try:
+ s.decode('UTF-8')
+ except:
+ return False
+ return True
-# TODO: PY3
-def to_utf8(string):
+def to_unicode(string):
if string is None:
- return ''
+ return unicode('')
elif isinstance(string, unicode):
return string
- try:
- x = unicode(string, 'ascii')
- return string
- except UnicodeError:
- encodings = ['utf-8', 'iso-8859-1', 'iso-8859-15', 'iso-8859-2']
- for enc in encodings:
- try:
- x = unicode(string, enc)
- except UnicodeError:
- pass
- else:
- if x.encode(enc) == string:
- return x.encode('utf-8')
- newstring = ''
- for char in string:
- if ord(char) > 127:
- newstring = newstring + '?'
+ for enc in ('utf-8', 'iso-8859-1', 'iso-8859-15', 'iso-8859-2'):
+ try:
+ x = unicode(string, enc)
+ except UnicodeError:
+ pass
else:
- newstring = newstring + char
- return newstring
+ if x.encode(enc) == string:
+ return x
+ return unicode(string, "ascii", errors="replace")
def readlines(path):
- fobj = open(path, 'rb')
- try:
+ with open(path, 'rb') as fobj:
for line in fobj:
yield b2s(line)
- finally:
- fobj.close()
def mktemp():
the rpm package (if installed) if no filename is given"""
groups = []
if not filename:
- try:
- p = InstalledPkg('rpm')
- except:
- pass
- else:
+ with InstalledPkg("rpm") as p:
groupsfiles = [x for x in p.files() if x.endswith('/GROUPS')]
if groupsfiles:
filename = groupsfiles[0]
if filename and os.path.exists(filename):
- fobj = open(filename)
- try:
+ with open(filename) as fobj:
groups = fobj.read().strip().splitlines()
- finally:
- fobj.close()
if 'Development/Debug' not in groups:
groups.append('Development/Debug')
if 'Unspecified' not in groups: # auto-added by rpm >= 4.6.0
# classes representing package
-class Pkg:
+class AbstractPkg:
+
+ def cleanup(self):
+ pass
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.cleanup()
+
+
+class Pkg(AbstractPkg):
_magic_from_compressed_re = re.compile('\([^)]+\s+compressed\s+data\\b')
os.close(fd)
self.is_source = not self.header[rpm.RPMTAG_SOURCERPM]
- self.name = b2s(self.header[rpm.RPMTAG_NAME])
+ self.name = self[rpm.RPMTAG_NAME]
if self.isNoSource():
self.arch = 'nosrc'
elif self.isSource():
if val == []:
return None
else:
- if key in (rpm.RPMTAG_VERSION, rpm.RPMTAG_RELEASE, rpm.RPMTAG_ARCH,
- rpm.RPMTAG_GROUP, rpm.RPMTAG_BUILDHOST,
- rpm.RPMTAG_LICENSE):
+ # Note that text tags we want to try decoding for real in TagsCheck
+ # such as summary, description and changelog are not here.
+ if key in (rpm.RPMTAG_NAME, rpm.RPMTAG_VERSION, rpm.RPMTAG_RELEASE,
+ rpm.RPMTAG_ARCH, rpm.RPMTAG_GROUP, rpm.RPMTAG_BUILDHOST,
+ rpm.RPMTAG_LICENSE, rpm.RPMTAG_HEADERI18NTABLE,
+ rpm.RPMTAG_PACKAGER, rpm.RPMTAG_SOURCERPM) \
+ or key in (x[0] for x in SCRIPT_TAGS) \
+ or key in (x[1] for x in SCRIPT_TAGS):
val = b2s(val)
return val
# LANGUAGE trumps other env vars per GNU gettext docs, see also #166
orig = os.environ.get('LANGUAGE')
os.environ['LANGUAGE'] = lang
- ret = b2s(self[tag])
+ ret = self[tag]
if orig is not None:
os.environ['LANGUAGE'] = orig
return ret
self.dirName() or '/', pkgfile.name.lstrip('/')))
pkgfile.flags = flags[idx]
pkgfile.mode = modes[idx]
- pkgfile.user = users[idx]
- pkgfile.group = groups[idx]
+ pkgfile.user = b2s(users[idx])
+ pkgfile.group = b2s(groups[idx])
pkgfile.linkto = links[idx] and safe_normpath(links[idx])
pkgfile.size = sizes[idx]
pkgfile.md5 = md5s[idx]
pkgfile.inode = inodes[idx]
pkgfile.requires = parse_deps(requires[idx])
pkgfile.provides = parse_deps(provides[idx])
- pkgfile.lang = langs[idx]
+ pkgfile.lang = b2s(langs[idx])
pkgfile.magic = magics[idx]
if not pkgfile.magic and _magic:
pkgfile.magic = _magic.file(pkgfile.path)
self._gatherDepInfo()
return self._provides
+ def recommends(self):
+ """Get package Recommends as list of
+ (name, flags, (epoch, version, release)) tuples."""
+ self._gatherDepInfo()
+ return self._recommends
+
+ def suggests(self):
+ """Get package Suggests as list of
+ (name, flags, (epoch, version, release)) tuples."""
+ self._gatherDepInfo()
+ return self._suggests
+
+ def enhances(self):
+ """Get package Enhances as list of
+ (name, flags, (epoch, version, release)) tuples."""
+ self._gatherDepInfo()
+ return self._enhances
+
+ def supplements(self):
+ """Get package Supplements as list of
+ (name, flags, (epoch, version, release)) tuples."""
+ self._gatherDepInfo()
+ return self._supplements
+
# internal function to gather dependency info used by the above ones
def _gather_aux(self, header, list, nametag, flagstag, versiontag,
prereq=None):
self._provides = []
self._conflicts = []
self._obsoletes = []
+ self._recommends = []
+ self._suggests = []
+ self._enhances = []
+ self._supplements = []
self._gather_aux(self.header, self._requires,
rpm.RPMTAG_REQUIRENAME,
rpm.RPMTAG_OBSOLETENAME,
rpm.RPMTAG_OBSOLETEFLAGS,
rpm.RPMTAG_OBSOLETEVERSION)
+ if hasattr(rpm, "RPMTAG_RECOMMENDNAME"): # rpm >= 4.12
+ self._gather_aux(self.header, self._recommends,
+ rpm.RPMTAG_RECOMMENDNAME,
+ rpm.RPMTAG_RECOMMENDFLAGS,
+ rpm.RPMTAG_RECOMMENDVERSION)
+ if hasattr(rpm, "RPMTAG_SUGGESTNAME"): # rpm >= 4.12
+ self._gather_aux(self.header, self._suggests,
+ rpm.RPMTAG_SUGGESTNAME,
+ rpm.RPMTAG_SUGGESTFLAGS,
+ rpm.RPMTAG_SUGGESTVERSION)
+ if hasattr(rpm, "RPMTAG_ENHANCENAME"): # rpm >= 4.12
+ self._gather_aux(self.header, self._enhances,
+ rpm.RPMTAG_ENHANCENAME,
+ rpm.RPMTAG_ENHANCEFLAGS,
+ rpm.RPMTAG_ENHANCEVERSION)
+ if hasattr(rpm, "RPMTAG_SUPPLEMENTNAME"): # rpm >= 4.12
+ self._gather_aux(self.header, self._supplements,
+ rpm.RPMTAG_SUPPLEMENTNAME,
+ rpm.RPMTAG_SUPPLEMENTFLAGS,
+ rpm.RPMTAG_SUPPLEMENTVERSION)
def scriptprog(self, which):
"""Get the specified script interpreter as a string.
interpreter arguments, if any."""
prog = self[which]
if prog is None:
- prog = b''
+ prog = ""
elif isinstance(prog, (list, tuple)):
# http://rpm.org/ticket/847#comment:2
- prog = b' '.join(prog)
- return b2s(prog)
+ prog = "".join(prog)
+ return prog
def getInstalledPkgs(name):
if not mi:
raise KeyError(name)
try:
- hdr = mi.next()
+ hdr = next(mi)
except StopIteration:
raise KeyError(name)
# Class to provide an API to a "fake" package, eg. for specfile-only checks
-class FakePkg:
+class FakePkg(AbstractPkg):
def __init__(self, name):
self.name = name
self.arch = None
self.current_linenum = None
- def cleanup(self):
- pass
-
# Class for files in packages
class PkgFile(object):
if __name__ == '__main__':
for p in sys.argv[1:]:
- pkg = Pkg(sys.argv[1], tempfile.gettempdir())
- print ('Requires: %s' % pkg.requires())
- print ('Prereq: %s' % pkg.prereq())
- print ('Conflicts: %s' % pkg.conflicts())
- print ('Provides: %s' % pkg.provides())
- print ('Obsoletes: %s' % pkg.obsoletes())
- pkg.cleanup()
+ with Pkg(p, tempfile.gettempdir()) as pkg:
+ print('Requires: %s' % pkg.requires())
+ print('Prereq: %s' % pkg.prereq())
+ print('Conflicts: %s' % pkg.conflicts())
+ print('Provides: %s' % pkg.provides())
+ print('Obsoletes: %s' % pkg.obsoletes())
# Pkg.py ends here
# pychecker fix
del p
-script_tags = [
- (rpm.RPMTAG_PREIN, rpm.RPMTAG_PREINPROG, '%pre'),
- (rpm.RPMTAG_POSTIN, rpm.RPMTAG_POSTINPROG, '%post'),
- (rpm.RPMTAG_PREUN, rpm.RPMTAG_PREUNPROG, '%preun'),
- (rpm.RPMTAG_POSTUN, rpm.RPMTAG_POSTUNPROG, '%postun'),
- (rpm.RPMTAG_TRIGGERSCRIPTS, rpm.RPMTAG_TRIGGERSCRIPTPROG, '%trigger'),
- (rpm.RPMTAG_PRETRANS, rpm.RPMTAG_PRETRANSPROG, '%pretrans'),
- (rpm.RPMTAG_POSTTRANS, rpm.RPMTAG_POSTTRANSPROG, '%posttrans'),
- (rpm.RPMTAG_VERIFYSCRIPT, rpm.RPMTAG_VERIFYSCRIPTPROG, '%verifyscript'),
- ]
-
def incorrect_shell_script(prog, shellscript):
return check_syntax_script(prog, '-n', shellscript)
prereq = [x[0] for x in pkg.prereq()]
files = pkg.files()
- for tag in script_tags:
+ for tag in Pkg.SCRIPT_TAGS:
script = pkg[tag[0]]
if not isinstance(script, list):
if ghost_files:
postin = pkg[rpm.RPMTAG_POSTIN]
prein = pkg[rpm.RPMTAG_PREIN]
- if not postin and not prein:
- printWarning(pkg, 'ghost-files-without-postin')
- else:
- for f in ghost_files:
- if (not postin or f not in postin) and \
- (not prein or f not in prein) and \
- f not in pkg.missingOkFiles():
- printWarning(pkg,
- 'postin-without-ghost-file-creation', f)
+ for f in ghost_files:
+ if f in pkg.missingOkFiles():
+ continue
+ if not postin and not prein:
+ printWarning(pkg, 'ghost-files-without-postin')
+ if (not postin or f not in postin) and \
+ (not prein or f not in prein):
+ printWarning(pkg,
+ 'postin-without-ghost-file-creation', f)
def check_aux(self, pkg, files, prog, script, tag, prereq):
if script:
self._spec_file = pkgfile.path
self.check_spec(pkg, self._spec_file)
- def check_spec(self, pkg, spec_file, spec_lines=[]):
+ def check_spec(self, pkg, spec_file):
'''SCL spec file checks'''
spec = '\n'.join(Pkg.readlines(spec_file))
if global_scl_definition.search(spec):
compress_ext, fname)
perm = pkgfile.mode & int("7777", 8)
if perm not in valid_src_perms:
- printWarning(pkg, 'strange-permission', fname, oct(perm))
+ printWarning(pkg, 'strange-permission', fname, "%o" % perm)
check = SourceCheck()
#############################################################################
import re
+import sys
import unicodedata
try:
from urlparse import urlparse
compop_regex = re.compile('[<>=]')
+setup_regex = re.compile(r'%setup\b') # intentionally no whitespace before!
setup_q_regex = re.compile(' -[A-Za-z]*q')
setup_t_regex = re.compile(' -[A-Za-z]*T')
setup_ab_regex = re.compile(' -[A-Za-z]*[ab]')
# check content of spec file
self.check_spec(pkg, self._spec_file)
- def check_spec(self, pkg, spec_file, spec_lines=[]):
+ def check_spec(self, pkg, spec_file):
self._spec_file = spec_file
spec_only = isinstance(pkg, Pkg.FakePkg)
- if not spec_lines:
- spec_lines = Pkg.readlines(spec_file)
patches = {}
applied_patches = []
applied_patches_ifarch = []
nbsp = chr(0xA0)
if is_utf8:
nbsp = UNICODE_NBSP
+ do_unicode = is_utf8 and sys.version_info[0] <= 2
- for line in spec_lines:
+ for line in Pkg.readlines(spec_file):
pkg.current_linenum += 1
- if is_utf8:
+ if do_unicode:
line = unicode(line, "utf-8", "replace")
char = line.find(nbsp)
if if_regex.search(line):
if_depth = if_depth + 1
- if line.startswith('%setup'):
+ if setup_regex.match(line):
if not setup_q_regex.search(line):
# Don't warn if there's a -T without -a or -b
if setup_t_regex.search(line):
if checker:
# squeeze whitespace to ease leading context check
checker.set_text(re.sub(r'\s+', ' ', str))
- uppername = pkg.name.upper()
if use_utf8:
- uppername = Pkg.to_utf8(uppername).decode('utf-8')
+ uppername = Pkg.to_unicode(pkg.header[rpm.RPMTAG_NAME]).upper()
+ else:
+ uppername = pkg.name.upper()
upperparts = uppername.split('-')
if lang.startswith('en'):
ups = [x + "'S" for x in upperparts]
def _unexpanded_macros(self, pkg, tagname, value, is_url=False):
if not value:
return
- # str(value) because value might be a list
- for match in AbstractCheck.macro_regex.findall(str(value)):
- # Do not warn about %XX URL escapes
- if is_url and re.match('^%[0-9A-F][0-9A-F]$', match, re.I):
- continue
- printWarning(pkg, 'unexpanded-macro', tagname, match)
+ if not isinstance(value, (list, tuple)):
+ value = [value]
+ for val in value:
+ for match in AbstractCheck.macro_regex.findall(val):
+ # Do not warn about %XX URL escapes
+ if is_url and re.match('^%[0-9A-F][0-9A-F]$', match, re.I):
+ continue
+ printWarning(pkg, 'unexpanded-macro', tagname, match)
def check(self, pkg):
- packager = Pkg.b2s(pkg[rpm.RPMTAG_PACKAGER])
+ packager = pkg[rpm.RPMTAG_PACKAGER]
if packager:
self._unexpanded_macros(pkg, 'Packager', packager)
if Config.getOption('Packager') and \
ignored_words.update((x[0] for x in pkg.obsoletes()))
langs = pkg[rpm.RPMTAG_HEADERI18NTABLE]
- if langs:
- langs = [Pkg.b2s(x) for x in langs]
- summary = Pkg.b2s(pkg[rpm.RPMTAG_SUMMARY])
+ summary = pkg[rpm.RPMTAG_SUMMARY]
if summary:
if not langs:
- self._unexpanded_macros(pkg, 'Summary', summary)
+ self._unexpanded_macros(pkg, 'Summary', Pkg.b2s(summary))
else:
for lang in langs:
self.check_summary(pkg, lang, ignored_words)
else:
printError(pkg, 'no-summary-tag')
- description = Pkg.b2s(pkg[rpm.RPMTAG_DESCRIPTION])
+ description = pkg[rpm.RPMTAG_DESCRIPTION]
if description:
if not langs:
- self._unexpanded_macros(pkg, '%description', description)
+ self._unexpanded_macros(pkg, '%description',
+ Pkg.b2s(description))
else:
for lang in langs:
self.check_description(pkg, lang, ignored_words)
if not changelog:
printError(pkg, 'no-changelogname-tag')
else:
- changelog = [Pkg.b2s(x) for x in changelog]
- clt = [Pkg.b2s(x) for x in pkg[rpm.RPMTAG_CHANGELOGTEXT]]
+ clt = pkg[rpm.RPMTAG_CHANGELOGTEXT]
if use_version_in_changelog:
- ret = changelog_version_regex.search(changelog[0])
+ ret = changelog_version_regex.search(Pkg.b2s(changelog[0]))
if not ret and clt:
# we also allow the version specified as the first
# thing on the first line of the text
- ret = changelog_text_version_regex.search(clt[0])
+ ret = changelog_text_version_regex.search(Pkg.b2s(clt[0]))
if not ret:
printWarning(pkg, 'no-version-in-last-changelog')
elif version and release:
printWarning(pkg, 'incoherent-version-in-changelog',
ret.group(1), expected)
- if clt:
- changelog = changelog + clt
- if use_utf8 and not Pkg.is_utf8_str(' '.join(changelog)):
- printError(pkg, 'tag-not-utf8', '%changelog')
+ if use_utf8:
+ if clt:
+ changelog = changelog + clt
+ for s in changelog:
+ if not Pkg.is_utf8_bytestr(s):
+ printError(pkg, 'tag-not-utf8', '%changelog')
+ break
clt = pkg[rpm.RPMTAG_CHANGELOGTIME][0]
if clt:
def check_description(self, pkg, lang, ignored_words):
description = pkg.langtag(rpm.RPMTAG_DESCRIPTION, lang)
- self._unexpanded_macros(pkg, '%%description -l %s' % lang, description)
- utf8desc = description
if use_utf8:
- utf8desc = Pkg.to_utf8(description).decode('utf-8')
- spell_check(pkg, utf8desc, '%%description -l %s', lang, ignored_words)
- for l in utf8desc.splitlines():
+ if not Pkg.is_utf8_bytestr(description):
+ printError(pkg, 'tag-not-utf8', '%description', lang)
+ description = Pkg.to_unicode(description)
+ else:
+ description = Pkg.b2s(description)
+ self._unexpanded_macros(pkg, '%%description -l %s' % lang, description)
+ spell_check(pkg, description, '%%description -l %s', lang,
+ ignored_words)
+ for l in description.splitlines():
if len(l) > max_line_len:
printError(pkg, 'description-line-too-long', lang, l)
res = forbidden_words_regex.search(l)
res = tag_regex.search(l)
if res:
printWarning(pkg, 'tag-in-description', lang, res.group(1))
- if use_utf8 and not Pkg.is_utf8_str(description):
- printError(pkg, 'tag-not-utf8', '%description', lang)
def check_summary(self, pkg, lang, ignored_words):
summary = pkg.langtag(rpm.RPMTAG_SUMMARY, lang)
- self._unexpanded_macros(pkg, 'Summary(%s)' % lang, summary)
- utf8summary = summary
if use_utf8:
- utf8summary = Pkg.to_utf8(summary).decode('utf-8')
- spell_check(pkg, utf8summary, 'Summary(%s)', lang, ignored_words)
+ if not Pkg.is_utf8_bytestr(summary):
+ printError(pkg, 'tag-not-utf8', 'Summary', lang)
+ summary = Pkg.to_unicode(summary)
+ else:
+ summary = Pkg.b2s(summary)
+ self._unexpanded_macros(pkg, 'Summary(%s)' % lang, summary)
+ spell_check(pkg, summary, 'Summary(%s)', lang, ignored_words)
if '\n' in summary:
printError(pkg, 'summary-on-multiple-lines', lang)
if summary[0] != summary[0].upper():
printWarning(pkg, 'summary-not-capitalized', lang, summary)
if summary[-1] == '.':
printWarning(pkg, 'summary-ended-with-dot', lang, summary)
- if len(utf8summary) > max_line_len:
+ if len(summary) > max_line_len:
printError(pkg, 'summary-too-long', lang, summary)
if leading_space_regex.search(summary):
printError(pkg, 'summary-has-leading-spaces', lang, summary)
if res:
printWarning(pkg, 'name-repeated-in-summary', lang,
res.group(1))
- if use_utf8 and not Pkg.is_utf8_str(summary):
- printError(pkg, 'tag-not-utf8', 'Summary', lang)
# Create an object to enable the auto registration of the test
from Filter import addDetails, printError, printWarning
import AbstractCheck
import Config
+import Pkg
zip_regex = re.compile('\.(zip|[ewj]ar)$')
# additional jar checks
if jar_regex.search(fname):
try:
- mf = z.read('META-INF/MANIFEST.MF')
+ mf = Pkg.b2s(z.read('META-INF/MANIFEST.MF'))
if classpath_regex.search(mf):
printWarning(pkg,
'class-path-in-manifest', fname)
# Type: string, default: 'mac'
#SetOption("ManWarningCategory", 'mac')
+# Command and arguments to validate appdata files.
+# Type: tuple of strings, default: see DEFAULT_APPDATA_CHECKER in AppDataCheck
+#setOption("AppDataChecker", ('appstream-util', 'validate-relax'))
+
# Output filters.
# ---------------
stdin = sys.stdin.readlines()
if not stdin:
continue
- pkg = Pkg.FakePkg(arg)
- runSpecChecks(pkg, arg)
+ with Pkg.FakePkg(arg) as pkg:
+ runSpecChecks(pkg, arg)
specfiles_checked += 1
continue
if stat.S_ISREG(st[stat.ST_MODE]):
if arg.endswith(".spec"):
# Short-circuit spec file checks
- pkg = Pkg.FakePkg(arg)
- runSpecChecks(pkg, arg)
+ with Pkg.FakePkg(arg) as pkg:
+ runSpecChecks(pkg, arg)
specfiles_checked += 1
elif "/" in arg or arg.endswith(".rpm") or \
arg.endswith(".spm"):
continue
for pkg in pkgs:
- runChecks(pkg)
+ with pkg:
+ runChecks(pkg)
packages_checked += 1
for dname in dirs:
try:
if fname.endswith('.rpm') or \
fname.endswith('.spm'):
- pkg = Pkg.Pkg(fname, extract_dir)
- runChecks(pkg)
+ with Pkg.Pkg(fname, extract_dir) as pkg:
+ runChecks(pkg)
packages_checked += 1
elif fname.endswith('.spec'):
- pkg = Pkg.FakePkg(fname)
- runSpecChecks(pkg, fname)
+ with Pkg.FakePkg(fname) as pkg:
+ runSpecChecks(pkg, fname)
specfiles_checked += 1
except KeyboardInterrupt:
def runChecks(pkg):
- try:
- if verbose:
- printInfo(pkg, 'checking')
-
- for name in Config.allChecks():
- check = AbstractCheck.AbstractCheck.known_checks.get(name)
- if check:
- check.verbose = verbose
- check.check(pkg)
- else:
- Pkg.warn('(none): W: unknown check %s, skipping' % name)
- finally:
- pkg.cleanup()
+ if verbose:
+ printInfo(pkg, 'checking')
+
+ for name in Config.allChecks():
+ check = AbstractCheck.AbstractCheck.known_checks.get(name)
+ if check:
+ check.verbose = verbose
+ check.check(pkg)
+ else:
+ Pkg.warn('(none): W: unknown check %s, skipping' % name)
def runSpecChecks(pkg, fname):
- try:
- if verbose:
- printInfo(pkg, 'checking')
-
- for name in Config.allChecks():
- check = AbstractCheck.AbstractCheck.known_checks.get(name)
- if check:
- check.verbose = verbose
- check.check_spec(pkg, fname)
- else:
- Pkg.warn('(none): W: unknown check %s, skipping' % name)
- finally:
- pkg.cleanup()
+ if verbose:
+ printInfo(pkg, 'checking')
+
+ for name in Config.allChecks():
+ check = AbstractCheck.AbstractCheck.known_checks.get(name)
+ if check:
+ check.verbose = verbose
+ check.check_spec(pkg, fname)
+ else:
+ Pkg.warn('(none): W: unknown check %s, skipping' % name)
#############################################################################
#
export PYTHONPATH=$(pwd)/tools:$(pwd)
export TESTPATH="$(pwd)/test/"
+: ${PYTHON:=python} ${PYTEST:=py.test}
echo
echo "Please ignore the possibly occurring output like this:"
echo
for i in $TESTPATH/test.*.py; do
- python $i
+ $PYTHON $i
RET=$?
if [ $RET -ne 0 ]; then
exit $RET
done
echo "Check that rpmlint executes with no unexpected errors"
-python ./rpmlint -C $(pwd) test/*/*.rpm test/spec/*.spec >/dev/null
+$PYTHON ./rpmlint -C $(pwd) test/*/*.rpm test/spec/*.spec >/dev/null
rc=$?
test $rc -eq 0 -o $rc -eq 64
# SCLCheck tests
-py.test -v
+$PYTEST -v