Skip to content

Commit

Permalink
Merge branch 'main' into opensuse
Browse files Browse the repository at this point in the history
  • Loading branch information
danigm committed Jul 4, 2023
2 parents dfca9f8 + d22202b commit 7a58af2
Show file tree
Hide file tree
Showing 25 changed files with 213 additions and 82 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ jobs:
desktop-file-utils
appstream-glib
myspell-en_US myspell-cs_CZ
myspell-fr_FR
python3-pyenchant
if: ${{ contains(matrix.container, 'opensuse') && matrix.build-type == 'normal' }}
- run: zypper -n install python3-flake8-comprehensions
Expand Down Expand Up @@ -98,6 +99,7 @@ jobs:
devscripts-checkbashisms
hunspell-en
hunspell-cs
hunspell-fr
python3-enchant
if: ${{ contains(matrix.container, 'fedora') && matrix.build-type == 'normal' }}
- run: rm -rf $(rpm --eval '%_dbpath')
Expand Down
4 changes: 4 additions & 0 deletions .packit/rpmlint.spec
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ BuildRequires: python3-rpm
BuildRequires: python3-tomli
BuildRequires: python3-tomli-w
BuildRequires: python3-zstandard
BuildRequires: python3-packaging
%else
BuildRequires: python3dist(setuptools)
# For tests
Expand All @@ -43,6 +44,7 @@ BuildRequires: python3dist(rpm)
BuildRequires: (python3dist(tomli) if python3 < 3.11)
BuildRequires: python3dist(tomli-w)
BuildRequires: python3dist(zstandard)
BuildRequires: python3dist(packaging)
%endif

# Rest of the test dependencies
Expand All @@ -54,9 +56,11 @@ BuildRequires: /usr/bin/desktop-file-validate
%if 0%{?suse_version}
BuildRequires: myspell-en_US
BuildRequires: myspell-cs_CZ
BuildRequires: myspell-fr_FR
%else
BuildRequires: hunspell-en
BuildRequires: hunspell-cs
BuildRequires: hunspell-fr
%endif

%if 0%{?fedora} || 0%{?rhel} >= 8
Expand Down
7 changes: 7 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- id: rpmlint
name: Check RPM package errors
description: rpmlint is a tool for checking common errors in RPM packages.
entry: rpmlint
language: python
files: \.rpm
types: [file]
2 changes: 2 additions & 0 deletions configs/openSUSE/users-groups.toml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ StandardGroups = [
'tomcat4',
'tor',
'tox',
'transmission',
'trove',
'trusted',
'tryton',
Expand Down Expand Up @@ -410,6 +411,7 @@ StandardUsers = [
'tomcat4',
'tor',
'toxcmd',
'transmission',
'trove',
'tryton',
'tss',
Expand Down
2 changes: 1 addition & 1 deletion rpmlint/checks/DocCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _check_doc_file_dependencies(self, pkg):
core_reqs = {} # dependencies of non-doc files
doc_reqs = {} # dependencies of doc files

for dep in pkg.header.dsFromHeader():
for dep in rpm.ds(pkg.header, 'requires'):
# skip deps which were found by find-requires
if dep.Flags() & rpm.RPMSENSE_FIND_REQUIRES != 0:
continue
Expand Down
109 changes: 55 additions & 54 deletions rpmlint/checks/PythonCheck.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from pathlib import Path
import platform
import re

from packaging.requirements import InvalidRequirement, Requirement
from rpmlint.checks.AbstractCheck import AbstractFilesCheck

# Warning messages
Expand Down Expand Up @@ -89,9 +91,13 @@ def check_requires_txt(self, pkg, filename):
if requirement.startswith('['):
break

# Ignore version limitations for now
requirement, *_ = re.split('[<>=!~]', requirement)
requirements.append(requirement)
# Ignore broken requirements
try:
req = Requirement(requirement)
except InvalidRequirement:
continue

requirements.append(req)

self._check_requirements(pkg, requirements)

Expand All @@ -112,16 +118,14 @@ def check_requires_metadata(self, pkg, filename):
continue

requirement = match.group('req')
# Ignore extra requires
if 'extra ==' in requirement:
continue
# Ignore windows platform
if 'platform_system == "Windows"' in requirement:

# Ignore broken requirements
try:
req = Requirement(requirement)
except InvalidRequirement:
continue

# Ignore version limitations for now
requirement, *_ = re.split('[ <>=!~]', requirement)
requirements.append(requirement)
requirements.append(req)

self._check_requirements(pkg, requirements)

Expand All @@ -131,29 +135,52 @@ def _check_requirements(self, pkg, requirements):
declared requires.
"""

env = {
'python_version': '.'.join(platform.python_version_tuple()[:2]),
'os_name': 'posix',
'platform_system': 'Linux',
}

# Look for python version
for req in pkg.requires:
if req.name == 'python(abi)':
_, pyv, _ = req.version
env['python_version'] = pyv
break

# Check for missing requirements
for req in requirements:
self._check_require(pkg, req.strip())
if req.marker:
# Ignore extra requires
if 'extra' in str(req.marker):
continue
# Ignore not env requirements
if not req.marker.evaluate(environment=env):
continue

self._check_require(pkg, req)

# Check for python requirement not needed
self._check_leftover_requirements(pkg, requirements)

def _check_require(self, pkg, module_name):
def _check_require(self, pkg, requirement):
"""
Look for the module_name in the package requirements, looking
for common python rpm package names like python-foo,
python3-foo, etc.
"""

if not module_name:
return True
names = self._module_names(requirement.name, extras=requirement.extras)

names = self._module_names(module_name)
# Add pythonX-foo variants
names += [f'python\\d*-{re.escape(i)}' for i in names]
regex = '|'.join(names)
# Support complex requirements like
# (python310-jupyter-server >= 1.15 with python310-jupyter-server < 3)
version_req = r'\s*(==|<|<=|>|>=)\s*[\w.]+\s*'
richop_req = r'\s+(and|or|if|unless|else|with|without)\s+.*'
try:
regex = re.compile(f'^({regex})$', re.IGNORECASE)
regex = re.compile(rf'^\(?({regex})({version_req})?({richop_req})?\)?\s*$', re.IGNORECASE)
except re.error:
# Bad regular expression, it could be a name with weird
# characters
Expand All @@ -163,7 +190,7 @@ def _check_require(self, pkg, module_name):
if regex.match(req):
return True

self.output.add_info('W', pkg, 'python-missing-require', module_name)
self.output.add_info('W', pkg, 'python-missing-require', requirement.name)
return False

def _check_leftover_requirements(self, pkg, requirements):
Expand All @@ -175,8 +202,7 @@ def _check_leftover_requirements(self, pkg, requirements):
pythonpac = re.compile(r'^python\d*-(?P<name>.+)$')
reqs = set()
for i in requirements:
names = self._normalize_modname(i)
for n in names:
for n in self._module_names(i.name, extras=i.extras):
reqs.add(n.lower())

for req in pkg.req_names:
Expand All @@ -195,48 +221,23 @@ def _check_leftover_requirements(self, pkg, requirements):
if not (names & reqs):
self.output.add_info('W', pkg, 'python-leftover-require', req)

def _normalize_modname(self, name):
"""
Convert a python module requirement spec to a common python
package possible names, replacing extra declaration with "-".
Examples:
* module[extra] -> [module-extra]
* module[e1,e2] -> [module-e1, module-e2]
"""
name = name.strip()
names = []

# Handle names with extras like jsonschema[format-nongpl]
extras = re.match(r'^(?P<module_name>.*)\[(?P<extra>.+)]$',
name)
if extras:
name = extras.group('module_name')
extras = [i.strip() for i in extras.group('extra').split(',')]
names = [f'{name}-{extra}' for extra in extras]
else:
names = [name]

return names

def _module_names(self, module_name):
def _module_names(self, module_name, extras=None):
"""
Return a list with possible variants of the module name,
replacing "-", "_".
It also replaces extras declaration to dash like:
module[extra] -> module-extra
"""

module_names = self._normalize_modname(module_name)

# Name variants changing '-' with '_'
variants = []
for i in module_names:
variants.append(i.replace('-', '_'))
variants.append(i.replace('_', '-'))
variants.append(module_name.replace('-', '_'))
variants.append(module_name.replace('_', '-'))

# Look also for python-MOD-EXTRA
if extras:
for e in extras:
variants += self._module_names(f'{module_name}-{e}')

return [
*module_names,
module_name,
*variants,
]
17 changes: 15 additions & 2 deletions rpmlint/checks/SpecCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,8 @@ def _checkline_package(self, line):
self._checkline_package_obsoletes(line)
self._checkline_package_conflicts(line)

self._checkline_forbidden_controlchars(line)

def _checkline_changelog(self, line):
if self.current_section == 'changelog':
deptoken = Pkg.has_forbidden_controlchars(line)
Expand Down Expand Up @@ -747,8 +749,13 @@ def _checkline_macros_in_comments(self, line):
# Test if there are macros in comments
if hash_pos != -1 and \
(hash_pos == 0 or line[hash_pos - 1] in (' ', '\t')):
for match in self.macro_regex.findall(
line[hash_pos + 1:]):

comment = line[hash_pos + 1:]
# Ignore special comments like #!BuildIgnore
if comment and comment[0] == '!':
return

for match in self.macro_regex.findall(comment):
res = re.match('%+', match)
if len(res.group(0)) % 2:
self.output.add_info('W', self.pkg, 'macro-in-comment', match)
Expand All @@ -775,3 +782,9 @@ def _checkline_python_sitelib_glob(self, line):
if python_sitelib_glob_regex.match(line):
self.output.add_info('W', self.pkg, 'python-sitelib-glob-in-files',
line[:-1])

def _checkline_forbidden_controlchars(self, line):
"""Look for controlchar in any line"""
# https://github.com/rpm-software-management/rpmlint/issues/1067
if Pkg.has_forbidden_controlchars(line):
self.output.add_info('W', self.pkg, 'forbidden-controlchar-found')
5 changes: 0 additions & 5 deletions rpmlint/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@ def process_diff_args(argv):
When relative, files matching the pattern anywhere
are excluded but not directory contents.""")

# print help if there is no argument or less than the 2 mandatory ones
if len(argv) < 2:
parser.print_help()
sys.exit(0)

options = parser.parse_args(args=argv)

# convert options to dict
Expand Down
33 changes: 15 additions & 18 deletions rpmlint/rpmdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,17 @@ class Rpmdiff:
PRCO = ('REQUIRES', 'PROVIDES', 'CONFLICTS', 'OBSOLETES',
'RECOMMENDS', 'SUGGESTS', 'ENHANCES', 'SUPPLEMENTS')

# {fname : (size, mode, mtime, flags, dev, inode,
# nlink, state, vflags, user, group, digest)}
__FILEIDX = [['S', 0],
['M', 1],
['5', 11],
['D', 4],
['N', 6],
['L', 7],
['V', 8],
['U', 9],
['G', 10],
['F', 3],
['T', 2]]
__FILEIDX = [['S', 'size'],
['M', 'mode'],
['5', 'digest'],
['D', 'rdev'],
['N', 'nlink'],
['L', 'state'],
['V', 'vflags'],
['U', 'user'],
['G', 'group'],
['F', 'fflags'],
['T', 'mtime']]

DEPFORMAT = '%-12s%s %s %s %s'
FORMAT = '%-12s%s'
Expand Down Expand Up @@ -78,9 +76,8 @@ def __init__(self, old, new, ignore=None, exclude=None):
self.__comparePRCOs(old, new, tag)

# compare the files

old_files_dict = self.__fileIteratorToDict(old.fiFromHeader())
new_files_dict = self.__fileIteratorToDict(new.fiFromHeader())
old_files_dict = self.__fileIteratorToDict(rpm.files(old))
new_files_dict = self.__fileIteratorToDict(rpm.files(new))
files = list(set(chain(iter(old_files_dict), iter(new_files_dict))))
files.sort()

Expand All @@ -101,7 +98,7 @@ def __init__(self, old, new, ignore=None, exclude=None):
fmt = ''
for entry in FILEIDX:
if entry[1] is not None and \
old_file[entry[1]] != new_file[entry[1]]:
getattr(old_file, entry[1]) != getattr(new_file, entry[1]):
fmt += entry[0]
diff = True
else:
Expand Down Expand Up @@ -236,5 +233,5 @@ def __comparePRCOs(self, old, new, name):
def __fileIteratorToDict(self, fi):
result = {}
for filedata in fi:
result[filedata[0]] = filedata[1:]
result[filedata.name] = filedata
return result
4 changes: 4 additions & 0 deletions rpmlint/spellcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def spell_check(self, text, fmt, lang='en_US', pkgname='', ignored_words=None):
warned = set()
suggestions = {}

# C lang is 'en_US'
if lang == 'C':
lang = 'en_US'

# Initialize spelling dictionary if not already done
if lang not in self._enchant_checkers:
self._init_checker(lang)
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
'zstandard',
'importlib-metadata;python_version<"3.8"',
'pyenchant',
'python-magic'
'python-magic',
'packaging',
],
tests_require=[
'pytest',
Expand Down
1 change: 1 addition & 0 deletions test/Testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def _has_dictionary(language):

HAS_ENGLISH_DICTIONARY = _has_dictionary('en_US')
HAS_CZECH_DICTIONARY = _has_dictionary('cs_CZ')
HAS_FRENCH_DICTIONARY = _has_dictionary('fr')


def get_tested_path(*paths):
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added test/binary/spellingerrors-default-0-0.noarch.rpm
Binary file not shown.
Binary file added test/binary/spellingerrors-lang-0-0.noarch.rpm
Binary file not shown.
Binary file added test/binary/spellingerrors-lang2-0-0.noarch.rpm
Binary file not shown.
Binary file added test/binary/spellingerrors-lang3-0-0.noarch.rpm
Binary file not shown.
Loading

0 comments on commit 7a58af2

Please sign in to comment.