diff options
author | TizenOpenSource <tizenopensrc@samsung.com> | 2023-12-27 18:11:19 +0900 |
---|---|---|
committer | TizenOpenSource <tizenopensrc@samsung.com> | 2023-12-27 18:11:19 +0900 |
commit | 727a4bfe21f594703093859adb151f67f0403a13 (patch) | |
tree | 90f3c63cafddcdc010091ead32a9d5e7d09e4cd2 | |
parent | 95b3517edeace0e940b51d8ac5609c8f9326f6f4 (diff) | |
download | rpmlint-upstream.tar.gz rpmlint-upstream.tar.bz2 rpmlint-upstream.zip |
Imported Upstream version 2.5.0upstream/2.5.0upstream
464 files changed, 23889 insertions, 14700 deletions
diff --git a/.dockerignore b/.dockerignore index 34fcef1..bde6d5d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,4 @@ -*.pyo -*.pyc +**/__pycache__ +**/*.pyo +**/*.pyc +!test/pyc/*.pyc diff --git a/.editorconfig b/.editorconfig index 74e2655..06749fd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,3 +10,6 @@ max_line_length = 79 [Makefile] indent_style = tab + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..88181f4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,39 @@ +name: "CodeQL" + +on: + push: + branches: [ "main", "opensuse" ] + pull_request: + branches: [ "main", "opensuse" ] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ python ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..0ccee0b --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,126 @@ +name: CI + +on: + push: + branches: [main, opensuse] + pull_request: + branches: [main, opensuse] + +jobs: + CI: + runs-on: ubuntu-latest + + strategy: + matrix: + build-type: ['normal'] + container: + - 'registry.fedoraproject.org/fedora:latest' + - 'registry.fedoraproject.org/fedora:rawhide' + - 'registry.opensuse.org/opensuse/tumbleweed:latest' + include: + - container: 'registry.fedoraproject.org/fedora:latest' + build-type: 'no-optional-deps' + - container: 'registry.opensuse.org/opensuse/tumbleweed:latest' + build-type: 'no-optional-deps' + fail-fast: false + + container: + image: ${{ matrix.container }} + + steps: + - run: zypper -n install + cpio gzip + bzip2 xz + binutils glibc glibc-32bit glibc-locale + python3-python-magic + python3-rpm + python3-base + python3-setuptools + python3-pybeam + python3-pytest + python3-pytest-cov + python3-pytest-xdist + python3-flake8 + python3-flake8-builtins + python3-flake8-bugbear + python3-flake8-import-order + python3-flake8-quotes + python3-pyxdg + python3-zstandard + python3-tomli + python3-tomli-w + python3-pip + python3-pipx + rpm-build + git + if: ${{ contains(matrix.container, 'opensuse') }} + - run: zypper -n install + checkbashisms dash + 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 + if: ${{ contains(matrix.container, 'opensuse') }} + - run: dnf --nogpgcheck --assumeyes install + /usr/bin/cpio + /usr/bin/bzip2 + /usr/bin/python3 + /usr/bin/readelf + /usr/bin/ldd + /usr/bin/c++filt + /usr/bin/xz + glibc + glibc.i686 + python3-setuptools + python3-magic + python3-rpm + python3-pybeam + python3-pytest + python3-pytest-cov + python3-pytest-xdist + python3-flake8 + python3-flake8-builtins + python3-flake8-import-order + python3-flake8-quotes + python3-pyxdg + python3-tomli-w + python3-zstandard + python3-pip + pipx + rpm-build + git + if: ${{ contains(matrix.container, 'fedora') }} + - run: dnf --assumeyes install + python3-tomli + if: ${{ contains(matrix.container, 'fedora') && ! contains(matrix.container, 'rawhide') }} + - run: dnf --nogpgcheck --assumeyes install + /usr/bin/appstream-util + /usr/bin/desktop-file-validate + dash + 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') + if: matrix.build-type == 'no-optional-deps' + - run: pipx install coveralls + - run: pipx install pyupgrade + - run: pipx install ruff + - uses: actions/checkout@v3 + - run: pytest + - run: flake8 + - run: /github/home/.local/bin/ruff . + - run: find . -name '*.py' | xargs /github/home/.local/bin/pyupgrade --py38-plus + - run: python3 -m cProfile -o profile.stats lint.py -V test/source/* test/binary/* > /dev/null + - run: python3 test/dump_stats.py profile.stats + - name: Collect the coveralls report + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: /github/home/.local/bin/coveralls + if: github.event_name != 'pull_request' @@ -1,9 +1,36 @@ +# Distribution / packaging +.eggs +*.egg-info +build/ +dist/ + +# Byte-compiled / optimized +__pycache__/ +*.py[cod] + +# Environments +.env +.venv +env/ +venv/ + +# Unit test / coverage reports .pytest_cache +.tox/ +.coverage +.coverage.* +.cache + +# IDEs +.idea + +# Patches and files *.orig *.rej -*.pyo -*.pyc +.*.swp + +# Others *.flog *.xz /__version__.py -.cache +/.packit/*.tar.gz diff --git a/.packit.yaml b/.packit.yaml new file mode 100644 index 0000000..cc1162c --- /dev/null +++ b/.packit.yaml @@ -0,0 +1,43 @@ +--- +# docs: https://packit.dev/docs/configuration/ +upstream_package_name: rpmlint +specfile_path: .packit/rpmlint.spec + +actions: + get-current-version: "rpmspec -q --srpm --qf '%{VERSION}' .packit/rpmlint.spec" + +jobs: +- job: copr_build + metadata: + targets: + - fedora-rawhide-x86_64 + - fedora-rawhide-aarch64 + - mageia-cauldron-x86_64 + - mageia-cauldron-aarch64 + - opensuse-tumbleweed-x86_64 + - opensuse-tumbleweed-aarch64 + trigger: pull_request +- job: copr_build + trigger: commit + metadata: + targets: + - fedora-rawhide-x86_64 + - fedora-rawhide-aarch64 + - mageia-cauldron-x86_64 + - mageia-cauldron-aarch64 + - opensuse-tumbleweed-x86_64 + - opensuse-tumbleweed-aarch64 + branch: main + project: rpm-software-management-rpmlint-mainline + list_on_homepage: True + preserve_project: True +- job: copr_build + trigger: commit + metadata: + targets: + - opensuse-tumbleweed-x86_64 + - opensuse-tumbleweed-aarch64 + branch: opensuse + project: rpm-software-management-rpmlint-opensuse + list_on_homepage: True + preserve_project: True diff --git a/.packit/rpmlint.spec b/.packit/rpmlint.spec new file mode 100644 index 0000000..2ef2ab6 --- /dev/null +++ b/.packit/rpmlint.spec @@ -0,0 +1,125 @@ +%{!?python3: %global python3 %{__python3}} + +Name: rpmlint +Version: 2.5.0 +Release: 0%{?dist} +Summary: Tool for checking common errors in RPM packages + +License: GPLv2+ +URL: https://github.com/rpm-software-management/rpmlint +Source0: %{url}/archive/%{version}/%{name}-%{version}.tar.gz + +BuildArch: noarch + +BuildRequires: python3-devel + +%if 0%{?suse_version} +# Unfortunately, these don't get pulled in automatically... +BuildRequires: python-rpm-macros +BuildRequires: python3-setuptools +# For tests +BuildRequires: python3-python-magic +BuildRequires: python3-pybeam +BuildRequires: python3-pyenchant +BuildRequires: python3-pytest +BuildRequires: python3-pytest-cov +BuildRequires: python3-pytest-xdist +BuildRequires: python3-pyxdg +BuildRequires: python3-rpm +BuildRequires: python3-tomli +BuildRequires: python3-tomli-w +BuildRequires: python3-zstandard +BuildRequires: python3-packaging +%else +BuildRequires: python3dist(setuptools) +# For tests +BuildRequires: python3dist(file-magic) +BuildRequires: python3dist(pybeam) +BuildRequires: python3dist(pyenchant) +BuildRequires: python3dist(pytest) +BuildRequires: python3dist(pytest-cov) +BuildRequires: python3dist(pytest-xdist) +BuildRequires: python3dist(pyxdg) +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 +BuildRequires: dash +BuildRequires: /usr/bin/appstream-util +BuildRequires: /usr/bin/checkbashisms +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 +BuildRequires: glibc-langpack-en +%endif + +%if 0%{?suse_version} +BuildRequires: glibc-locale-base +%endif + +%if 0%{?mageia} +BuildRequires: locales-en +%endif + +Requires: /bin/bash +Requires: /usr/bin/appstream-util +Requires: /usr/bin/bzip2 +Requires: /usr/bin/checkbashisms +Requires: /usr/bin/cpio +Requires: /usr/bin/desktop-file-validate +Requires: /usr/bin/groff +Requires: /usr/bin/gtbl +Requires: /usr/bin/ldd +Requires: /usr/bin/man +Requires: /usr/bin/perl +Requires: /usr/bin/readelf +Requires: /usr/bin/xz +Requires: /usr/bin/zstd + +# Enable Python dependency generation +%{?python_enable_dependency_generator} + +%description +rpmlint is a tool for checking common errors in RPM packages. Binary +and source packages as well as spec files can be checked. + + +%prep +%autosetup + + +%build +%py3_build + + +%install +%py3_install + + +%check +%python3 -m pytest + + +%files +%license COPYING +%doc README* +%{_bindir}/rpmlint +%{_bindir}/rpmdiff +%{python3_sitelib}/rpmlint* + + +%changelog @@ -1,2 +0,0 @@ -[pep8] -ignore = E122,E501 diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..ed7dce7 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -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] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 58c3985..0000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: generic - -sudo: required - -services: - - docker - -env: - - DIST=centos7 PYTHON=python PYTEST=py.test FLAKE8=true - - DIST=fedora28 PYTHON=python3 PYTEST=py.test-3 FLAKE8=flake8-3 - - DIST=fedoradev PYTHON=python3 PYTEST=py.test-3 FLAKE8=flake8-3 - -before_install: - - docker build -t $DIST -f test/Dockerfile-$DIST . - -script: - - docker run -e PYTHON=$PYTHON -e PYTEST=$PYTEST -e FLAKE8=$FLAKE8 $DIST test/docker-script.sh diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ba4a1f2 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "python", + "request": "launch", + "program": "lint.py", + "console": "integratedTerminal", + "args": ["./test/binary/*.rpm"] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1af333c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "test" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, +} diff --git a/AbstractCheck.py b/AbstractCheck.py deleted file mode 100644 index 1f594c2..0000000 --- a/AbstractCheck.py +++ /dev/null @@ -1,119 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : AbstractCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Tue Sep 28 00:22:38 1999 -# Purpose : Abstract class to hold all the derived classes. -############################################################################# - -import contextlib -import re -try: - import urllib2 -except ImportError: - import urllib.request as urllib2 - -import Config -from Filter import addDetails, printInfo, printWarning - -# Note: do not add any capturing parentheses here -macro_regex = re.compile(r'%+[{(]?[a-zA-Z_]\w{2,}[)}]?') - - -class _HeadRequest(urllib2.Request): - def get_method(self): - return "HEAD" - - -class _HeadRedirectHandler(urllib2.HTTPRedirectHandler): - def redirect_request(*args): - res = urllib2.HTTPRedirectHandler.redirect_request(*args) - if res: - res = _HeadRequest(res.get_full_url()) - return res - - -class AbstractCheck(object): - known_checks = {} - - def __init__(self, name): - if not AbstractCheck.known_checks.get(name): - AbstractCheck.known_checks[name] = self - self.name = name - self.verbose = False - self.network_enabled = Config.getOption("NetworkEnabled", False) - self.network_timeout = Config.getOption("NetworkTimeout", 10) - - def check(self, pkg): - if pkg.isSource(): - return self.check_source(pkg) - return self.check_binary(pkg) - - def check_source(self, pkg): - return - - def check_binary(self, pkg): - return - - def check_spec(self, pkg, spec_file, spec_lines=None): - return - - def check_url(self, pkg, tag, url): - """ - Check that URL points to something that seems to exist. - Return info() of the response if available. - """ - if not self.network_enabled: - if self.verbose: - printInfo(pkg, 'network-checks-disabled', url) - return - - if self.verbose: - printInfo(pkg, 'checking-url', url, - '(timeout %s seconds)' % self.network_timeout) - - res = None - try: - opener = urllib2.build_opener(_HeadRedirectHandler()) - opener.addheaders = [('User-Agent', - 'rpmlint/%s' % Config.__version__)] - res = opener.open(_HeadRequest(url), timeout=self.network_timeout) - except Exception as e: - errstr = str(e) or repr(e) or type(e) - printWarning(pkg, 'invalid-url', '%s:' % tag, url, errstr) - info = None - if res: - with contextlib.closing(res): - info = res.info() - return info - - -class AbstractFilesCheck(AbstractCheck): - def __init__(self, name, file_regexp): - self.__files_re = re.compile(file_regexp) - AbstractCheck.__init__(self, name) - - def check_binary(self, pkg): - ghosts = pkg.ghostFiles() - for filename in (x for x in pkg.files() if x not in ghosts): - if self.__files_re.match(filename): - self.check_file(pkg, filename) - - def check_file(self, pkg, filename): - """Virtual method called for each file that match the regexp passed - to the constructor. - """ - raise NotImplementedError('check must be implemented in subclass') - - -addDetails( -'invalid-url', -'''The value should be a valid, public HTTP, HTTPS, or FTP URL.''', - -'network-checks-disabled', -'''Checks requiring network access have not been enabled in configuration, -see the NetworkEnabled option.''', -) - -# AbstractCheck.py ends here diff --git a/AppDataCheck.py b/AppDataCheck.py deleted file mode 100644 index 710cc98..0000000 --- a/AppDataCheck.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# check appdata file format violation -# -# http://people.freedesktop.org/~hughsient/appdata/ -# - -import xml.etree.ElementTree as ET - -import AbstractCheck -import Config -from Filter import addDetails, printError -from Pkg import getstatusoutput - - -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): - def __init__(self): - # desktop file need to be in $XDG_DATA_DIRS - # $ echo $XDG_DATA_DIRS/applications - # /var/lib/menu-xdg:/usr/share - AbstractCheck.AbstractFilesCheck.__init__( - self, "AppDataCheck", r"/usr/share/appdata/.*\.appdata.xml$") - - def check_file(self, pkg, filename): - root = pkg.dirName() - f = root + filename - - checker = appdata_checker - if checker[0] == "appstream-util" and not self.network_enabled: - checker += ("--nonet",) - validation_failed = False - try: - st = getstatusoutput(checker + (f,)) - # Return code nonzero? - validation_failed = (st[0] != 0) - except OSError: - # checker is not installed, do a validation manually - try: - ET.parse(pkg.dirName() + filename) - except ET.ParseError: - validation_failed = True - if validation_failed: - printError(pkg, 'invalid-appdata-file', filename) - - -check = AppDataCheck() - -addDetails( -'invalid-appdata-file', -'''appdata file is not valid, check with %s''' % (" ".join(appdata_checker)), -) diff --git a/BinariesCheck.py b/BinariesCheck.py deleted file mode 100644 index 6bb9ef4..0000000 --- a/BinariesCheck.py +++ /dev/null @@ -1,791 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : BinariesCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Tue Sep 28 07:01:42 1999 -# Purpose : check binary files in a binary rpm package. -############################################################################# - -import os -import re -import stat -import subprocess - -import rpm - -import AbstractCheck -import Config -from Filter import addDetails, printError, printWarning -import Pkg - - -DEFAULT_SYSTEM_LIB_PATHS = ( - '/lib', '/usr/lib', '/usr/X11R6/lib', - '/lib64', '/usr/lib64', '/usr/X11R6/lib64') - - -def create_regexp_call(call): - r = r".*?\s+(%s(?:@GLIBC\S+)?)(?:\s|$)" % call - return re.compile(r) - - -def create_nonlibc_regexp_call(call): - r = r".*?\s+UND\s+(%s)\s?.*$" % call - return re.compile(r) - - -class BinaryInfo(object): - - needed_regex = re.compile(r'\s+\(NEEDED\).*\[(\S+)\]') - rpath_regex = re.compile(r'\s+\(RPATH\).*\[(\S+)\]') - soname_regex = re.compile(r'\s+\(SONAME\).*\[(\S+)\]') - comment_regex = re.compile(r'^\s+\[\s*\d+\]\s+\.comment\s+') - pic_regex = re.compile(r'^\s+\[\s*\d+\]\s+\.rela?\.(data|text)') - # GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4 - stack_regex = re.compile(r'^\s+GNU_STACK\s+(?:(?:\S+\s+){5}(\S+)\s+)?') - stack_exec_regex = re.compile(r'^..E$') - undef_regex = re.compile(r'^undefined symbol:\s+(\S+)') - unused_regex = re.compile(r'^\s+(\S+)') - call_regex = re.compile(r'\s0\s+FUNC\s+(.*)') - exit_call_regex = create_regexp_call(r'_?exit') - fork_call_regex = create_regexp_call(r'fork') - setgid_call_regex = create_regexp_call(r'set(?:res|e)?gid') - setuid_call_regex = create_regexp_call(r'set(?:res|e)?uid') - setgroups_call_regex = create_regexp_call(r'(?:ini|se)tgroups') - chroot_call_regex = create_regexp_call('chroot') - - forbidden_functions = Config.getOption("WarnOnFunction") - if forbidden_functions: - for name, func in forbidden_functions.items(): - # precompile regexps - f_name = func['f_name'] - func['f_regex'] = create_nonlibc_regexp_call(f_name) - if 'good_param' in func: - func['waiver_regex'] = re.compile(func['good_param']) - # register descriptions - addDetails(name, func['description']) - - chdir_call_regex = create_regexp_call('chdir') - mktemp_call_regex = create_regexp_call('mktemp') - lto_section_name_prefix = '.gnu.lto_.' - - def __init__(self, pkg, path, fname, is_ar, is_shlib): - self.readelf_error = False - self.needed = [] - self.rpath = [] - self.undef = [] - self.unused = [] - self.comment = False - self.soname = False - self.non_pic = True - self.stack = False - self.exec_stack = False - self.exit_calls = [] - self.forbidden_calls = [] - fork_called = False - self.tail = '' - self.lto_sections = False - - self.setgid = False - self.setuid = False - self.setgroups = False - self.chroot = False - self.chdir = False - self.chroot_near_chdir = False - self.mktemp = False - - is_debug = path.endswith('.debug') - # Currently this implementation works only on specific - # architectures due to reliance on arch specific assembly. - if (pkg.arch.startswith('armv') or pkg.arch == 'aarch64'): - # 10450: ebffffec bl 10408 <chroot@plt> - BinaryInfo.objdump_call_regex = re.compile(br'\sbl\s+(.*)') - elif (pkg.arch.endswith('86') or pkg.arch == 'x86_64'): - # 401eb8: e8 c3 f0 ff ff callq 400f80 <chdir@plt> - BinaryInfo.objdump_call_regex = re.compile(br'callq?\s(.*)') - else: - BinaryInfo.objdump_call_regex = None - - res = Pkg.getstatusoutput( - ('readelf', '-W', '-S', '-l', '-d', '-s', path)) - if not res[0]: - lines = res[1].splitlines() - for line in lines: - if BinaryInfo.lto_section_name_prefix in line: - self.lto_sections = True - - r = BinaryInfo.needed_regex.search(line) - if r: - self.needed.append(r.group(1)) - continue - - r = BinaryInfo.rpath_regex.search(line) - if r: - for p in r.group(1).split(':'): - self.rpath.append(p) - continue - - if BinaryInfo.comment_regex.search(line): - self.comment = True - continue - - if BinaryInfo.pic_regex.search(line): - self.non_pic = False - continue - - r = BinaryInfo.soname_regex.search(line) - if r: - self.soname = r.group(1) - continue - - r = BinaryInfo.stack_regex.search(line) - if r: - self.stack = True - flags = r.group(1) - if flags and BinaryInfo.stack_exec_regex.search(flags): - self.exec_stack = True - continue - - if line.startswith("Symbol table"): - break - - for line in lines: - r = BinaryInfo.call_regex.search(line) - if not r: - continue - line = r.group(1) - - if BinaryInfo.mktemp_call_regex.search(line): - self.mktemp = True - - if BinaryInfo.setgid_call_regex.search(line): - self.setgid = True - - if BinaryInfo.setuid_call_regex.search(line): - self.setuid = True - - if BinaryInfo.setgroups_call_regex.search(line): - self.setgroups = True - - if BinaryInfo.chdir_call_regex.search(line): - self.chdir = True - - if BinaryInfo.chroot_call_regex.search(line): - self.chroot = True - - if BinaryInfo.forbidden_functions: - for r_name, func in BinaryInfo.forbidden_functions.items(): - ret = func['f_regex'].search(line) - if ret: - self.forbidden_calls.append(r_name) - - if is_shlib: - r = BinaryInfo.exit_call_regex.search(line) - if r: - self.exit_calls.append(r.group(1)) - continue - r = BinaryInfo.fork_call_regex.search(line) - if r: - fork_called = True - continue - - # check if we don't have a string that will automatically - # waive the presence of a forbidden call - if self.forbidden_calls: - res = Pkg.getstatusoutput(('strings', path)) - if not res[0]: - for line in res[1].splitlines(): - # as we need to remove elements, iterate backwards - for i in range(len(self.forbidden_calls) - 1, -1, -1): - func = self.forbidden_calls[i] - f = BinaryInfo.forbidden_functions[func] - if 'waiver_regex' not in f: - continue - r = f['waiver_regex'].search(line) - if r: - del self.forbidden_calls[i] - - if self.non_pic: - self.non_pic = 'TEXTREL' in res[1] - - # Ignore all exit() calls if fork() is being called. - # Does not have any context at all but without this kludge, the - # number of false positives would probably be intolerable. - if fork_called: - self.exit_calls = [] - - # check if chroot is near chdir (since otherwise, chroot is called - # without chdir) - if not BinaryInfo.objdump_call_regex and self.chroot and self.chdir: - # On some architectures, e.g. PPC, it is to difficult to - # find the actual invocations of chroot/chdir, if both - # exist assume chroot is fine - self.chroot_near_chdir = True - - elif self.chroot and self.chdir: - p = subprocess.Popen(('objdump', '-d', path), - stdout=subprocess.PIPE, bufsize=-1, - env=dict(os.environ, LC_ALL="C")) - with p.stdout: - index = 0 - chroot_index = -99 - chdir_index = -99 - for line in p.stdout: - res = BinaryInfo.objdump_call_regex.search(line) - if not res: - continue - if b'@plt' not in res.group(1): - pass - elif b'chroot@plt' in res.group(1): - chroot_index = index - if abs(chroot_index - chdir_index) <= 2: - self.chroot_near_chdir = True - break - elif b'chdir@plt' in res.group(1): - chdir_index = index - if abs(chroot_index - chdir_index) <= 2: - self.chroot_near_chdir = True - break - index += 1 - if p.wait() and not self.chroot_near_chdir: - printWarning(pkg, 'binaryinfo-objdump-failed', fname) - self.chroot_near_chdir = True # avoid false positive - elif chroot_index == -99 and chdir_index == -99: - self.chroot_near_chdir = True # avoid false positive - - else: - self.readelf_error = True - # Go and others are producing ar archives that don't have ELF - # headers, so don't complain about it - if not is_ar: - printWarning(pkg, 'binaryinfo-readelf-failed', - fname, re.sub('\n.*', '', res[1])) - - try: - with open(path, 'rb') as fobj: - fobj.seek(-12, os.SEEK_END) - self.tail = Pkg.b2s(fobj.read()) - except Exception as e: - printWarning(pkg, 'binaryinfo-tail-failed %s: %s' % (fname, e)) - - # Undefined symbol and unused direct dependency checks make sense only - # for installed packages. - # skip debuginfo: https://bugzilla.redhat.com/190599 - if not is_ar and not is_debug and isinstance(pkg, Pkg.InstalledPkg): - # We could do this with objdump, but it's _much_ simpler with ldd. - res = Pkg.getstatusoutput(('ldd', '-d', '-r', path)) - if not res[0]: - for line in res[1].splitlines(): - undef = BinaryInfo.undef_regex.search(line) - if undef: - self.undef.append(undef.group(1)) - if self.undef: - try: - res = Pkg.getstatusoutput(['c++filt'] + self.undef) - if not res[0]: - self.undef = res[1].splitlines() - except OSError: - pass - else: - printWarning(pkg, 'ldd-failed', fname) - res = Pkg.getstatusoutput(('ldd', '-r', '-u', path)) - if res[0]: - # Either ldd doesn't grok -u (added in glibc 2.3.4) or we have - # unused direct dependencies - in_unused = False - for line in res[1].splitlines(): - if not line.rstrip(): - pass - elif line.startswith('Unused direct dependencies'): - in_unused = True - elif in_unused: - unused = BinaryInfo.unused_regex.search(line) - if unused: - self.unused.append(unused.group(1)) - else: - in_unused = False - - -path_regex = re.compile(r'(.*/)([^/]+)') -numeric_dir_regex = re.compile(r'/usr(?:/share)/man/man./(.*)\.[0-9](?:\.gz|\.bz2)') -versioned_dir_regex = re.compile(r'[^.][0-9]') -ldso_soname_regex = re.compile(r'^ld(-linux(-(ia|x86_)64))?\.so') -so_regex = re.compile(r'/lib(64)?/[^/]+\.so(\.[0-9]+)*$') -validso_regex = re.compile(r'(\.so\.\d+(\.\d+)*|\d\.so)$') -sparc_regex = re.compile(r'SPARC32PLUS|SPARC V9|UltraSPARC') -system_lib_paths = Config.getOption('SystemLibPaths', DEFAULT_SYSTEM_LIB_PATHS) -pie_exec_re = Config.getOption('PieExecutables') -if pie_exec_re: - pie_exec_re = re.compile(pie_exec_re) -usr_lib_regex = re.compile(r'^/usr/lib(64)?/') -bin_regex = re.compile(r'^(/usr(/X11R6)?)?/s?bin/') -soversion_regex = re.compile(r'.*?([0-9][.0-9]*)\\.so|.*\\.so\\.([0-9][.0-9]*).*') -reference_regex = re.compile(r'\.la$|^/usr/lib(64)?/pkgconfig/') -usr_lib_exception_regex = re.compile(Config.getOption('UsrLibBinaryException', r'^/usr/lib(64)?/(perl|python|ruby|menu|pkgconfig|ocaml|lib[^/]+\.(so|l?a)$|bonobo/servers/|\.build-id)')) -srcname_regex = re.compile(r'(.*?)-[0-9]') -invalid_dir_ref_regex = re.compile(r'/(home|tmp)(\W|$)') -ocaml_mixed_regex = re.compile(r'^Caml1999X0\d\d$') -usr_arch_share_regex = re.compile(r'/share/.*/(?:x86|i.86|x86_64|ppc|ppc64|s390|s390x|ia64|m68k|arm|aarch64|mips|riscv)') - - -def dir_base(path): - res = path_regex.search(path) - if res: - return res.group(1), res.group(2) - else: - return '', path - - -class BinariesCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, 'BinariesCheck') - - def check_binary(self, pkg): - files = pkg.files() - exec_files = [] - has_lib = False - version = None - binary = False - binary_in_usr_lib = False - has_usr_lib_file = False - file_in_lib64 = False - - multi_pkg = False - srpm = pkg[rpm.RPMTAG_SOURCERPM] - if srpm: - res = srcname_regex.search(srpm) - if res: - multi_pkg = (pkg.name != res.group(1)) - - for fname, pkgfile in files.items(): - - if not stat.S_ISDIR(pkgfile.mode) and usr_lib_regex.search(fname): - has_usr_lib_file = True - if not binary_in_usr_lib and \ - usr_lib_exception_regex.search(fname): - # Fake that we have binaries there to avoid - # only-non-binary-in-usr-lib false positives - binary_in_usr_lib = True - - if stat.S_ISREG(pkgfile.mode) and \ - (fname.startswith("/usr/lib64") or fname.startswith("/lib64")): - file_in_lib64 = True - - is_elf = pkgfile.magic.startswith('ELF ') - is_ar = 'current ar archive' in pkgfile.magic - is_ocaml_native = 'Objective caml native' in pkgfile.magic - is_lua_bytecode = 'Lua bytecode' in pkgfile.magic - is_shell = "shell script" in pkgfile.magic - is_binary = is_elf or is_ar or is_ocaml_native or is_lua_bytecode - - if is_shell: - file_start = None - try: - with open(pkgfile.path, 'rb') as inputf: - file_start = inputf.read(2048) - except IOError: - pass - if (file_start and b'This wrapper script should never ' - b'be moved out of the build directory' in file_start): - printError(pkg, 'libtool-wrapper-in-package', fname) - - if not is_binary: - if reference_regex.search(fname): - lines = pkg.grep(invalid_dir_ref_regex, fname) - if lines: - printError(pkg, 'invalid-directory-reference', fname, - '(line %s)' % ", ".join(lines)) - continue - - # binary files only from here on - - binary = True - - if has_usr_lib_file and not binary_in_usr_lib and \ - usr_lib_regex.search(fname): - binary_in_usr_lib = True - - if pkg.arch == 'noarch': - printError( - pkg, - 'arch-independent-package-contains-binary-or-object', - fname) - continue - - # arch dependent packages only from here on - - # in /usr/share ? - if fname.startswith('/usr/share/') and not usr_arch_share_regex.search(fname): - printError(pkg, 'arch-dependent-file-in-usr-share', fname) - - # in /etc ? - if fname.startswith('/etc/'): - printError(pkg, 'binary-in-etc', fname) - - if pkg.arch == 'sparc' and sparc_regex.search(pkgfile.magic): - printError(pkg, 'non-sparc32-binary', fname) - - if is_ocaml_native or is_lua_bytecode or fname.endswith('.o') or \ - fname.endswith('.static'): - continue - - # stripped ? - if 'not stripped' in pkgfile.magic: - printWarning(pkg, 'unstripped-binary-or-object', fname) - - # inspect binary file - is_shlib = so_regex.search(fname) - bin_info = BinaryInfo(pkg, pkgfile.path, fname, is_ar, is_shlib) - - if is_shlib: - has_lib = True - - # shared libs - if is_shlib and not bin_info.readelf_error: - - # so name in library - if not bin_info.soname: - printWarning(pkg, 'no-soname', fname) - else: - if not validso_regex.search(bin_info.soname): - printError(pkg, 'invalid-soname', fname, - bin_info.soname) - else: - (directory, base) = dir_base(fname) - try: - symlink = directory + bin_info.soname - link = files[symlink].linkto - if link not in (fname, base, ''): - printError(pkg, 'invalid-ldconfig-symlink', - fname, link) - except KeyError: - if base.startswith("lib") or \ - base.startswith("ld-"): - printError(pkg, 'no-ldconfig-symlink', fname) - - res = soversion_regex.search(bin_info.soname) - if res: - soversion = res.group(1) or res.group(2) - if version is None: - version = soversion - elif version != soversion: - version = -1 - - if bin_info.non_pic: - printError(pkg, 'shlib-with-non-pic-code', fname) - - # It could be useful to check these for others than shared - # libs only, but that has potential to generate lots of - # false positives and noise. - for s in bin_info.undef: - printWarning(pkg, 'undefined-non-weak-symbol', fname, s) - for s in bin_info.unused: - printWarning(pkg, 'unused-direct-shlib-dependency', - fname, s) - - # calls exit() or _exit()? - for ec in bin_info.exit_calls: - printWarning(pkg, 'shared-lib-calls-exit', fname, ec) - - if bin_info.lto_sections: - printError(pkg, 'lto-bytecode', fname) - - for ec in bin_info.forbidden_calls: - printWarning(pkg, ec, fname, - BinaryInfo.forbidden_functions[ec]['f_name']) - - # rpath ? - if bin_info.rpath: - for p in bin_info.rpath: - if p in system_lib_paths or not usr_lib_regex.search(p): - printError(pkg, 'binary-or-shlib-defines-rpath', - fname, bin_info.rpath) - break - - is_exec = 'executable' in pkgfile.magic - is_shobj = 'shared object' in pkgfile.magic - is_pie_exec = 'pie executable' in pkgfile.magic - - if not is_exec and not is_shobj: - continue - - if is_shobj and not is_exec and '.so' not in fname and \ - bin_regex.search(fname): - # pkgfile.magic does not contain "executable" for PIEs - is_exec = True - - if is_exec: - - if bin_regex.search(fname): - exec_files.append(fname) - - if ocaml_mixed_regex.search(bin_info.tail): - printWarning(pkg, 'ocaml-mixed-executable', fname) - - if ((not is_shobj and not is_pie_exec) and - pie_exec_re and pie_exec_re.search(fname)): - printError(pkg, 'non-position-independent-executable', - fname) - - if bin_info.readelf_error: - continue - - if not bin_info.needed and not ( - bin_info.soname and - ldso_soname_regex.search(bin_info.soname)): - if is_shobj: - printError(pkg, - 'shared-lib-without-dependency-information', - fname) - else: - printError(pkg, 'statically-linked-binary', fname) - - else: - # linked against libc ? - if "libc." not in fname and \ - (not bin_info.soname or - ("libc." not in bin_info.soname and - not ldso_soname_regex.search(bin_info.soname))): - - found_libc = False - for lib in bin_info.needed: - if "libc." in lib: - found_libc = True - break - - if not found_libc: - if is_shobj: - printError(pkg, 'library-not-linked-against-libc', - fname) - else: - printError(pkg, 'program-not-linked-against-libc', - fname) - - if bin_info.stack: - if bin_info.exec_stack: - printWarning(pkg, 'executable-stack', fname) - elif not bin_info.readelf_error and ( - pkg.arch.endswith("86") or - pkg.arch.startswith("pentium") or - pkg.arch in ("athlon", "x86_64")): - printError(pkg, 'missing-PT_GNU_STACK-section', fname) - - if bin_info.setgid and bin_info.setuid and not bin_info.setgroups: - printError(pkg, 'missing-call-to-setgroups-before-setuid', - fname) - - if bin_info.chroot and not bin_info.chroot_near_chdir: - printError(pkg, 'missing-call-to-chdir-with-chroot', fname) - - if bin_info.mktemp: - printError(pkg, 'call-to-mktemp', fname) - - if has_lib: - for f in exec_files: - printError(pkg, 'executable-in-library-package', f) - for f in files: - res = numeric_dir_regex.search(f) - fn = res and res.group(1) or f - if f not in exec_files and not so_regex.search(f) and \ - not versioned_dir_regex.search(fn): - printError(pkg, 'non-versioned-file-in-library-package', f) - if version and version != -1 and version not in pkg.name: - printError(pkg, 'incoherent-version-in-name', version) - - if not binary and not multi_pkg and not file_in_lib64 and pkg.arch != 'noarch': - printError(pkg, 'no-binary') - - if pkg.arch == 'noarch' and file_in_lib64: - printError(pkg, 'noarch-with-lib64') - - if has_usr_lib_file and not binary_in_usr_lib: - printWarning(pkg, 'only-non-binary-in-usr-lib') - - -# Create an object to enable the auto registration of the test -check = BinariesCheck() - -# Add information about checks -addDetails( -'arch-independent-package-contains-binary-or-object', -'''The package contains a binary or object file but is tagged -noarch.''', - -'arch-dependent-file-in-usr-share', -'''This package installs an ELF binary in the /usr/share - hierarchy, which is reserved for architecture-independent files.''', - -'binary-in-etc', -'''This package installs an ELF binary in /etc. Both the -FHS and the FSSTND forbid this.''', - -# 'non-sparc32-binary', -# '', - -'noarch-with-lib64', -'''This package is marked as noarch but installs files into lib64. -Not all architectures have this in path, so the package can't be -noarch.''', - -'invalid-soname', -'''The soname of the library is neither of the form lib<libname>.so.<major> or -lib<libname>-<major>.so.''', - -'invalid-ldconfig-symlink', -'''The symbolic link references the wrong file. It should reference -the shared library.''', - -'no-ldconfig-symlink', -'''The package should not only include the shared library itself, but -also the symbolic link which ldconfig would produce. (This is -necessary, so that the link gets removed by rpm automatically when -the package gets removed, even if for some reason ldconfig would not be -run at package postinstall phase.)''', - -'shlib-with-non-pic-code', -'''The listed shared libraries contain object code that was compiled -without -fPIC. All object code in shared libraries should be -recompiled separately from the static libraries with the -fPIC option. -Use the ``eu-findtextrel'' command on a library with debugging symbols -to list code compiled without -fPIC. - -Another common mistake that causes this problem is linking with -``gcc -Wl,-shared'' instead of ``gcc -shared''.''', - -'libtool-wrapper-in-package', -'''Your package contains a libtool wrapper shell script. This -will not work. Instead of installing the libtool wrapper file run -``libtool --mode=install install -m perm <file> <dest>'' in order -to install the relinked file.''', - -'binary-or-shlib-defines-rpath', -'''The binary or shared library defines `RPATH'. Usually this is a -bad thing because it hardcodes the path to search libraries and so -makes it difficult to move libraries around. Most likely you will find a -Makefile with a line like: gcc test.o -o test -Wl,--rpath. Also, sometimes -configure scripts provide a --disable-rpath flag to avoid this.''', - -'statically-linked-binary', -'''The package installs a statically linked binary or object file. - -Usually this is a packaging bug. If not, contact your rpmlint distributor -about this so that this error gets included in the exception file for rpmlint -and will not be flagged as a packaging bug in the future (or add it to your -local configuration if you installed rpmlint from the source tarball).''', - -'executable-in-library-package', -'''The package mixes up libraries and executables. Mixing up these -both types of files makes upgrades quite impossible.''', - -'non-versioned-file-in-library-package', -'''The package contains files in non versioned directories. This makes it -impossible to have multiple major versions of the libraries installed. -One solution can be to change the directories which contain the files -to subdirs of /usr/lib/<name>-<version> or /usr/share/<name>-<version>. -Another solution can be to include a version number in the file names -themselves.''', - -'incoherent-version-in-name', -'''The package name should contain the major version of the library.''', - -'invalid-directory-reference', -'This file contains a reference to /tmp or /home.', - -'no-binary', -'''The package should be of the noarch architecture because it doesn't contain -any binaries.''', - -# http://sources.redhat.com/ml/libc-alpha/2003-05/msg00034.html -'undefined-non-weak-symbol', -'''The binary contains undefined non-weak symbols. This may indicate improper -linkage; check that the binary has been linked as expected.''', - -# http://www.redhat.com/archives/fedora-maintainers/2006-June/msg00176.html -'unused-direct-shlib-dependency', -'''The binary contains unused direct shared library dependencies. This may -indicate gratuitously bloated linkage; check that the binary has been linked -with the intended shared libraries only.''', - -'only-non-binary-in-usr-lib', -'''There are only non binary files in /usr/lib so they should be in -/usr/share.''', - -'binaryinfo-readelf-failed', -'''Executing readelf on this file failed, all checks could not be run.''', - -'binaryinfo-objdump-failed', -'''Executing objdump on this file failed, all checks could not be run.''', - -'binaryinfo-tail-failed', -'''Reading trailing bytes of this file failed, all checks could not be run.''', - -'ldd-failed', -'''Executing ldd on this file failed, all checks could not be run.''', - -'executable-stack', -'''The binary declares the stack as executable. Executable stack is usually an -error as it is only needed if the code contains GCC trampolines or similar -constructs which uses code on the stack. One common source for needlessly -executable stack cases are object files built from assembler files which -don\'t define a proper .note.GNU-stack section.''', - -'missing-PT_GNU_STACK-section', -'''The binary lacks a PT_GNU_STACK section. This forces the dynamic linker to -make the stack executable. Usual suspects include use of a non-GNU linker or -an old GNU linker version.''', - -'shared-lib-calls-exit', -'''This library package calls exit() or _exit(), probably in a non-fork() -context. Doing so from a library is strongly discouraged - when a library -function calls exit(), it prevents the calling program from handling the -error, reporting it to the user, closing files properly, and cleaning up any -state that the program has. It is preferred for the library to return an -actual error code and let the calling program decide how to handle the -situation.''', - -'ocaml-mixed-executable', -'''Executables built with ocamlc -custom are deprecated. Packagers should ask -upstream maintainers to build these executables without the -custom option. If -this cannot be changed and the executable needs to be packaged in its current -form, make sure that rpmbuild does not strip it during the build, and on setups -that use prelink, make sure that prelink does not strip it either, usually by -placing a blacklist file in /etc/prelink.conf.d. For more information, see -http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=256900#49''', - -'non-position-independent-executable', -'''This executable must be position independent. Check that it is built with --fPIE/-fpie in compiler flags and -pie in linker flags.''', - -'missing-call-to-setgroups-before-setuid', -'''This executable is calling setuid and setgid without setgroups or -initgroups. There is a high probability this means it didn't relinquish all -groups, and this would be a potential security issue to be fixed. Seek POS36-C -on the web for details about the problem.''', - -'missing-call-to-chdir-with-chroot', -'''This executable appears to call chroot without using chdir to change the -current directory. This is likely an error and permits an attacker to break out -of the chroot by using fchdir. While that's not always a security issue, this -has to be checked.''', - -'call-to-mktemp', -'''This executable calls mktemp. As advised by the manpage (mktemp(3)), this -function should be avoided. Some implementations are deeply insecure, and there -is a race condition between the time of check and time of use (TOCTOU). -See http://capec.mitre.org/data/definitions/29.html for details, and contact -upstream to have this issue fixed.''', - -'unstripped-binary-or-object', -'''This executable should be stripped from debugging symbols, in order to take -less space and be loaded faster. This is usually done automatically at -buildtime by rpm. Check the build logs and the permission on the file (some -implementations only strip if the permission is 0755).''', - -'lto-bytecode', -'''This executable contains a LTO section. LTO bytecode is not portable -and should not be distributed in static libraries or e.g. Python modules.''', -) - -# BinariesCheck.py ends here @@ -1,12 +1,12 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to +the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not @@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - - GNU GENERAL PUBLIC LICENSE + + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains @@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions: License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in @@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - + 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is @@ -225,7 +225,7 @@ impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - + 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License @@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it @@ -303,10 +303,9 @@ the "copyright" line and a pointer to where the full notice is found. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. @@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names: This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General +library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. diff --git a/Config.py b/Config.py deleted file mode 100644 index 2deeeca..0000000 --- a/Config.py +++ /dev/null @@ -1,171 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : Config.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Fri Oct 15 20:04:25 1999 -# Purpose : handle configuration options. To be used from config files. -############################################################################# - -import locale -import os.path -import re - -try: - from __version__ import __version__ -except ImportError: - __version__ = 'devel' - -DEFAULT_CHECKS = ("DistributionCheck", - "TagsCheck", - "BinariesCheck", - "ConfigCheck", - "FilesCheck", - "DocFilesCheck", - "FHSCheck", - "SignatureCheck", - "I18NCheck", - "MenuCheck", - "PostCheck", - "InitScriptCheck", - "SourceCheck", - "SpecCheck", - "NamingPolicyCheck", - "ZipCheck", - "PamCheck", - "RpmFileCheck", - "MenuXDGCheck", - "AppDataCheck", - ) - -USEUTF8_DEFAULT = False -try: - if locale.getpreferredencoding() == 'UTF-8': - USEUTF8_DEFAULT = True -except UnicodeError: - try: - if re.match('utf', locale.getdefaultlocale()[1], re.I): - USEUTF8_DEFAULT = True - except UnicodeError: - pass - -info = False -no_exception = False - -# handle the list of checks to load -_checks = [] -_checks.extend(DEFAULT_CHECKS) - - -def addCheck(check): - check = re.sub(r'\.py[co]?$', '', check) - if check not in _checks: - _checks.append(check) - - -def allChecks(): - if _checks == []: - defaultChecks() - return _checks - - -def defaultChecks(): - resetChecks() - _checks.extend(DEFAULT_CHECKS) - - -def resetChecks(): - global _checks - - _checks = [] - - -# handle the list of directories to look for checks - -_dirs = ["/usr/share/rpmlint"] - - -def addCheckDir(dir): - d = os.path.expanduser(dir) - if d not in _dirs: - _dirs.insert(0, d) - - -def checkDirs(): - return _dirs - - -# handle options - -_options = {} - - -def setOption(name, value): - _options[name] = value - - -def getOption(name, default=""): - return _options.get(name, default) - - -# List of filters -_filters = [] -_filters_re = None - - -def addFilter(s): - global _filters_re - - _filters.append(s) - _filters_re = None - - -def removeFilter(s): - global _filters_re - - try: - _filters.remove(s) - except ValueError: - pass - else: - _filters_re = None - - -_scoring = {} - - -def setBadness(s, score): - _scoring[s] = score - - -def badness(s): - return _scoring.get(s, 0) - - -_non_named_group_re = re.compile(r'[^\\](\()[^:]') - - -def isFiltered(s): - global _filters_re - - if _filters_re is None: - # no filter - if len(_filters) == 0: - return False - _filters_re = '(?:' + _filters[0] + ')' - - for idx in range(1, len(_filters)): - # to prevent named group overflow that happen when there is too - # many () in a single regexp: AssertionError: sorry, but this - # version only supports 100 named groups - if '(' in _filters[idx]: - _non_named_group_re.subn('(:?', _filters[idx]) - _filters_re = _filters_re + '|(?:' + _filters[idx] + ')' - _filters_re = re.compile(_filters_re) - - if not no_exception: - if _filters_re.search(s): - return True - return False - -# Config.py ends here diff --git a/ConfigCheck.py b/ConfigCheck.py deleted file mode 100644 index 1db9573..0000000 --- a/ConfigCheck.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : ConfigCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Sun Oct 3 21:48:20 1999 -# Purpose : -############################################################################# - -import AbstractCheck -from Filter import addDetails, printError, printWarning - - -class ConfigCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "ConfigCheck") - - def check_binary(self, pkg): - config_files = pkg.configFiles() - noreplace_files = pkg.noreplaceFiles() - - for c in config_files: - if c.startswith("/var/lib/games/"): - printError(pkg, "score-file-must-not-be-conffile", c) - elif not c.startswith("/etc/") and not c.startswith("/var/"): - printWarning(pkg, "non-etc-or-var-file-marked-as-conffile", c) - - if c not in noreplace_files: - printWarning(pkg, "conffile-without-noreplace-flag", c) - - -# Create an object to enable the auto registration of the test -check = ConfigCheck() - -# Add information about checks -addDetails( -'score-file-must-not-be-conffile', -"""A file in /var/lib/games/ is a configuration file. Store your conf -files in /etc instead.""", - -'non-etc-or-var-file-marked-as-conffile', -"""A file not in /etc or /var is marked as being a configuration file. -Please put your conf files in /etc or /var.""", - -'conffile-without-noreplace-flag', -"""A configuration file is stored in your package without the noreplace flag. -A way to resolve this is to put the following in your SPEC file: - -%config(noreplace) /etc/your_config_file_here -""", - -) - -# ConfigCheck.py ends here diff --git a/DistributionCheck.py b/DistributionCheck.py deleted file mode 100644 index e2e6c63..0000000 --- a/DistributionCheck.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : DistributionCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Tue Sep 28 00:05:33 1999 -# Purpose : check the Distribution specificities in a binary rpm package. -############################################################################# - -import re - -import rpm - -import AbstractCheck -import Config -from Filter import addDetails, printWarning - - -man_regex = re.compile(r"/man(?:\d[px]?|n)/") -info_regex = re.compile(r"(/usr/share|/usr)/info/") -vendor = Config.getOption("Vendor") -distribution = Config.getOption("Distribution") -compress_ext = Config.getOption("CompressExtension", "bz2") - - -class DistributionCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "DistributionCheck") - - def check_binary(self, pkg): - if vendor and pkg[rpm.RPMTAG_VENDOR] != vendor: - printWarning(pkg, "invalid-vendor", pkg[rpm.RPMTAG_VENDOR]) - - if distribution and pkg[rpm.RPMTAG_DISTRIBUTION] != distribution: - printWarning(pkg, "invalid-distribution", - pkg[rpm.RPMTAG_DISTRIBUTION]) - - if compress_ext: - for fname in pkg.files(): - if man_regex.search(fname): - if not fname.endswith(compress_ext): - printWarning(pkg, 'manpage-not-compressed', - compress_ext, fname) - elif info_regex.search(fname) and \ - not fname.endswith("/info/dir"): - if not fname.endswith(compress_ext): - printWarning(pkg, 'infopage-not-compressed', - compress_ext, fname) - - -# Create an object to enable the auto registration of the test -check = DistributionCheck() - -addDetails( -'invalid-vendor', -'In the "%s" distribution, vendor should be "%s".' % (distribution, vendor), - -'invalid-distribution', -'The distribution value should be "' + distribution + '".', - -'manpage-not-compressed', -'''This manual page is not compressed with the %s compression method -(does not have the %s extension). If the compression does not happen -automatically when the package is rebuilt, make sure that you have the -appropriate rpm helper and/or config packages for your target distribution -installed and try rebuilding again; if it still does not happen automatically, -you can compress this file in the %%install section of the spec file.''' -% (compress_ext, compress_ext), - -'infopage-not-compressed', -'''This info page is not compressed with the %s compression method -(does not have the %s extension). If the compression does not happen -automatically when the package is rebuilt, make sure that you have the -appropriate rpm helper and/or config packages for your target distribution -installed and try rebuilding again; if it still does not happen automatically, -you can compress this file in the %%install section of the spec file.''' -% (compress_ext, compress_ext), -) - -# DistributionCheck.py ends here diff --git a/DocFilesCheck.py b/DocFilesCheck.py deleted file mode 100644 index 958c184..0000000 --- a/DocFilesCheck.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2005 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import rpm - -import AbstractCheck -from Filter import addDetails, printWarning -from Pkg import b2s - - -class DocFilesCheck(AbstractCheck.AbstractCheck): - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, 'DocFilesCheck') - - def __checkRequirements(self, pkg): - - doc_files = pkg.docFiles() - files = pkg.files() - - reqs = {} - for fname, pkgfile in files.items(): - reqs[fname] = [x[0] for x in pkgfile.requires] - - core_reqs = {} # dependencies of non-doc files - doc_reqs = {} # dependencies of doc files - - for dep in pkg.header.dsFromHeader(): - # skip deps which were found by find-requires - if dep.Flags() & rpm.RPMSENSE_FIND_REQUIRES != 0: - continue - core_reqs[dep.N()] = [] - - # register things which are provided by the package - for i in pkg.header[rpm.RPMTAG_PROVIDES]: - core_reqs[b2s(i)] = [] - for i in files: - core_reqs[i] = [] - - for i in files: - if not reqs[i]: - continue # skip empty dependencies - if i in doc_files: - target = doc_reqs - else: - target = core_reqs - - for r in reqs[i]: - if r not in target: - target[r] = [] - target[r].append(i) - - # go through the calculated requirements of the %doc files - for (dep, req_files) in doc_reqs.items(): - if dep not in core_reqs: - for f in req_files: - printWarning(pkg, "doc-file-dependency", f, dep) - - def __checkUnwantedFiles(self, pkg): - - for docfile in pkg.docFiles(): - if docfile.endswith("/INSTALL"): - printWarning(pkg, "install-file-in-docs", docfile) - - def check_binary(self, pkg): - - if not pkg.docFiles(): - return - - self.__checkRequirements(pkg) - self.__checkUnwantedFiles(pkg) - - -check = DocFilesCheck() - -addDetails( -'doc-file-dependency', -'''An included file marked as %doc creates a possible additional dependency in -the package. Usually, this is not wanted and may be caused by eg. example -scripts with executable bits set included in the package's documentation.''', - -'install-file-in-docs', -'''A file whose name suggests that it contains installation instructions is -included in the package. Such instructions are often not relevant for already -installed packages; if this is the case for this file and it does not contain -any information that is of interest after the package has been built and -installed, do not include the file in the binary package.''', -) - -# DocFilesCheck.py ends here diff --git a/FHSCheck.py b/FHSCheck.py deleted file mode 100644 index 75ae1c5..0000000 --- a/FHSCheck.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : FHSCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Fri Oct 15 17:40:32 1999 -# Purpose : check FHS conformity -############################################################################# - -import re - -import AbstractCheck -from Filter import addDetails, printWarning - - -class FHSCheck(AbstractCheck.AbstractCheck): - usr_regex = re.compile("^/usr/([^/]+)/") - usr_subdir = ('X11R6', 'bin', 'games', 'include', 'lib', 'lib64', - 'local', 'sbin', 'share', 'src', 'tmp') - var_regex = re.compile("^/var/([^/]+)/") - var_fsstnd = ('adm', 'catman', 'local', 'named', 'nis', 'preserve') - var_subdir = ('account', 'cache', 'crash', 'games', 'lib', 'lock', 'log', - 'mail', 'opt', 'run', 'spool', 'tmp', 'yp', 'www', 'ftp') - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "FHSCheck") - - def check_binary(self, pkg): - var_list = [] - usr_list = [] - - for fname in pkg.files(): - s = FHSCheck.usr_regex.search(fname) - if s: - d = s.group(1) - if d not in FHSCheck.usr_subdir and d not in usr_list: - printWarning(pkg, "non-standard-dir-in-usr", d) - usr_list.append(d) - else: - s = FHSCheck.var_regex.search(fname) - if s: - d = s.group(1) - if d in var_list: - continue - if d in FHSCheck.var_fsstnd: - printWarning(pkg, "FSSTND-dir-in-var", fname) - var_list.append(d) - elif d not in FHSCheck.var_subdir: - printWarning(pkg, "non-standard-dir-in-var", d) - var_list.append(d) - - -# Create an object to enable the auto registration of the test -check = FHSCheck() - -addDetails( -'non-standard-dir-in-usr', -"""Your package is creating a non-standard subdirectory in /usr. The standard -directories are: -%s.""" % ", ".join(FHSCheck.usr_subdir), - -'FSSTND-dir-in-var', -"""Your package is creating an illegal directory in /var. The FSSTND (illegal) -ones are: -%s.""" % ", ".join(FHSCheck.var_fsstnd), - -'non-standard-dir-in-var', -"""Your package is creating a non-standard subdirectory in /var. The standard -directories are: -%s.""" % ", ".join(FHSCheck.var_subdir), -) - -# FHSCheck.py ends here diff --git a/FilesCheck.py b/FilesCheck.py deleted file mode 100644 index db43d4a..0000000 --- a/FilesCheck.py +++ /dev/null @@ -1,1491 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : FilesCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Mon Oct 4 19:32:49 1999 -# Purpose : test various aspects on files: locations, owner, groups, -# permission, setuid, setgid... -############################################################################# - -from datetime import datetime -import os -import re -import stat - -import rpm - -import AbstractCheck -import Config -from Filter import addDetails, printError, printWarning -from Pkg import b2s, catcmd, getstatusoutput, is_utf8, is_utf8_bytestr, shquote - - -# must be kept in sync with the filesystem package -STANDARD_DIRS = ( - '/', - '/bin', - '/boot', - '/etc', - '/etc/X11', - '/etc/opt', - '/etc/profile.d', - '/etc/skel', - '/etc/xinetd.d', - '/home', - '/lib', - '/lib/modules', - '/lib64', - '/media', - '/mnt', - '/mnt/cdrom', - '/mnt/disk', - '/mnt/floppy', - '/opt', - '/proc', - '/root', - '/run', - '/sbin', - '/selinux', - '/srv', - '/sys', - '/tmp', - '/usr', - '/usr/X11R6', - '/usr/X11R6/bin', - '/usr/X11R6/doc', - '/usr/X11R6/include', - '/usr/X11R6/lib', - '/usr/X11R6/lib64', - '/usr/X11R6/man', - '/usr/X11R6/man/man1', - '/usr/X11R6/man/man2', - '/usr/X11R6/man/man3', - '/usr/X11R6/man/man4', - '/usr/X11R6/man/man5', - '/usr/X11R6/man/man6', - '/usr/X11R6/man/man7', - '/usr/X11R6/man/man8', - '/usr/X11R6/man/man9', - '/usr/X11R6/man/mann', - '/usr/bin', - '/usr/bin/X11', - '/usr/etc', - '/usr/games', - '/usr/include', - '/usr/lib', - '/usr/lib/X11', - '/usr/lib/games', - '/usr/lib/gcc-lib', - '/usr/lib/menu', - '/usr/lib64', - '/usr/lib64/gcc-lib', - '/usr/local', - '/usr/local/bin', - '/usr/local/doc', - '/usr/local/etc', - '/usr/local/games', - '/usr/local/info', - '/usr/local/lib', - '/usr/local/lib64', - '/usr/local/man', - '/usr/local/man/man1', - '/usr/local/man/man2', - '/usr/local/man/man3', - '/usr/local/man/man4', - '/usr/local/man/man5', - '/usr/local/man/man6', - '/usr/local/man/man7', - '/usr/local/man/man8', - '/usr/local/man/man9', - '/usr/local/man/mann', - '/usr/local/sbin', - '/usr/local/share', - '/usr/local/share/man', - '/usr/local/share/man/man1', - '/usr/local/share/man/man2', - '/usr/local/share/man/man3', - '/usr/local/share/man/man4', - '/usr/local/share/man/man5', - '/usr/local/share/man/man6', - '/usr/local/share/man/man7', - '/usr/local/share/man/man8', - '/usr/local/share/man/man9', - '/usr/local/share/man/mann', - '/usr/local/src', - '/usr/sbin', - '/usr/share', - '/usr/share/dict', - '/usr/share/doc', - '/usr/share/icons', - '/usr/share/info', - '/usr/share/man', - '/usr/share/man/man1', - '/usr/share/man/man2', - '/usr/share/man/man3', - '/usr/share/man/man4', - '/usr/share/man/man5', - '/usr/share/man/man6', - '/usr/share/man/man7', - '/usr/share/man/man8', - '/usr/share/man/man9', - '/usr/share/man/mann', - '/usr/share/misc', - '/usr/src', - '/usr/tmp', - '/var', - '/var/cache', - '/var/db', - '/var/lib', - '/var/lib/games', - '/var/lib/misc', - '/var/lib/rpm', - '/var/local', - '/var/log', - '/var/mail', - '/var/nis', - '/var/opt', - '/var/preserve', - '/var/spool', - '/var/spool/mail', - '/var/tmp', -) - -DEFAULT_GAMES_GROUPS = 'Games' - -DEFAULT_DANGLING_EXCEPTIONS = ( - ['consolehelper$', 'usermode-consoleonly'], -) - -# Standard users and groups from LSB Core 4.0.0: 21.2 User & Group Names -DEFAULT_STANDARD_USERS = ('root', 'bin', 'daemon', 'adm', 'lp', 'sync', - 'shutdown', 'halt', 'mail', 'news', 'uucp', - 'operator', 'man', 'nobody',) -DEFAULT_STANDARD_GROUPS = ('root', 'bin', 'daemon', 'adm', 'lp', 'sync', - 'shutdown', 'halt', 'mail', 'news', 'uucp', - 'man', 'nobody',) - -DEFAULT_DISALLOWED_DIRS = ( - '/home', - '/mnt', - '/opt', - '/tmp', - '/usr/local', - '/usr/tmp', - '/var/local', - '/var/lock', - '/var/run', - '/var/tmp', -) - -compressions = r'\.(gz|z|Z|zip|bz2|lzma|xz|zst)' -sub_bin_regex = re.compile(r'^(/usr)?/s?bin/\S+/') -backup_regex = re.compile(r'(~|\#[^/]+\#|((\.orig|\.rej)(' + compressions + ')?))$') -compr_regex = re.compile(compressions + r'$') -absolute_regex = re.compile(r'^/([^/]+)') -absolute2_regex = re.compile(r'^/?([^/]+)') -points_regex = re.compile(r'^\.\./(.*)') -doc_regex = re.compile(r'^/usr(/share|/X11R6)?/(doc|man|info)/|^/usr/share/gnome/help') -bin_regex = re.compile(r'^/(?:usr/(?:s?bin|games)|s?bin)/(.*)') -includefile_regex = re.compile(r'\.(c|h)(pp|xx)?$', re.IGNORECASE) -develfile_regex = re.compile(r'\.(a|cmxa?|mli?|gir)$') -buildconfigfile_regex = re.compile(r'(\.pc|/bin/.+-config)$') -# room for improvement with catching more -R, but also for false positives... -buildconfig_rpath_regex = re.compile(r'(?:-rpath|Wl,-R)\b') -sofile_regex = re.compile(r'/lib(64)?/(.+/)?lib[^/]+\.so$') -devel_regex = re.compile(r'(.*)-(debug(info|source)?|devel|headers|source|static)$') -debuginfo_package_regex = re.compile(r'-debug(info)?$') -debugsource_package_regex = re.compile(r'-debugsource$') -use_debugsource = Config.getOption('UseDebugSource', False) -lib_regex = re.compile(r'/lib(?:64)?/lib[^/]+(?:\.so\.[\d\.]+|-[\d\.]+\.so)$') -ldconfig_regex = re.compile(r'^[^#]*ldconfig', re.MULTILINE) -depmod_regex = re.compile(r'^[^#]*depmod', re.MULTILINE) -install_info_regex = re.compile(r'^[^#]*install-info', re.MULTILINE) -perl_temp_file_regex = re.compile(r'.*perl.*/(\.packlist|perllocal\.pod)$') -scm_regex = re.compile( - r'/(?:RCS|CVS)/[^/]+$|/\.(?:bzr|cvs|git|hg|svn)ignore$|' - r',v$|/\.hgtags$|/\.(?:bzr|git|hg|svn)/|/(?:\.arch-ids|{arch})/') -games_path_regex = re.compile(r'^/usr(/lib(64)?)?/games/') -games_group_regex = re.compile(Config.getOption('RpmGamesGroups', DEFAULT_GAMES_GROUPS)) -dangling_exceptions = Config.getOption('DanglingSymlinkExceptions', DEFAULT_DANGLING_EXCEPTIONS) -logrotate_regex = re.compile(r'^/etc/logrotate\.d/(.*)') -module_rpms_ok = Config.getOption('KernelModuleRPMsOK', True) -kernel_modules_regex = re.compile(r'^(?:/usr)/lib/modules/([0-9]+\.[0-9]+\.[0-9]+[^/]*?)/') -kernel_package_regex = re.compile(r'^kernel(22)?(-)?(smp|enterprise|bigmem|secure|BOOT|i686-up-4GB|p3-smp-64GB)?') -normal_zero_length_regex = re.compile(r'^/etc/security/console\.apps/|/\.nosearch$|/__init__\.py$') -perl_regex = re.compile(r'^/usr/lib/perl5/(?:vendor_perl/)?([0-9]+\.[0-9]+)\.([0-9]+)/') -python_regex = re.compile(r'^/usr/lib(?:64)?/python([.0-9]+)/') -python_bytecode_regex_pep3147 = re.compile(r'^(.*)/__pycache__/(.*?)\.([^.]+)(\.opt-[12])?\.py[oc]$') -python_bytecode_regex = re.compile(r'^(.*)(\.py[oc])$') -python_default_version = Config.getOption('PythonDefaultVersion', None) -perl_version_trick = Config.getOption('PerlVersionTrick', True) -log_regex = re.compile(r'^/var/log/[^/]+$') -lib_path_regex = re.compile(r'^(/usr(/X11R6)?)?/lib(64)?') -lib_package_regex = re.compile(r'^(lib|.+-libs)') -hidden_file_regex = re.compile(r'/\.[^/]*$') -manifest_perl_regex = re.compile(r'^/usr/share/doc/perl-.*/MANIFEST(\.SKIP)?$') -shebang_regex = re.compile(br'^#!\s*(\S+)(.*?)$', re.M) -interpreter_regex = re.compile(r'^/(?:usr/)?(?:s?bin|games|libexec(?:/.+)?|(?:lib(?:64)?|share)/.+)/([^/]+)$') -script_regex = re.compile(r'^/((usr/)?s?bin|etc/(rc\.d/init\.d|X11/xinit\.d|cron\.(hourly|daily|monthly|weekly)))/') -sourced_script_regex = re.compile(r'^/etc/(bash_completion\.d|profile\.d)/') -use_utf8 = Config.getOption('UseUTF8', Config.USEUTF8_DEFAULT) -skipdocs_regex = re.compile(Config.getOption('SkipDocsRegexp', r'\.(?:rtf|x?html?|svg|ml[ily]?)$'), re.IGNORECASE) -meta_package_regex = re.compile(Config.getOption('MetaPackageRegexp', r'^(bundle|task)-')) -filesys_packages = ['filesystem'] # TODO: make configurable? -quotes_regex = re.compile(r'[\'"]+') -start_certificate_regex = re.compile(r'^-----BEGIN CERTIFICATE-----$') -start_private_key_regex = re.compile(r'^----BEGIN PRIVATE KEY-----$') - -for idx in range(0, len(dangling_exceptions)): - dangling_exceptions[idx][0] = re.compile(dangling_exceptions[idx][0]) -del idx - -use_relative_symlinks = Config.getOption("UseRelativeSymlinks", True) - -standard_groups = Config.getOption('StandardGroups', DEFAULT_STANDARD_GROUPS) -standard_users = Config.getOption('StandardUsers', DEFAULT_STANDARD_USERS) - -disallowed_dirs = Config.getOption('DisallowedDirs', DEFAULT_DISALLOWED_DIRS) - -non_readable_regexs = (re.compile(r'^/var/log/'), - re.compile(r'^/etc/(g?shadow-?|securetty)$')) - -man_base_regex = re.compile(r'^/usr(?:/share)?/man(?:/overrides)?/man[^/]+/(.+)\.[1-9n]') -man_warn_regex = re.compile(r'^([^:]+:)\d+:\s*') -man_nowarn_regex = re.compile( - # From Lintian: ignore common undefined macros from pod2man << Perl 5.10 - r'\`(Tr|IX)\' not defined|' - # .so entries won't resolve as we're dealing with stdin - r'No such file or directory|' - # TODO, better handling for these (see e.g. Lintian) - r'(can\'t break|cannot adjust) line') -man_warn_category = Config.getOption('ManWarningCategory', 'mac') - -fsf_license_regex = re.compile(br'(GNU((\s+(Library|Lesser|Affero))?(\s+General)?\s+Public|\s+Free\s+Documentation)\s+Licen[cs]e|(GP|FD)L)', re.IGNORECASE) -fsf_wrong_address_regex = re.compile(br'(675\s+Mass\s+Ave|59\s+Temple\s+Place|Franklin\s+Steet|02139|02111-1307)', re.IGNORECASE) - -scalable_icon_regex = re.compile(r'^/usr(?:/local)?/share/icons/.*/scalable/') - -# "is binary" stuff borrowed from https://pypi.python.org/pypi/binaryornot -# TODO: switch to it sometime later instead of embedding our own copy - -printable_extended_ascii = b'\n\r\t\f\b' -if bytes is str: - # Python 2 means we need to invoke chr() explicitly - printable_extended_ascii += b''.join(map(chr, range(32, 256))) -else: - # Python 3 means bytes accepts integer input directly - printable_extended_ascii += bytes(range(32, 256)) - - -def peek(filename, pkg, length=1024): - """ - Peek into a file, return a chunk from its beginning and a flag if it - seems to be a text file. - """ - chunk = None - try: - with open(filename, 'rb') as fobj: - chunk = fobj.read(length) - except IOError as e: # eg. https://bugzilla.redhat.com/209876 - printWarning(pkg, 'read-error', e) - return (chunk, False) - - if b'\0' in chunk: - return (chunk, False) - - if not chunk: # Empty files are considered text - return (chunk, True) - - fl = filename.lower() - - # PDF's are binary but often detected as text by the algorithm below - if fl.endswith('.pdf') and chunk.startswith(b'%PDF-'): - return (chunk, False) - # Ditto RDoc RI files - if fl.endswith('.ri') and '/ri/' in fl: - return (chunk, False) - - # Binary if control chars are > 30% of the string - control_chars = chunk.translate(None, printable_extended_ascii) - nontext_ratio = float(len(control_chars)) / float(len(chunk)) - istext = nontext_ratio <= 0.30 - - return (chunk, istext) - - -# See Python sources for a full list of the values here. -# https://github.com/python/cpython/blob/master/Lib/importlib/_bootstrap_external.py -# https://github.com/python/cpython/blob/2.7/Python/import.c -# https://github.com/python/cpython/commit/93602e3af70d3b9f98ae2da654b16b3382b68d50 -_python_magic_values = { - '2.2': [60717], - '2.3': [62011], - '2.4': [62061], - '2.5': [62131], - '2.6': [62161], - '2.7': [62211], - '3.0': [3130], - '3.1': [3150], - '3.2': [3180], - '3.3': [3230], - '3.4': [3310], - '3.5': [3350, 3351], # 3350 for < 3.5.2 - '3.6': [3379], - '3.7': [3390, 3391, 3392, 3393, 3394], -} - - -def get_expected_pyc_magic(path): - """ - .pyc/.pyo files embed a 4-byte magic value identifying which version of - the python bytecode ABI they are for. Given a path to a .pyc/.pyo file, - return a (magic ABI values, python version) tuple. For example, - '/usr/lib/python3.1/foo.pyc' should return (3151, '3.1'). - The first value will be None if the python version was not resolved - from the given pathname and the PythonDefaultVersion configuration - variable is not set, or if we don't know the magic ABI values for the - python version (no matter from which source the version came from). - The second value will be None if a python version could not be resolved - from the given pathname. - """ - - ver_from_path = None - m = python_regex.search(path) - if m: - ver_from_path = m.group(1) - - expected_version = ver_from_path or python_default_version - expected_magic_values = _python_magic_values.get(expected_version) - - if not expected_magic_values: - return (None, ver_from_path) - - # In Python 2, if Py_UnicodeFlag is set, Python's import code uses a value - # one higher, but this is off by default. In Python 3.0 and 3.1 (but no - # longer in 3.2), it always uses the value one higher: - if expected_version[:3] in ('3.0', '3.1'): - expected_magic_values = [x + 1 for x in expected_magic_values] - - return (expected_magic_values, ver_from_path) - - -def py_demarshal_long(b): - """ - Counterpart to Python's PyMarshal_ReadLongFromFile, operating on the - bytes in a string. - """ - if isinstance(b, str): - b = map(ord, b) - return (b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24)) - - -def pyc_magic_from_chunk(chunk): - """From given chunk (beginning of the file), return Python magic number""" - return py_demarshal_long(chunk[:4]) & 0xffff - - -def pyc_mtime_from_chunk(chunk): - """From given chunk (beginning of the file), return mtime or None - - From Python 3.7, mtime is not always present. - - See https://www.python.org/dev/peps/pep-0552/#specification - """ - magic = pyc_magic_from_chunk(chunk) - second = py_demarshal_long(chunk[4:8]) - if magic >= _python_magic_values['3.7'][0]: - if second == 0: - return py_demarshal_long(chunk[8:12]) - return None # No mtime saved, TODO check hashes instead - return second - - -def python_bytecode_to_script(path): - """ - Given a python bytecode path, give the path of the .py file - (or None if not python bytecode). - """ - - res = python_bytecode_regex_pep3147.search(path) - if res: - return res.group(1) + '/' + res.group(2) + '.py' - - res = python_bytecode_regex.search(path) - if res: - return res.group(1) + '.py' - - return None - - -def script_interpreter(chunk): - res = shebang_regex.search(chunk) if chunk else None - return (b2s(res.group(1)), b2s(res.group(2)).strip()) \ - if res and res.start() == 0 else (None, "") - - -class FilesCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, 'FilesCheck') - - def check(self, pkg): - - if use_utf8: - 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) - - config_files = pkg.configFiles() - ghost_files = pkg.ghostFiles() - doc_files = pkg.docFiles() - req_names = pkg.req_names() - lib_package = lib_package_regex.search(pkg.name) - is_kernel_package = kernel_package_regex.search(pkg.name) - debuginfo_package = debuginfo_package_regex.search(pkg.name) - debugsource_package = debugsource_package_regex.search(pkg.name) - - # report these errors only once - perl_dep_error = False - python_dep_error = False - lib_file = False - non_lib_file = None - log_files = [] - logrotate_file = False - debuginfo_srcs = False - debuginfo_debugs = False - - if not doc_files: - printWarning(pkg, 'no-documentation') - - if files: - if meta_package_regex.search(pkg.name): - printWarning(pkg, 'file-in-meta-package') - elif debuginfo_package or debugsource_package: - printError(pkg, 'empty-debuginfo-package') - - # Prefetch scriptlets, strip quotes from them (#169) - postin = pkg[rpm.RPMTAG_POSTIN] or \ - pkg.scriptprog(rpm.RPMTAG_POSTINPROG) - if postin: - postin = quotes_regex.sub('', postin) - postun = pkg[rpm.RPMTAG_POSTUN] or \ - pkg.scriptprog(rpm.RPMTAG_POSTUNPROG) - if postun: - postun = quotes_regex.sub('', postun) - - # Unique (rdev, inode) combinations - hardlinks = {} - - # All executable files from standard bin dirs (basename => [paths]) - # Hack: basenames with empty paths links are symlinks (not subject - # to duplicate binary check, but yes for man page existence check) - bindir_exes = {} - - # All man page "base" names (without section etc extensions) - man_basenames = set() - - for f, pkgfile in files.items(): - mode = pkgfile.mode - user = pkgfile.user - group = pkgfile.group - link = pkgfile.linkto - size = pkgfile.size - rdev = pkgfile.rdev - inode = pkgfile.inode - is_doc = f in doc_files - nonexec_file = False - - for match in AbstractCheck.macro_regex.findall(f): - printWarning(pkg, 'unexpanded-macro', f, match) - if standard_users and user not in standard_users: - printWarning(pkg, 'non-standard-uid', f, user) - if standard_groups and group not in standard_groups: - printWarning(pkg, 'non-standard-gid', f, group) - - if not module_rpms_ok and kernel_modules_regex.search(f) and not \ - is_kernel_package: - printError(pkg, "kernel-modules-not-in-kernel-packages", f) - - for i in disallowed_dirs: - if f.startswith(i): - printError(pkg, 'dir-or-file-in-%s' % - '-'.join(i.split('/')[1:]), f) - - if f.startswith('/run/'): - if f not in ghost_files: - printWarning(pkg, 'non-ghost-in-run', f) - elif f.startswith('/etc/systemd/system/'): - printWarning(pkg, 'systemd-unit-in-etc', f) - elif f.startswith('/etc/udev/rules.d/'): - printWarning(pkg, 'udev-rule-in-etc', f) - elif f.startswith('/etc/tmpfiles.d/'): - printWarning(pkg, 'tmpfiles-conf-in-etc', f) - elif sub_bin_regex.search(f): - printError(pkg, 'subdir-in-bin', f) - elif '/site_perl/' in f: - printWarning(pkg, 'siteperl-in-perl-module', f) - - if backup_regex.search(f): - printError(pkg, 'backup-file-in-package', f) - elif scm_regex.search(f): - printError(pkg, 'version-control-internal-file', f) - elif f.endswith('/.htaccess'): - printError(pkg, 'htaccess-file', f) - elif hidden_file_regex.search(f) and not f.startswith("/etc/skel/") and not f.endswith("/.build-id"): - printWarning(pkg, 'hidden-file-or-dir', f) - elif manifest_perl_regex.search(f): - printWarning(pkg, 'manifest-in-perl-module', f) - elif f == '/usr/info/dir' or f == '/usr/share/info/dir': - printError(pkg, 'info-dir-file', f) - - res = logrotate_regex.search(f) - if res: - logrotate_file = True - if res.group(1) != pkg.name: - printError(pkg, 'incoherent-logrotate-file', f) - - deps = [x[0] for x in pkg.requires() + pkg.recommends() + pkg.suggests()] - if res and not ('logrotate' in deps) and pkg.name != "logrotate": - printError(pkg, 'missing-dependency-to-logrotate', "for logrotate script", f) - if f.startswith('/etc/cron.') \ - and not ('cron' in deps) and pkg.name != "cron": - printError(pkg, 'missing-dependency-to-cron', "for cron script", f) - if f.startswith('/etc/xinet.d/') \ - and not ('xinetd' in deps) and pkg.name != "xinetd": - printError(pkg, 'missing-dependency-to-xinetd', "for xinet.d script", f) - - if link != '': - ext = compr_regex.search(link) - if ext: - if not re.compile(r'\.%s$' % ext.group(1)).search(f): - printError(pkg, 'compressed-symlink-with-wrong-ext', - f, link) - - perm = mode & 0o7777 - mode_is_exec = mode & 0o111 - - if log_regex.search(f): - log_files.append(f) - - # Hardlink check - 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, "%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, - "%o" % perm) - if mode & 0o777 != 0o755: - printError(pkg, 'non-standard-executable-perm', f, - "%o" % perm) - - if not devel_pkg: - if lib_path_regex.search(f): - lib_file = True - elif not is_doc: - non_lib_file = f - - if log_regex.search(f): - nonexec_file = True - if user != 'root': - printError(pkg, 'non-root-user-log-file', f, user) - if group != 'root': - printError(pkg, 'non-root-group-log-file', f, group) - if f not in ghost_files: - printError(pkg, 'non-ghost-file', f) - - chunk = None - istext = False - res = None - try: - res = os.access(pkgfile.path, os.R_OK) - except UnicodeError as e: # e.g. non-ASCII, C locale, python 3 - printWarning(pkg, 'inaccessible-filename', f, e) - else: - if res: - (chunk, istext) = peek(pkgfile.path, pkg) - - (interpreter, interpreter_args) = script_interpreter(chunk) - - if doc_regex.search(f): - if not interpreter: - nonexec_file = True - if not is_doc: - printError(pkg, 'not-listed-as-documentation', f) - - if devel_pkg and f.endswith('.typelib'): - printError(pkg, 'non-devel-file-in-devel-package', f) - - # 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: - if not ldconfig_regex.search(postin): - printError(pkg, 'postin-without-ldconfig', f) - - if not postun: - printError(pkg, 'library-without-ldconfig-postun', f) - else: - if not ldconfig_regex.search(postun): - printError(pkg, 'postun-without-ldconfig', f) - - # check depmod call in %post and %postun - res = not is_kernel_package and kernel_modules_regex.search(f) - if res: - kernel_version = res.group(1) - kernel_version_regex = re.compile( - r'\bdepmod\s+-a.*F\s+/boot/System\.map-' + - re.escape(kernel_version) + r'\b.*\b' + - re.escape(kernel_version) + r'\b', - re.MULTILINE | re.DOTALL) - - if not postin or not depmod_regex.search(postin): - printError(pkg, 'module-without-depmod-postin', f) - # check that we run depmod on the right kernel - elif not kernel_version_regex.search(postin): - printError(pkg, 'postin-with-wrong-depmod', f) - - if not postun or not depmod_regex.search(postun): - printError(pkg, 'module-without-depmod-postun', f) - # check that we run depmod on the right kernel - elif not kernel_version_regex.search(postun): - printError(pkg, 'postun-with-wrong-depmod', f) - - # check install-info call in %post and %postun - if f.startswith('/usr/share/info/'): - if not postin: - printError(pkg, - 'info-files-without-install-info-postin', f) - elif not install_info_regex.search(postin): - printError(pkg, 'postin-without-install-info', f) - - preun = pkg[rpm.RPMTAG_PREUN] or \ - pkg.scriptprog(rpm.RPMTAG_PREUNPROG) - if not postun and not preun: - printError(pkg, - 'info-files-without-install-info-postun', f) - elif not ((postun and install_info_regex.search(postun)) or - (preun and install_info_regex.search(preun))): - printError(pkg, 'postin-without-install-info', f) - - # check perl temp file - if perl_temp_file_regex.search(f): - printWarning(pkg, 'perl-temp-file', f) - - is_buildconfig = istext and buildconfigfile_regex.search(f) - - # check rpaths in buildconfig files - if is_buildconfig: - ln = pkg.grep(buildconfig_rpath_regex, f) - if ln: - printError(pkg, 'rpath-in-buildconfig', f, 'lines', ln) - - res = bin_regex.search(f) - if res: - if not mode_is_exec: - printWarning(pkg, 'non-executable-in-bin', f, - "%o" % perm) - else: - exe = res.group(1) - if "/" not in exe: - bindir_exes.setdefault(exe, []).append(f) - - if (not devel_pkg and not is_doc and - (is_buildconfig or includefile_regex.search(f) or - develfile_regex.search(f))): - printWarning(pkg, 'devel-file-in-non-devel-package', f) - if mode & 0o444 != 0o444 and perm & 0o7000 == 0: - ok_nonreadable = False - for regex in non_readable_regexs: - if regex.search(f): - ok_nonreadable = True - break - if not ok_nonreadable: - 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, "%o" % perm) - - if not perl_dep_error: - res = perl_regex.search(f) - if res: - if perl_version_trick: - vers = res.group(1) + '.' + res.group(2) - else: - vers = res.group(1) + res.group(2) - if not (pkg.check_versioned_dep('perl-base', vers) or - pkg.check_versioned_dep('perl', vers)): - printError(pkg, 'no-dependency-on', - 'perl-base', vers) - perl_dep_error = True - - if not python_dep_error: - res = python_regex.search(f) - if (res and not - any((pkg.check_versioned_dep(dep, res.group(1)) - for dep in ( - 'python', 'python-base', 'python(abi)')))): - printError(pkg, 'no-dependency-on', 'python-base', - res.group(1)) - python_dep_error = True - - source_file = python_bytecode_to_script(f) - if source_file: - if source_file in files: - if chunk: - # Verify that the magic ABI value embedded in the - # .pyc header is correct - found_magic = pyc_magic_from_chunk(chunk) - exp_magic, exp_version = get_expected_pyc_magic(f) - if exp_magic and found_magic not in exp_magic: - found_version = 'unknown' - for (pv, pm) in _python_magic_values.items(): - if found_magic in pm: - found_version = pv - break - # If expected version was from the file path, - # issue # an error, otherwise a warning. - msg = (pkg, - 'python-bytecode-wrong-magic-value', - f, "expected %s (%s), found %d (%s)" % - (" or ".join(map(str, exp_magic)), - exp_version or python_default_version, - found_magic, found_version)) - if exp_version is not None: - printError(*msg) - else: - printWarning(*msg) - - # Verify that the timestamp embedded in the .pyc - # header matches the mtime of the .py file: - pyc_timestamp = pyc_mtime_from_chunk(chunk) - # If it's a symlink, check target file mtime. - srcfile = pkg.readlink(files[source_file]) - if not srcfile: - printWarning( - pkg, 'python-bytecode-without-source', f) - elif (pyc_timestamp is not None and - pyc_timestamp != srcfile.mtime): - cts = datetime.fromtimestamp( - pyc_timestamp).isoformat() - sts = datetime.fromtimestamp( - srcfile.mtime).isoformat() - printError( - pkg, 'python-bytecode-inconsistent-mtime', - f, cts, srcfile.name, sts) - else: - printWarning(pkg, 'python-bytecode-without-source', f) - - # normal executable check - if mode & stat.S_IXUSR and perm != 0o755: - printError(pkg, 'non-standard-executable-perm', - f, "%o" % perm) - if mode_is_exec: - if f in config_files: - printError(pkg, 'executable-marked-as-config-file', f) - if not nonexec_file: - # doc_regex and log_regex checked earlier, no match, - # check rest of usual cases here. Sourced scripts have - # their own check, so disregard them here. - nonexec_file = f.endswith('.pc') or \ - compr_regex.search(f) or \ - includefile_regex.search(f) or \ - develfile_regex.search(f) or \ - logrotate_regex.search(f) - if nonexec_file: - printWarning(pkg, 'spurious-executable-perm', f) - elif f.startswith('/etc/') and f not in config_files and \ - f not in ghost_files: - printWarning(pkg, 'non-conffile-in-etc', f) - - if pkg.arch == 'noarch' and f.startswith('/usr/lib64/python'): - printError(pkg, 'noarch-python-in-64bit-path', f) - - if debuginfo_package: - if f.endswith('.debug'): - debuginfo_debugs = True - else: - debuginfo_srcs = True - - res = man_base_regex.search(f) - if res: - man_basenames.add(res.group(1)) - if use_utf8 and chunk: - # TODO: sequence based invocation - cmd = getstatusoutput( - '%s %s | gtbl | groff -mtty-char -Tutf8 ' - '-P-c -mandoc -w%s >%s' % - (catcmd(f), shquote(pkgfile.path), - shquote(man_warn_category), os.devnull), - shell=True, lc_all="en_US.UTF-8") - for line in cmd[1].split("\n"): - res = man_warn_regex.search(line) - if not res or man_nowarn_regex.search(line): - continue - printWarning(pkg, "manual-page-warning", f, - line[res.end(1):]) - - if f.endswith(".svgz") and f[0:-1] not in files \ - and scalable_icon_regex.search(f): - printWarning(pkg, "gzipped-svg-icon", f) - - if f.endswith('.pem') and f not in ghost_files: - if pkg.grep(start_certificate_regex, f): - printWarning(pkg, 'pem-certificate', f) - if pkg.grep(start_private_key_regex, f): - printError(pkg, 'pem-private-key', f) - - # text file checks - if istext: - # ignore perl module shebang -- TODO: disputed... - if f.endswith('.pm'): - interpreter = None - # sourced scripts should not be executable - if sourced_script_regex.search(f): - if interpreter: - printError(pkg, - 'sourced-script-with-shebang', f, - interpreter, interpreter_args) - if mode_is_exec: - printError(pkg, 'executable-sourced-script', - f, "%o" % perm) - # ...but executed ones should - elif interpreter or mode_is_exec or script_regex.search(f): - if interpreter: - res = interpreter_regex.search(interpreter) - if (mode_is_exec or script_regex.search(f)): - if res and res.group(1) == 'env': - printError(pkg, 'env-script-interpreter', - f, interpreter, - interpreter_args) - elif not res: - printError(pkg, 'wrong-script-interpreter', - f, interpreter, - interpreter_args) - elif not nonexec_file and not \ - (lib_path_regex.search(f) and - f.endswith('.la')): - printError(pkg, 'script-without-shebang', f) - - if not mode_is_exec and not is_doc and \ - interpreter and interpreter.startswith("/"): - printError(pkg, 'non-executable-script', f, - "%o" % perm, interpreter, - interpreter_args) - if b'\r' in chunk: - printError( - pkg, 'wrong-script-end-of-line-encoding', f) - elif is_doc and not skipdocs_regex.search(f): - if b'\r' in chunk: - printWarning( - pkg, 'wrong-file-end-of-line-encoding', f) - # We check only doc text files for UTF-8-ness; - # checking everything may be slow and can generate - # lots of unwanted noise. - if use_utf8 and not is_utf8(pkgfile.path): - printWarning(pkg, 'file-not-utf8', f) - if fsf_license_regex.search(chunk) and \ - fsf_wrong_address_regex.search(chunk): - printError(pkg, 'incorrect-fsf-address', f) - - elif is_doc and chunk and compr_regex.search(f): - ff = compr_regex.sub('', f) - if not skipdocs_regex.search(ff): - # compressed docs, eg. info and man files etc - if use_utf8 and not is_utf8(pkgfile.path): - printWarning(pkg, 'file-not-utf8', f) - - # normal dir check - elif stat.S_ISDIR(mode): - if mode & 0o1002 == 2: # world writable w/o sticky bit - printError(pkg, 'world-writable', f, "%o" % perm) - if perm != 0o755: - 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) and not f.endswith("/.build-id"): - printWarning(pkg, 'hidden-file-or-dir', f) - - # symbolic link check - elif stat.S_ISLNK(mode): - - is_so = sofile_regex.search(f) - if not devel_pkg and is_so and not link.endswith('.so'): - printWarning(pkg, 'devel-file-in-non-devel-package', f) - - res = man_base_regex.search(f) - if res: - man_basenames.add(res.group(1)) - else: - res = bin_regex.search(f) - if res: - exe = res.group(1) - if "/" not in exe: - bindir_exes.setdefault(exe, []) - - # absolute link - r = absolute_regex.search(link) - if r: - if not is_so and link not in files and \ - link not in req_names: - is_exception = False - for e in dangling_exceptions: - if e[0].search(link): - is_exception = e[1] - break - if is_exception: - if is_exception not in req_names: - printWarning(pkg, 'no-dependency-on', - is_exception) - else: - printWarning(pkg, 'dangling-symlink', f, link) - linktop = r.group(1) - r = absolute_regex.search(f) - if r: - filetop = r.group(1) - if filetop == linktop or use_relative_symlinks: - printWarning(pkg, 'symlink-should-be-relative', - f, link) - # relative link - else: - if not is_so: - abslink = '%s/%s' % (os.path.dirname(f), link) - abslink = os.path.normpath(abslink) - if abslink not in files and abslink not in req_names: - is_exception = False - for e in dangling_exceptions: - if e[0].search(link): - is_exception = e[1] - break - if is_exception: - if is_exception not in req_names: - printWarning(pkg, 'no-dependency-on', - is_exception) - else: - printWarning(pkg, 'dangling-relative-symlink', - f, link) - pathcomponents = f.split('/')[1:] - r = points_regex.search(link) - lastpop = None - mylink = None - - while r: - mylink = r.group(1) - if len(pathcomponents) == 0: - printError(pkg, 'symlink-has-too-many-up-segments', - f, link) - break - else: - lastpop = pathcomponents[0] - pathcomponents = pathcomponents[1:] - r = points_regex.search(mylink) - - if mylink and lastpop: - r = absolute2_regex.search(mylink) - linktop = r.group(1) - - # does the link go up and then down into the same - # directory? - # if linktop == lastpop: - # printWarning(pkg, 'lengthy-symlink', f, link) - - # have we reached the root directory? - if len(pathcomponents) == 0 and linktop != lastpop \ - and not use_relative_symlinks: - # relative link into other toplevel directory - printWarning(pkg, 'symlink-should-be-absolute', f, - link) - # check additional segments for mistakes like - # `foo/../bar/' - for linksegment in mylink.split('/'): - if linksegment == '..': - printError( - pkg, - 'symlink-contains-up-and-down-segments', - f, link) - - if f.startswith('/etc/cron.d/'): - if stat.S_ISLNK(mode): - printError(pkg, 'symlink-crontab-file', f) - - if mode_is_exec: - printError(pkg, 'executable-crontab-file', f) - - if stat.S_IWGRP & mode or stat.S_IWOTH & mode: - printError(pkg, 'non-owner-writeable-only-crontab-file', f) - - 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) - - if not use_debugsource and debuginfo_package and debuginfo_debugs and not debuginfo_srcs: - printError(pkg, 'debuginfo-without-sources') - - for exe, paths in bindir_exes.items(): - if len(paths) > 1: - printWarning(pkg, "duplicate-executable", exe, paths) - if exe not in man_basenames: - printWarning(pkg, "no-manual-page-for-binary", exe) - - -# Create an object to enable the auto registration of the test -check = FilesCheck() - -addDetails( -'no-documentation', -'''The package contains no documentation (README, doc, etc). -You have to include documentation files.''', - -'not-listed-as-documentation', -'''The documentation files of this package are not listed with -the standard %doc tag.''', - -'non-standard-uid', -'''A file in this package is owned by a non standard user. -Standard users are: -%s.''' % ", ".join(standard_users), - -'non-standard-gid', -'''A file in this package is owned by a non standard group. -Standard groups are: -%s.''' % ", ".join(standard_groups), - -'library-without-ldconfig-postin', -'''This package contains a library and provides no %post scriptlet containing -a call to ldconfig.''', - -'postin-without-ldconfig', -'''This package contains a library and its %post scriptlet doesn't call -ldconfig.''', - -'library-without-ldconfig-postun', -'''This package contains a library and provides no %postun scriptlet containing -a call to ldconfig.''', - -'postun-without-ldconfig', -'''This package contains a library and its %postun doesn't call ldconfig.''', - -'info-files-without-install-info-postin', -'''This package contains info files and provides no %post scriptlet containing -a call to install-info.''', - -'postin-without-install-info', -'This package contains info files and its %post doesn\'t call install-info.', - -'info-files-without-install-info-postun', -'''This package contains info files and provides no %postun scriptlet -containing a call to install-info.''', - -'postun-without-install-info', -'''This package contains info files and its %postun doesn't call -install-info.''', - -'perl-temp-file', -'''You have a perl temporary file in your package. Usually, this -file is beginning with a dot (.) and contain "perl" in its name.''', - -'non-ghost-in-run', -'''A file or directory in the package is located in /run. Files installed -in this directory should be marked as %ghost and created at runtime to work -properly in tmpfs /run setups.''', - -'systemd-unit-in-etc', -'''A systemd unit has been packaged in /etc/systemd/system. These units should -be installed in the system unit dir instead.''', - -'udev-rule-in-etc', -'''A udev rule has been packaged in /etc/udev/rules.d. These rules should be -installed in the system rules dir instead.''', - -'tmpfiles-conf-in-etc', -'''A tmpfiles config has been packaged in /etc/tmpfiles.d. These rules should be -installed in the system tmpfiles dir instead.''', - -'subdir-in-bin', -'''The package contains a subdirectory in /usr/bin. It's not permitted to -create a subdir there. Create it in /usr/lib/ instead.''', - -'backup-file-in-package', -'''You have a file whose name looks like one for backup files, usually created -by an editor or resulting from applying unclean (fuzzy, or ones with line -offsets) patches.''', - -'version-control-internal-file', -'''You have included file(s) internally used by a version control system -in the package. Move these files out of the package and rebuild it.''', - -'htaccess-file', -'''You have individual apache configuration .htaccess file(s) in your package. -Replace them by a central configuration file in /etc/, according to the web -application packaging policy for your distribution.''', - -'info-dir-file', -'''You have /usr/info/dir or /usr/share/info/dir in your package. It will cause -conflicts with other packages and thus is not allowed. Please remove it and -rebuild your package.''', - -'non-conffile-in-etc', -'''A non-executable file in your package is being installed in /etc, but is not -a configuration file. All non-executable files in /etc should be configuration -files. Mark the file as %config in the spec file.''', - -'compressed-symlink-with-wrong-ext', -'''The symlink points to a compressed file but doesn't use the same -extension.''', - -'setuid-binary', -'''The file is setuid; this may be dangerous, especially if this -file is setuid root. Sometimes file capabilities can be used instead of -setuid bits.''', - -'setgid-binary', -'''The file is setgid. Usually this is a packaging bug. If this is a game, -then, you should use the proper rpm group, or location.''', - -'non-standard-executable-perm', -'''A standard executable should have permission set to 0755. If you get this -message, it means that you have a wrong executable permissions in some files -included in your package.''', - -'non-executable-in-bin', -'''A file is being installed in /usr/bin, but is not an executable. Be sure -that the file is an executable or that it has executable permissions.''', - -'devel-file-in-non-devel-package', -'''A file that is needed only e.g. when developing or building software is -included in a non-devel package. These files should go in devel packages.''', - -'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 -included in your package.''', - -'spurious-executable-perm', -'''The file is installed with executable permissions, but was identified as one -that probably should not be executable. Verify if the executable bits are -desired, and remove if not.''', - -'world-writable', -'''A file or directory in the package is installed with world writable -permissions, which is most likely a security issue.''', - -'standard-dir-owned-by-package', -'''This package owns a directory that is part of the standard hierarchy, which -can lead to default directory permissions or ownerships being changed to -something non-standard.''', - -'no-dependency-on', -''' -''', - -'cross-directory-hard-link', -'''File is hard linked across directories. This can cause problems in -installations where the directories are located on different devices.''', - -'dangling-symlink', -'''The target of the symbolic link does not exist within this package or its -file based dependencies. Verify spelling of the link target and that the -target is included in a package in this package's dependency chain.''', - -'symlink-should-be-relative', -'''Absolute symlinks are problematic eg. when working with chroot environments. -symlinks(8) is a tool that can be useful for creating/dealing with relative -symlinks at package build time.''', - -'dangling-relative-symlink', -'''The target of the symbolic link does not exist within this package or its -file based dependencies. Verify spelling of the link target and that the -target is included in a package in this package's dependency chain.''', - -'symlink-has-too-many-up-segments', -''' -''', - -'symlink-should-be-absolute', -''' -''', - -'symlink-contains-up-and-down-segments', -''' -''', - -'non-readable', -'''The file can't be read by everybody. Review if this is expected.''', - -'incoherent-logrotate-file', -'''Your logrotate file should be named /etc/logrotate.d/<package name>.''', - -'non-root-user-log-file', -'''If you need log files owned by a non-root user, just create a subdir in -/var/log and put your log files in it.''', - -'non-root-group-log-file', -'''If you need log files owned by a non-root group, just create a subdir in -/var/log and put your log files in it.''', - -'non-ghost-file', -'''File should be tagged %ghost.''', - -'outside-libdir-files', -'''This library package must not contain non library files to allow 64 -and 32 bits versions of the package to coexist.''', - -'hidden-file-or-dir', -'''The file or directory is hidden. You should see if this is normal, -and delete it from the package if not.''', - -'module-without-depmod-postin', -'''This package contains a kernel module but provides no call to depmod in the -%post scriptlet.''', - -'postin-with-wrong-depmod', -'''This package contains a kernel module but its %post scriptlet calls depmod -for the wrong kernel.''', - -'module-without-depmod-postun', -'''This package contains a kernel module but provides no call to depmod in the -%postun scriptlet.''', - -'postun-with-wrong-depmod', -'''This package contains a kernel module but its %postun scriptlet calls depmod -for the wrong kernel.''', - -'log-files-without-logrotate', -'''This package contains files in /var/log/ without adding logrotate -configuration for them.''', - -'unexpanded-macro', -'''This package contains a file whose path contains something that looks like -an unexpanded macro; this is often the sign of a misspelling. Please check your -specfile.''', - -'manifest-in-perl-module', -'''This perl module package contains a MANIFEST or a MANIFEST.SKIP file -in the documentation directory.''', - -'siteperl-in-perl-module', -'''This perl module package installs files under the subdirectory site_perl, -while they must appear under vendor_perl.''', - -'executable-marked-as-config-file', -'''Executables must not be marked as config files because that may -prevent upgrades from working correctly. If you need to be able to -customize an executable, make it for example read a config file in -/etc/sysconfig.''', - -'sourced-script-with-shebang', -'''This text file contains a shebang, but is meant to be sourced, not -executed.''', - -'executable-sourced-script', -'''This text file has executable bit set, but is meant to be sourced, not -executed.''', - -'wrong-script-interpreter', -'''This script uses an interpreter which is either an inappropriate one -or located in an inappropriate directory for packaged system software. - -Alternatively, if the file should not be executed, then ensure that -it is not marked as executable. -''', - -'env-script-interpreter', -'''This script uses 'env' as an interpreter. -For the rpm runtime dependency detection to work, the shebang -#!/usr/bin/env python - -needs to be patched into -#!/usr/bin/python - -otherwise the package dependency generator merely adds a dependency -on /usr/bin/env rather than the actual interpreter /usr/bin/python. - -Alternatively, if the file should not be executed, then ensure that -it is not marked as executable or don't install it in a path that -is reserved for executables. -''', - -'non-executable-script', -'''This text file contains a shebang or is located in a path dedicated for -executables, but lacks the executable bits and cannot thus be executed. If -the file is meant to be an executable script, add the executable bits, -otherwise remove the shebang or move the file elsewhere.''', - -'script-without-shebang', -'''This text file has executable bits set or is located in a path dedicated -for executables, but lacks a shebang and cannot thus be executed. If the file -is meant to be an executable script, add the shebang, otherwise remove the -executable bits or move the file elsewhere.''', - -'wrong-script-end-of-line-encoding', -'''This script has wrong end-of-line encoding, usually caused by creation or -modification on a non-Unix system. It will prevent its execution.''', - -'wrong-file-end-of-line-encoding', -'''This file has wrong end-of-line encoding, usually caused by creation or -modification on a non-Unix system. It could prevent it from being displayed -correctly in some circumstances.''', - -'file-not-utf8', -'''The character encoding of this file is not UTF-8. Consider converting it -in the specfile's %prep section for example using iconv(1).''', - -'filename-not-utf8', -'''The character encoding of the name of this file is not UTF-8. -Rename it.''', - -'file-in-meta-package', -'''This package seems to be a meta-package (an empty package used to require -other packages), but it is not empty. You should remove or rename it, see the -option MetaPackageRegexp.''', - -'empty-debuginfo-package', -'''This debuginfo package contains no files. This is often a sign of binaries -being unexpectedly stripped too early during the build, rpmbuild not being able -to strip the binaries, the package actually being a noarch one but erratically -packaged as arch dependent, or something else. Verify what the case is, and -if there's no way to produce useful debuginfo out of it, disable creation of -the debuginfo package.''', - -'debuginfo-without-sources', -'''This debuginfo package appears to contain debug symbols but no source files. -This is often a sign of binaries being unexpectedly stripped too early during -the build, or being compiled without compiler debug flags (which again often -is a sign of distro's default compiler flags ignored which might have security -consequences), or other compiler flags which result in rpmbuild's debuginfo -extraction not working as expected. Verify that the binaries are not -unexpectedly stripped and that the intended compiler flags are used.''', - -'missing-dependency-to-cron', -'''This package installs a file in /etc/cron.*/ but -doesn't require cron to be installed. as cron is not part of the essential packages, -your package should explicitely require cron to make sure that your cron job is -executed. If it is an optional feature of your package, recommend or suggest cron.''', - -'missing-dependency-to-logrotate', -'''This package installs a file in /etc/logrotate.d/ but -doesn't require logrotate to be installed. Because logrotate is not part of the essential packages, -your package should explicitely depend on logrotate to make sure that your logrotate -job is executed. If it is an optional feature of your package, recommend or suggest logrotate.''', - -'missing-dependency-to-xinetd', -'''This package installs a file in /etc/xinetd.d/ but -doesn't require xinetd to be installed. Because xinetd is not part of the essential packages, -your package should explicitely depend on logrotate to make sure that your xinetd -job is executed. If it is an optional feature of your package, recommend or suggest xinetd.''', - -'read-error', -'''This file could not be read. A reason for this could be that the info about -it in the rpm header indicates that it is supposed to be a readable normal file -but it actually is not in the filesystem. Because of this, some checks will -be skipped.''', - -'inaccessible-filename', -'''An error occurred while trying to access this file due to some characters -in its name. Because of this, some checks will be skipped. Access could work -with some other locale settings.''', - -'executable-crontab-file', -'''This crontab file has executable bit set, which is refused by newer version -of cron''', - -'non-owner-writeable-only-crontab-file', -'''This crontab file is writeable by other users as its owner, which is refused -by newer version of cron and insecure''', - -'symlink-crontab-file', -'''This crontab file is a symbolic link, which is insecure and refused by newer -version of cron''', - -'rpath-in-buildconfig', -'''This build configuration file contains rpaths which will be introduced into -dependent packages.''', - -'python-bytecode-wrong-magic-value', -'''The "magic" ABI version embedded in this python bytecode file isn't equal -to that of the corresponding runtime, which will force the interpreter to -recompile the .py source every time, ignoring the saved bytecode.''', - -'python-bytecode-inconsistent-mtime', -'''The timestamp embedded in this python bytecode file isn't equal to the mtime -of the original source file, which will force the interpreter to recompile the -.py source every time, ignoring the saved bytecode.''', - -'python-bytecode-without-source', -'''This python bytecode file (.pyo/.pyc) is not accompanied by its original -source file (.py)''', - -'duplicate-executable', -'''This executable file exists in more than one standard binary directories. -It can cause problems when dirs in $PATH are reordered.''', - -'no-manual-page-for-binary', -'''Each executable in standard binary directories should have a man page.''', - -'manual-page-warning', -'''This man page may contain problems that can cause it not to be formatted -as intended.''', - -'incorrect-fsf-address', -'''The Free Software Foundation address in this file seems to be outdated or -misspelled. Ask upstream to update the address, or if this is a license file, -possibly the entire file with a new copy available from the FSF.''', - -'gzipped-svg-icon', -'''Not all desktop environments that support SVG icons support them gzipped -(.svgz). Install the icon as plain uncompressed SVG.''', - -'pem-certificate', -'''Shipping a PEM certificate is likely wrong. If used for the default -configuration, this is insecure ( since the certificate is public ). If this -is used for validation, ie a CA certificate store, then this must be kept up -to date due to CA compromise. The only valid reason is for testing purpose, -so ignore this warning if this is the case.''', - -'pem-private-key', -'''Private key in a .pem file should not be shipped in a rpm, unless -this is for testing purpose ( ie, run by the test suite ). Shipping it -as part of the example documentation mean that someone will sooner or later -use it and setup a insecure configuration.''' -) - -for i in disallowed_dirs: - addDetails('dir-or-file-in-%s' % '-'.join(i.split('/')[1:]), - '''A file in the package is located in %s. It's not permitted -for packages to install files in this directory.''' % i) - - -# FilesCheck.py ends here diff --git a/Filter.py b/Filter.py deleted file mode 100644 index a728115..0000000 --- a/Filter.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : Filter.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Sat Oct 23 15:52:27 1999 -# Purpose : filter the output of rpmlint to allow exceptions. -############################################################################# - -from __future__ import print_function - -import codecs -import locale -import sys -import textwrap - -import Config -try: - import Testing -except ImportError: - Testing = None - - -_rawout = None -_diagnostic = list() -_badness_score = 0 -printed_messages = {"I": 0, "W": 0, "E": 0} - -__preferred_encoding = locale.getpreferredencoding() -if sys.version_info[0] < 3: - __stdout = codecs.getwriter(__preferred_encoding)(sys.stdout, 'replace') - - def __print(s): - if isinstance(s, str): - s = s.decode(__preferred_encoding, 'replace') - print(s, file=__stdout) -else: - __stdout = codecs.getwriter(__preferred_encoding)( - sys.stdout.buffer, 'replace') - - def __print(s): - print(s, file=__stdout) - - -def printInfo(pkg, reason, *details): - _print("I", pkg, reason, details) - - -def printWarning(pkg, reason, *details): - _print("W", pkg, reason, details) - - -def printError(pkg, reason, *details): - _print("E", pkg, reason, details) - - -def _print(msgtype, pkg, reason, details): - global _badness_score - - threshold = badnessThreshold() - - badness = 0 - if threshold >= 0: - badness = Config.badness(reason) - # anything with badness is an error - if badness: - msgtype = 'E' - # errors without badness become warnings - elif msgtype == 'E': - msgtype = 'W' - - ln = "" - if pkg.current_linenum is not None: - ln = "%s:" % pkg.current_linenum - arch = "" - if pkg.arch is not None: - arch = ".%s" % pkg.arch - s = "%s%s:%s %s: %s" % (pkg.name, arch, ln, msgtype, reason) - if badness: - s = s + " (Badness: %d)" % badness - for d in details: - s = s + " %s" % d - if Testing and Testing.isTest(): - Testing.addOutput(s) - else: - if _rawout: - print(s.encode(locale.getpreferredencoding(), "replace"), - file=_rawout) - if not Config.isFiltered(s): - printed_messages[msgtype] += 1 - _badness_score += badness - if threshold >= 0: - _diagnostic.append(s + "\n") - else: - __print(s) - if Config.info: - printDescriptions(reason) - return True - - return False - - -def printDescriptions(reason): - try: - d = _details[reason] - if d and d != '' and d != "\n": - __print(textwrap.fill(d, 78)) - __print("") - except KeyError: - pass - - -def _diag_sortkey(x): - xs = x.split() - return (xs[2], xs[1]) - - -def printAllReasons(): - threshold = badnessThreshold() - if threshold < 0: - return False - - global _diagnostic - _diagnostic.sort(key=_diag_sortkey, reverse=True) - last_reason = '' - for diag in _diagnostic: - if Config.info: - reason = diag.split()[2] - if reason != last_reason: - if len(last_reason): - printDescriptions(last_reason) - last_reason = reason - __print(diag) - if Config.info and len(last_reason): - printDescriptions(last_reason) - _diagnostic = list() - return _badness_score > threshold - - -_details = {} - - -def addDetails(*details): - for idx in range(int(len(details) / 2)): - if not details[idx * 2] in _details: - _details[details[idx * 2]] = details[idx * 2 + 1] - - -def badnessScore(): - return _badness_score - - -def badnessThreshold(): - return Config.getOption("BadnessThreshold", -1) - - -def setRawOut(file): - global _rawout - if _rawout: - _rawout.close() - _rawout = open(file, "w") - -# Filter.py ends here diff --git a/INSTALL b/INSTALL deleted file mode 100644 index b879f67..0000000 --- a/INSTALL +++ /dev/null @@ -1,19 +0,0 @@ -You need the following utilities to run rpmlint: - - o python 2.6 or newer - o rpm 4.4.2.2 or newer and its python bindings - o libmagic 5.05 and its python bindings (optional) - o readelf - o cpio - o desktop-file-validate - o gzip, bzip2 and xz - o groff and gtbl (optional) - o enchant and its python bindings (optional) - o appstream-util, part of appstream-glib (optional) - -and the following programs to install it: - - o python 2.6 or newer - o rpm python bindings 4.4 or newer - o sed - o pytest and flake8 (for running the test suite) diff --git a/InitScriptCheck.py b/InitScriptCheck.py deleted file mode 100644 index 9df2b9a..0000000 --- a/InitScriptCheck.py +++ /dev/null @@ -1,277 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# Project : Mandriva Linux -# Module : rpmlint -# File : InitScriptCheck.py -# Author : Frederic Lepied -# Created On : Fri Aug 25 09:26:37 2000 -# Purpose : check init scripts (files in /etc/rc.d/init.d) -############################################################################# - -import os -import re - -import rpm - -import AbstractCheck -import Config -from Filter import addDetails, printError, printWarning -import Pkg - - -chkconfig_content_regex = re.compile(r'^\s*#\s*chkconfig:\s*([-0-9]+)\s+[-0-9]+\s+[-0-9]+') -subsys_regex = re.compile(r'/var/lock/subsys/([^/"\'\s;&|]+)', re.MULTILINE) -chkconfig_regex = re.compile(r'^[^#]*(chkconfig|add-service|del-service)', re.MULTILINE) -status_regex = re.compile(r'^[^#]*status', re.MULTILINE) -reload_regex = re.compile(r'^[^#]*reload', re.MULTILINE) -use_deflevels = Config.getOption('UseDefaultRunlevels', True) -lsb_tags_regex = re.compile(r'^# ([\w-]+):\s*(.*?)\s*$') -lsb_cont_regex = re.compile(r'^#(?:%s| )(.*?)\s*$' % "\t") -use_subsys = Config.getOption('UseVarLockSubsys', True) - -LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop', 'Should-Start', - 'Should-Stop', 'Default-Start', 'Default-Stop', - 'Short-Description', 'Description') -RECOMMENDED_LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop', - 'Default-Stop', 'Short-Description') - - -class InitScriptCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, 'InitScriptCheck') - - def check_binary(self, pkg): - initscript_list = [] - for fname, pkgfile in pkg.files().items(): - - if not fname.startswith('/etc/init.d/') and \ - not fname.startswith('/etc/rc.d/init.d/'): - continue - - basename = os.path.basename(fname) - initscript_list.append(basename) - if pkgfile.mode & 0o500 != 0o500: - printError(pkg, 'init-script-non-executable', fname) - - if "." in basename: - printError(pkg, 'init-script-name-with-dot', fname) - - # check chkconfig call in %post and %preun - 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[rpm.RPMTAG_PREUN] or \ - pkg.scriptprog(rpm.RPMTAG_PREUNPROG) - if not preun: - printError(pkg, 'init-script-without-chkconfig-preun', fname) - elif not chkconfig_regex.search(preun): - printError(pkg, 'preun-without-chkconfig', fname) - - status_found = False - reload_found = False - chkconfig_content_found = False - subsys_regex_found = False - in_lsb_tag = False - in_lsb_description = False - lastline = '' - lsb_tags = {} - # check common error in file content - content = None - try: - content = [x for x in Pkg.readlines(pkgfile.path)] - except Exception as e: - printWarning(pkg, 'read-error', e) - continue - content_str = "".join(content) - for line in content: - line = line[:-1] # chomp - # TODO check if there is only one line like this - if line.startswith('### BEGIN INIT INFO'): - in_lsb_tag = True - continue - if line.endswith('### END INIT INFO'): - in_lsb_tag = False - for kw, vals in lsb_tags.items(): - if len(vals) != 1: - printError(pkg, 'redundant-lsb-keyword', kw) - - for kw in RECOMMENDED_LSB_KEYWORDS: - if kw not in lsb_tags: - printWarning(pkg, 'missing-lsb-keyword', - "%s in %s" % (kw, fname)) - if in_lsb_tag: - # TODO maybe we do not have to handle this ? - if lastline.endswith('\\'): - line = lastline + line - else: - res = lsb_tags_regex.search(line) - if not res: - cres = lsb_cont_regex.search(line) - if not (in_lsb_description and cres): - in_lsb_description = False - printError( - pkg, 'malformed-line-in-lsb-comment-block', - line) - else: - lsb_tags["Description"][-1] += \ - " " + cres.group(1) - else: - tag = res.group(1) - if not tag.startswith('X-') and \ - tag not in LSB_KEYWORDS: - printError(pkg, 'unknown-lsb-keyword', line) - else: - in_lsb_description = (tag == 'Description') - if tag not in lsb_tags: - lsb_tags[tag] = [] - lsb_tags[tag].append(res.group(2)) - lastline = line - - if not status_found and status_regex.search(line): - status_found = True - - if not reload_found and reload_regex.search(line): - reload_found = True - - res = chkconfig_content_regex.search(line) - if res: - chkconfig_content_found = True - if use_deflevels: - if res.group(1) == '-': - printWarning(pkg, 'no-default-runlevel', fname) - elif res.group(1) != '-': - printWarning(pkg, 'service-default-enabled', fname) - - res = subsys_regex.search(line) - if res: - subsys_regex_found = True - name = res.group(1) - if use_subsys and name != basename: - error = True - if name[0] == '$': - value = Pkg.substitute_shell_vars(name, - content_str) - if value == basename: - error = False - else: - i = name.find('}') - if i != -1: - name = name[0:i] - error = name != basename - if error and len(name): - if name[0] == '$': - printWarning(pkg, 'incoherent-subsys', fname, - name) - else: - printError(pkg, 'incoherent-subsys', fname, - name) - - if "Default-Start" in lsb_tags: - if "".join(lsb_tags["Default-Start"]): - printWarning(pkg, 'service-default-enabled', fname) - - if not status_found: - printError(pkg, 'no-status-entry', fname) - if not reload_found: - printWarning(pkg, 'no-reload-entry', fname) - if not chkconfig_content_found: - printError(pkg, 'no-chkconfig-line', fname) - if not subsys_regex_found and use_subsys: - printError(pkg, 'subsys-not-used', fname) - elif subsys_regex_found and not use_subsys: - printError(pkg, 'subsys-unsupported', fname) - - if len(initscript_list) == 1: - pkgname = re.sub("-sysvinit$", "", pkg.name.lower()) - goodnames = (pkgname, pkgname + 'd') - if initscript_list[0] not in goodnames: - printWarning(pkg, 'incoherent-init-script-name', - initscript_list[0], str(goodnames)) - - -# Create an object to enable the auto registration of the test -check = InitScriptCheck() - -addDetails( -'init-script-without-chkconfig-postin', -'''The package contains an init script but doesn't contain a %post with -a call to chkconfig.''', - -'postin-without-chkconfig', -'''The package contains an init script but doesn't call chkconfig in its -%post script.''', - -'init-script-without-chkconfig-preun', -'''The package contains an init script but doesn't contain a %preun with -a call to chkconfig.''', - -'preun-without-chkconfig', -'''The package contains an init script but doesn't call chkconfig in its -%preun script.''', - -'missing-lsb-keyword', -'''The package contains an init script that does not contain one of the LSB -init script comment block convention keywords that are recommendable for all -init scripts. If there is nothing to add to a keyword's value, include the -keyword in the script with an empty value. Note that as of version 3.2, the -LSB specification does not mandate presence of any keywords.''', - -'no-status-entry', -'''In your init script (/etc/rc.d/init.d/your_file), you don't -have a 'status' entry, which is necessary for good functionality.''', - -'no-reload-entry', -'''In your init script (/etc/rc.d/init.d/your_file), you don't -have a 'reload' entry, which is necessary for good functionality.''', - -'no-chkconfig-line', -'''The init script doesn't contain a chkconfig line to specify the runlevels -at which to start and stop it.''', - -'no-default-runlevel', -'''The default runlevel isn't specified in the init script.''', - -'service-default-enabled', -'''The service is enabled by default after "chkconfig --add"; for security -reasons, most services should not be. Use "-" as the default runlevel in the -init script's "chkconfig:" line and/or remove the "Default-Start:" LSB keyword -to fix this if appropriate for this service.''', - -'subsys-unsupported', -'''The init script uses /var/lock/subsys which is not supported by -this distribution.''', - -'subsys-not-used', -'''While your daemon is running, you have to put a lock file in -/var/lock/subsys/. To see an example, look at this directory on your -machine and examine the corresponding init scripts.''', - -'incoherent-subsys', -'''The filename of your lock file in /var/lock/subsys/ is incoherent -with your actual init script name. For example, if your script name -is httpd, you have to use 'httpd' as the filename in your subsys directory. -It is also possible that rpmlint gets this wrong, especially if the init -script contains nontrivial shell variables and/or assignments. These -cases usually manifest themselves when rpmlint reports that the subsys name -starts a with '$'; in these cases a warning instead of an error is reported -and you should check the script manually.''', - -'incoherent-init-script-name', -'''The init script name should be the same as the package name in lower case, -or one with 'd' appended if it invokes a process by that name.''', - -'init-script-name-with-dot', -'''The init script name should not contain a dot in its name. Some versions -of chkconfig don't work as expected with init script names like that.''', - -'init-script-non-executable', -'''The init script should have at least the execution bit set for root -in order for it to run at boot time.''', -) - -# InitScriptCheck.py ends here diff --git a/LSBCheck.py b/LSBCheck.py deleted file mode 100644 index e29bb8b..0000000 --- a/LSBCheck.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# Project : Mandriva Linux -# Module : rpmlint -# File : LSBCheck.py -# Author : Frederic Lepied -# Created On : Tue Jan 30 14:44:37 2001 -# Purpose : LSB non compliance checks -############################################################################# - -import re - -import rpm - -import AbstractCheck -from Filter import addDetails, printError - - -version_regex = re.compile('^[a-zA-Z0-9.+]+$') -name_regex = re.compile('^[a-z0-9.+-]+$') - - -class LSBCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "LSBCheck") - - def check(self, pkg): - - name = pkg.name - if name and not name_regex.search(name): - printError(pkg, 'non-lsb-compliant-package-name', name) - - version = pkg[rpm.RPMTAG_VERSION] - if version and not version_regex.search(version): - printError(pkg, 'non-lsb-compliant-version', version) - - release = pkg[rpm.RPMTAG_RELEASE] - if release and not version_regex.search(release): - printError(pkg, 'non-lsb-compliant-release', release) - - -# Create an object to enable the auto registration of the test -check = LSBCheck() - -addDetails( -'non-lsb-compliant-package-name', -"""Your package name contains an illegal character. Use only -alphanumeric symbols in your package name.""", - -'non-lsb-compliant-version', -"""Your version number contains an illegal character. Use only -lowercase letters and/or numbers.""", - -'non-lsb-compliant-release', -"""Your version number contains an illegal character. Use only -lowercase letters and/or numbers.""", - -) - -# LSBCheck.py ends here diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..650c4eb --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include Makefile +include COPYING +include README.md +include conftest.py +recursive-include configs *.toml +recursive-include rpmlint *.toml +recursive-include test * @@ -1,70 +1,7 @@ -############################################################################# -# File : Makefile -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Mon Sep 30 13:20:18 1999 -# Purpose : rules to manage the files. -############################################################################# - -BINDIR=/usr/bin -LIBDIR=/usr/share/rpmlint -ETCDIR=/etc -MANDIR=/usr/share/man - -FILES = rpmlint *.py INSTALL README.md README.devel COPYING tools/*.py \ - Makefile config rpmdiff rpmdiff.1 rpmlint.bash-completion rpmlint.1 \ - test.sh test/*/*.rpm test/spec/*.spec test/*.py -GENERATED = __version__.py - -PACKAGE = rpmlint -PYTHON = /usr/bin/python - -# update this variable to create a new release -VERSION := 1.10 - -all: __version__.py __isocodes__.py +all: rpmlint/__isocodes__.py clean: - 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 *.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 compileall -d $(LIBDIR) $(DESTDIR)$(LIBDIR) - 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 \ - mkdir -p $(DESTDIR)$(ETCDIR)/bash_completion.d ; \ - cp -p rpmlint.bash-completion $(DESTDIR)$(ETCDIR)/bash_completion.d/rpmlint ; \ - else \ - mkdir -p $(DESTDIR)$$compdir ; \ - cp -p rpmlint.bash-completion $(DESTDIR)$$compdir/rpmlint ; \ - ln -sf rpmlint $(DESTDIR)$$compdir/rpmdiff ; \ - fi - cp -p rpmdiff.1 rpmlint.1 $(DESTDIR)$(MANDIR)/man1 + rm -f rpmlint/__isocodes__.py -verify: - pychecker --limit=100 [A-Z]*.py __*__.py - -check: - ./test.sh - -__version__.py: Makefile - echo "# Automatically generated, do not edit" > $@ - echo "__version__ = '$(VERSION)'" >> $@ - -__isocodes__.py: +rpmlint/__isocodes__.py: tools/generate-isocodes.py > $@ - -.PHONY: all clean install verify check - -# Makefile ends here diff --git a/MenuCheck.py b/MenuCheck.py deleted file mode 100644 index 004565d..0000000 --- a/MenuCheck.py +++ /dev/null @@ -1,454 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# Project : Mandriva Linux -# Module : rpmlint -# File : MenuCheck.py -# Author : Frederic Lepied -# Created On : Mon Mar 20 07:43:37 2000 -############################################################################# - -import re -import stat - -import rpm - -import AbstractCheck -import Config -from Filter import addDetails, printError, printInfo, printWarning -import Pkg - - -DEFAULT_VALID_SECTIONS = ( - 'Office/Accessories', - 'Office/Address Books', - 'Office/Communications/Fax', - 'Office/Communications/PDA', - 'Office/Communications/Phone', - 'Office/Communications/Other', - 'Office/Drawing', - 'Office/Graphs', - 'Office/Presentations', - 'Office/Publishing', - 'Office/Spreadsheets', - 'Office/Tasks Management', - 'Office/Time Management', - 'Office/Wordprocessors', - 'Office/Other', - 'Internet/Chat', - 'Internet/File Transfer', - 'Internet/Instant Messaging', - 'Internet/Mail', - 'Internet/News', - 'Internet/Remote Access', - 'Internet/Video Conference', - 'Internet/Web Browsers', - 'Internet/Web Editors', - 'Internet/Other', - 'Multimedia/Graphics', - 'Multimedia/Sound', - 'Multimedia/Video', - 'Multimedia/Other', - 'System/Archiving/Backup', - 'System/Archiving/CD Burning', - 'System/Archiving/Compression', - 'System/Archiving/Other', - 'System/Configuration/Boot and Init', - 'System/Configuration/GNOME', - 'System/Configuration/Hardware', - 'System/Configuration/KDE', - 'System/Configuration/Networking', - 'System/Configuration/Packaging', - 'System/Configuration/Printing', - 'System/Configuration/Users', - 'System/Configuration/Other', - 'System/File Tools', - 'System/Monitoring', - 'System/Session/Windowmanagers', - 'System/Terminals', - 'System/Text Tools', - 'System/Other', - 'More Applications/Accessibility', - 'More Applications/Communications', - 'More Applications/Databases', - 'More Applications/Development/Code Generators', - 'More Applications/Development/Development Environments', - 'More Applications/Development/Interpreters', - 'More Applications/Development/Tools', - 'More Applications/Development/Other', - 'More Applications/Documentation', - 'More Applications/Editors', - 'More Applications/Education/Economy', - 'More Applications/Education/Geography', - 'More Applications/Education/History', - 'More Applications/Education/Languages', - 'More Applications/Education/Literature', - 'More Applications/Education/Sciences', - 'More Applications/Education/Sports', - 'More Applications/Education/Other', - 'More Applications/Emulators', - 'More Applications/Finances', - 'More Applications/Games/Adventure', - 'More Applications/Games/Arcade', - 'More Applications/Games/Boards', - 'More Applications/Games/Cards', - 'More Applications/Games/Puzzles', - 'More Applications/Games/Sports', - 'More Applications/Games/Strategy', - 'More Applications/Games/Toys', - 'More Applications/Games/Other', - 'More Applications/Sciences/Artificial Intelligence', - 'More Applications/Sciences/Astronomy', - 'More Applications/Sciences/Biology', - 'More Applications/Sciences/Chemistry', - 'More Applications/Sciences/Computer Science', - 'More Applications/Sciences/Data visualization', - 'More Applications/Sciences/Electricity', - 'More Applications/Sciences/Geosciences', - 'More Applications/Sciences/Image Processing', - 'More Applications/Sciences/Mathematics', - 'More Applications/Sciences/Numerical Analysis', - 'More Applications/Sciences/Parallel Computing', - 'More Applications/Sciences/Physics', - 'More Applications/Sciences/Robotics', - 'More Applications/Sciences/Other', - 'More Applications/Other', -) - -DEFAULT_EXTRA_MENU_NEEDS = ( - 'gnome', - 'icewm', - 'kde', - 'wmaker', -) - -DEFAULT_ICON_PATH = ( - ('/usr/share/icons/', 'normal'), - ('/usr/share/icons/mini/', 'mini'), - ('/usr/share/icons/large/', 'large'), -) - -DEFAULT_LAUNCHERS = ( - ['(?:/usr/bin/)?kdesu', ('/usr/bin/kdesu', 'kdesu')], - ['(?:/usr/bin/)?launch_x11_clanapp', - ('/usr/bin/launch_x11_clanapp', 'clanlib', 'libclanlib0')], - ['(?:/usr/bin/)?soundwrapper', None], -) - -menu_file_regex = re.compile(r'^/usr/lib/menu/([^/]+)$') -old_menu_file_regex = re.compile(r'^/usr/share/(gnome/apps|applnk)/([^/]+)$') -package_regex = re.compile(r'\?package\((.*)\):') -needs_regex = re.compile(r'needs=(\"([^\"]+)\"|([^ %s\"]+))' % "\t") -section_regex = re.compile(r'section=(\"([^\"]+)\"|([^ %s\"]+))' % "\t") -title_regex = re.compile(r'[\"\s]title=(\"([^\"]+)\"|([^ %s\"]+))' % "\t") -longtitle_regex = re.compile(r'longtitle=(\"([^\"]+)\"|([^ %s\"]+))' % "\t") -command_regex = re.compile(r'command=(?:\"([^\"]+)\"|([^ %s\"]+))' % "\t") -icon_regex = re.compile(r'icon=\"?([^\" ]+)') -valid_sections = Config.getOption('ValidMenuSections', DEFAULT_VALID_SECTIONS) -update_menus_regex = re.compile(r'^[^#]*update-menus', re.MULTILINE) -standard_needs = Config.getOption('ExtraMenuNeeds', DEFAULT_EXTRA_MENU_NEEDS) -icon_paths = Config.getOption('IconPath', DEFAULT_ICON_PATH) -xpm_ext_regex = re.compile(r'/usr/share/icons/(mini/|large/).*\.xpm$') -icon_ext_regex = re.compile(Config.getOption('IconFilename', r'.*\.png$')) -version_regex = re.compile(r'([0-9.][0-9.]+)($|\s)') -launchers = Config.getOption('MenuLaunchers', DEFAULT_LAUNCHERS) -xdg_migrated_regex = re.compile(r'xdg=\"?([^\" ]+)') - -# compile regexps -for l in launchers: - l[0] = re.compile(l[0]) -del l - - -class MenuCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, 'MenuCheck') - - def check_binary(self, pkg): - files = pkg.files() - menus = [] - - for fname, pkgfile in files.items(): - # Check menu files - res = menu_file_regex.search(fname) - mode = pkgfile.mode - if res: - basename = res.group(1) - if not stat.S_ISREG(mode): - printError(pkg, 'non-file-in-menu-dir', fname) - else: - if basename != pkg.name: - printWarning(pkg, 'non-coherent-menu-filename', fname) - if mode & 0o444 != 0o444: - printError(pkg, 'non-readable-menu-file', fname) - if mode & 0o111: - printError(pkg, 'executable-menu-file', fname) - menus.append(fname) - else: - # Check old menus from KDE and GNOME - res = old_menu_file_regex.search(fname) - if res: - if stat.S_ISREG(mode): - printError(pkg, 'old-menu-entry', fname) - else: - # Check non transparent xpm files - res = xpm_ext_regex.search(fname) - if res: - if stat.S_ISREG(mode) and not pkg.grep('None",', fname): - printWarning(pkg, 'non-transparent-xpm', fname) - if fname.startswith('/usr/lib64/menu'): - printError(pkg, 'menu-in-wrong-dir', fname) - - if menus: - 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[rpm.RPMTAG_POSTUN] or \ - pkg.scriptprog(rpm.RPMTAG_POSTUNPROG) - if not postun: - printError(pkg, 'menu-without-postun') - elif not update_menus_regex.search(postun): - printError(pkg, 'postun-without-update-menus') - - directory = pkg.dirName() - for f in menus: - # remove comments and handle cpp continuation lines - cmd = Pkg.getstatusoutput(('/lib/cpp', directory + f), True)[1] - - for line in cmd.splitlines(): - if not line.startswith('?'): - continue - res = package_regex.search(line) - if res: - package = res.group(1) - if package != pkg.name: - printWarning(pkg, - 'incoherent-package-value-in-menu', - package, f) - else: - printInfo(pkg, 'unable-to-parse-menu-entry', line) - - command = True - res = command_regex.search(line) - if res: - command_line = (res.group(1) or res.group(2)).split() - command = command_line[0] - for launcher in launchers: - if not launcher[0].search(command): - continue - found = False - if launcher[1]: - found = '/bin/' + command_line[0] in files or \ - '/usr/bin/' + command_line[0] in files or \ - '/usr/X11R6/bin/' + command_line[0] \ - in files - if not found: - for l in launcher[1]: - if l in pkg.req_names(): - found = True - break - if not found: - printError(pkg, - 'use-of-launcher-in-menu-but-no-requires-on', - launcher[1][0]) - command = command_line[1] - break - - if command[0] == '/': - if command not in files: - printWarning( - pkg, 'menu-command-not-in-package', - command) - elif not ('/bin/' + command in files or - '/usr/bin/' + command in files or - '/usr/X11R6/bin/' + command in files): - printWarning(pkg, 'menu-command-not-in-package', - command) - else: - printWarning(pkg, 'missing-menu-command') - command = False - - res = longtitle_regex.search(line) - if res: - grp = res.groups() - title = grp[1] or grp[2] - if title[0] != title[0].upper(): - printWarning(pkg, 'menu-longtitle-not-capitalized', - title) - res = version_regex.search(title) - if res: - printWarning(pkg, 'version-in-menu-longtitle', - title) - else: - printError(pkg, 'no-longtitle-in-menu', f) - title = None - - res = title_regex.search(line) - if res: - grp = res.groups() - title = grp[1] or grp[2] - if title[0] != title[0].upper(): - printWarning(pkg, 'menu-title-not-capitalized', - title) - res = version_regex.search(title) - if res: - printWarning(pkg, 'version-in-menu-title', title) - if '/' in title: - printError(pkg, 'invalid-title', title) - else: - printError(pkg, 'no-title-in-menu', f) - title = None - - res = needs_regex.search(line) - if res: - grp = res.groups() - needs = (grp[1] or grp[2]).lower() - if needs in ('x11', 'text', 'wm'): - res = section_regex.search(line) - if res: - grp = res.groups() - section = grp[1] or grp[2] - # don't warn entries for sections - if command and section not in valid_sections: - printError(pkg, 'invalid-menu-section', - section, f) - else: - printInfo(pkg, 'unable-to-parse-menu-section', - line) - elif needs not in standard_needs: - printInfo(pkg, 'strange-needs', needs, f) - else: - printInfo(pkg, 'unable-to-parse-menu-needs', line) - - res = icon_regex.search(line) - if res: - icon = res.group(1) - if not icon_ext_regex.search(icon): - printWarning(pkg, 'invalid-menu-icon-type', icon) - if icon[0] == '/' and needs == 'x11': - printWarning(pkg, 'hardcoded-path-in-menu-icon', - icon) - else: - for path in icon_paths: - if (path[0] + icon) not in files: - printError( - pkg, path[1] + '-icon-not-in-package', - icon, f) - else: - printWarning(pkg, 'no-icon-in-menu', title) - - res = xdg_migrated_regex.search(line) - if res: - if not res.group(1).lower() == "true": - printError(pkg, 'non-xdg-migrated-menu') - else: - printError(pkg, 'non-xdg-migrated-menu') - - -# Create an object to enable the auto registration of the test -check = MenuCheck() - -addDetails( -'non-file-in-menu-dir', -'''/usr/lib/menu must not contain anything else than normal files.''', - -'non-coherent-menu-filename', -'''The menu file name should be /usr/lib/menu/<package>.''', - -'non-readable-menu-file', -'''The menu file isn't readable. Check the permissions.''', - -'old-menu-entry', -''' -''', - -'non-transparent-xpm', -'''xpm icon should be transparent for use in menus.''', - -'menu-without-postin', -'''A menu file exists in the package but no %post scriptlet is present to call -update-menus.''', - -'postin-without-update-menus', -'''A menu file exists in the package but its %post scriptlet doesn't call -update-menus.''', - -'menu-without-postun', -'''A menu file exists in the package but no %postun scriptlet is present to -call update-menus.''', - -'postun-without-update-menus', -'''A menu file exists in the package but its %postun scriptlet doesn't call -update-menus.''', - -'incoherent-package-value-in-menu', -'''The package field of the menu entry isn't the same as the package name.''', - -'use-of-launcher-in-menu-but-no-requires-on', -'''The menu command uses a launcher but there is no dependency in the package -that contains it.''', - -'menu-command-not-in-package', -'''The command used in the menu isn't included in the package.''', - -'menu-longtitle-not-capitalized', -'''The longtitle field of the menu doesn't start with a capital letter.''', - -'version-in-menu-longtitle', -'''The longtitle filed of the menu entry contains a version. This is bad -because it will be prone to error when the version of the package changes.''', - -'no-longtitle-in-menu', -'''The longtitle field isn't present in the menu entry.''', - -'menu-title-not-capitalized', -'''The title field of the menu entry doesn't start with a capital letter.''', - -'version-in-menu-title', -'''The title filed of the menu entry contains a version. This is bad -because it will be prone to error when the version of the package changes.''', - -'no-title-in-menu', -'''The title field isn't present in the menu entry.''', - -'invalid-menu-section', -'''The section field of the menu entry isn't standard.''', - -'unable-to-parse-menu-section', -'''rpmlint wasn't able to parse the menu section. Please report.''', - -'hardcoded-path-in-menu-icon', -'''The path of the icon is hardcoded in the menu entry. This prevent multiple -sizes of the icon from being found.''', - -'normal-icon-not-in-package', -'''The normal icon isn't present in the package.''', - -'mini-icon-not-in-package', -'''The mini icon isn't present in the package.''', - -'large-icon-not-in-package', -'''The large icon isn't present in the package.''', - -'no-icon-in-menu', -'''The menu entry doesn't contain an icon field.''', - -'invalid-title', -'''The menu title contains invalid characters like /.''', - -'missing-menu-command', -'''The menu file doesn't contain a command.''', - -'menu-in-wrong-directory', -'''The menu files must be under /usr/lib/menu.''', - -'non-xdg-migrated-menu', -'''The menu file has not been migrated to new XDG menu system.''', - -) - -# MenuCheck.py ends here diff --git a/MenuXDGCheck.py b/MenuXDGCheck.py deleted file mode 100644 index 5364a75..0000000 --- a/MenuXDGCheck.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# check xdg file format violation -# -# http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html -# - -import codecs -import os -try: - import ConfigParser as cfgparser -except ImportError: - import configparser as cfgparser - -import AbstractCheck -from Filter import addDetails, printError, printWarning -from Pkg import getstatusoutput, is_utf8 - -STANDARD_BIN_DIRS = ('/bin', '/sbin', '/usr/bin', '/usr/sbin') - - -class MenuXDGCheck(AbstractCheck.AbstractFilesCheck): - def __init__(self): - # desktop file need to be in $XDG_DATA_DIRS - # $ echo $XDG_DATA_DIRS/applications - # /var/lib/menu-xdg:/usr/share - AbstractCheck.AbstractFilesCheck.__init__( - self, "MenuXDGCheck", r'(?:/usr|/etc/opt|/opt/.*)/share/applications/.*\.desktop$') - - def parse_desktop_file(self, pkg, root, f, filename): - cfp = cfgparser.RawConfigParser() - try: - with codecs.open(f, encoding='utf-8') as inputf: - cfp.readfp(inputf, filename) - except cfgparser.DuplicateSectionError as e: - printError( - pkg, 'desktopfile-duplicate-section', filename, - '[%s]' % e.section) - except cfgparser.MissingSectionHeaderError: - printError( - pkg, 'desktopfile-missing-header', filename) - except cfgparser.Error as e: - # Only in Python >= 3.2 - if (hasattr(cfgparser, 'DuplicateOptionError') and - isinstance(e, cfgparser.DuplicateOptionError)): - printError( - pkg, 'desktopfile-duplicate-option', filename, - '[%s]/%s' % (e.section, e.option)) - else: - printWarning( - pkg, 'invalid-desktopfile', filename, - e.message.partition(':')[0]) - except UnicodeDecodeError as e: - printWarning( - pkg, 'invalid-desktopfile', filename, 'Unicode error: %s' % (e)) - else: - binary = None - if cfp.has_option('Desktop Entry', 'Exec'): - binary = cfp.get('Desktop Entry', 'Exec').partition(' ')[0] - if binary: - found = False - if binary.startswith('/'): - found = os.path.exists(root + binary) - else: - for i in STANDARD_BIN_DIRS: - if os.path.exists(root + i + '/' + binary): - # no need to check if the binary is +x, rpmlint does it - # in another place - found = True - break - if not found: - printWarning( - pkg, 'desktopfile-without-binary', filename, binary) - - def check_file(self, pkg, filename): - root = pkg.dirName() - f = root + filename - st = getstatusoutput(('desktop-file-validate', f), True) - if st[0]: - error_printed = False - for line in st[1].splitlines(): - if 'error: ' in line: - printError(pkg, 'invalid-desktopfile', filename, - line.split('error: ')[1]) - error_printed = True - if not error_printed: - printError(pkg, 'invalid-desktopfile', filename) - if not is_utf8(f): - printError(pkg, 'non-utf8-desktopfile', filename) - - self.parse_desktop_file(pkg, root, f, filename) - - -check = MenuXDGCheck() - -addDetails( -'invalid-desktopfile', -'''.desktop file is not valid, check with desktop-file-validate''', - -'non-utf8-desktopfile', -'''.desktop file is not encoded in UTF-8''', - -'desktopfile-without-binary', -'''the .desktop file is for a file not present in the package. You -should check the requires or see if this is not a error''', - -'desktopfile-duplicate-section', -'''The .desktop file contains the mentioned section name twice, which -can trigger parsing ambiguities. Remove the duplicate.''', - -'desktopfile-duplicate-option', -'''The .desktop file contains the mentioned option key twice, -which can trigger parsing ambiguities. Remove the duplicate.''', - -'desktopfile-missing-header', -'''The .desktop file should start with a section header.''', -) diff --git a/NamingPolicyCheck.py b/NamingPolicyCheck.py deleted file mode 100644 index 0837f83..0000000 --- a/NamingPolicyCheck.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# Project : Mandriva Linux -# Module : rpmlint -# File : NamingPolicyCheck.py -# Author : Michael Scherer -# Created On : Mon May 19 11:25:37 2003 -# Purpose : Check package names according to their content. -############################################################################# - -import re - -import AbstractCheck -from Filter import addDetails, printWarning - -# could be added. -# -# zope -# abiword2 -# alsaplayer-plugin-input -# emacs -# gstreamer -# nautilus -# vlc-plugin -# XFree -# xine - -simple_naming_policy_re = re.compile(r'\^[a-zA-Z1-9-_]*$') - - -class NamingPolicyNotAppliedException(Exception): - pass - - -class NamingPolicyCheck(AbstractCheck.AbstractCheck): - checks_ = [] - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "NamingPolicyCheck") - - def add_check(self, pkg_name, name_re, file_re): - c = {} - c['pkg_name'] = pkg_name - c['name_re'] = re.compile(name_re) - c['file_re'] = re.compile(file_re) - self.checks_.append(c) - - if simple_naming_policy_re.search(name_re): - details = "Its name should begin with " + name_re[1:] - else: - details = "Its name should match the regular expression " + name_re - - addDetails(pkg_name + '-naming-policy-not-applied', - "This package doesn't respect the naming policy for %s " - "packages.\n%s." % (pkg_name, details)) - - def check_binary(self, pkg): - files = pkg.files() - if not files: - return - try: - # check for files then - for c in self.checks_: - for f in files: - if c['file_re'].search(f) and \ - not c['name_re'].search(pkg.name): - raise NamingPolicyNotAppliedException - except NamingPolicyNotAppliedException: - printWarning(pkg, c['pkg_name'] + '-naming-policy-not-applied', f) - - -check = NamingPolicyCheck() - -# -# these are the check currently implemented. -# -# first argument is the name of the check, printed by the warning. -# ex : xmms. -# -# secund argument is the regular expression of the naming policy. -# ex: xmms plugin should be named xmms-name_of_plugin. -# -# third is the path of the file that should contains a package to be related to -# the naming scheme. -# ex: xmms plugin are put under /usr/lib/xmms/ -# -# the module is far from being perfect since you need to check this file for -# the naming file. -# if somone as a elegant solution, I will be happy to implement and test it. - -check.add_check('xmms', '^xmms(-|$)', '^/usr/lib(64)?/xmms/') -check.add_check('python', '^python(-|$)', '^/usr/lib(64)?/python[1-9](-[1-9])?') -check.add_check('perl5', '^perl(-|$)', '^/usr/lib(64)?/perl5/vendor_perl') -check.add_check('apache2', '^apache2-mod_', '^/usr/lib(64)?/apache2-') -check.add_check('fortune', '^fortune(-|$)', '^/usr/share/games/fortunes/') -check.add_check('php', '^php(-|$)', '/usr/lib(64)?/php/extensions/') -check.add_check('ruby', '^ruby(-|$)', '/usr/lib(64)?/ruby/[1-9](-[1-9])?/') -check.add_check('ocaml', '^ocaml(-|$)', '/usr/lib(64)?/ocaml/') - -# these exception should be added -# apache2 => apache2-devel -# apache2-modules -# ruby => apache2-mod_ruby -# ruby - -# NamingPolicyCheck.py ends here diff --git a/PamCheck.py b/PamCheck.py deleted file mode 100644 index 0aa586c..0000000 --- a/PamCheck.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# Project : Mandriva Linux -# Module : rpmlint -# File : PamCheck.py -# Author : Michael Scherer -# Created On : 31/01/2006 -# Purpose : Apply pam policy -############################################################################# - -import re - -import AbstractCheck -from Filter import addDetails, printError - - -pam_stack_re = re.compile(r'^\s*[^#].*pam_stack\.so\s*service') - - -class PamCheck(AbstractCheck.AbstractFilesCheck): - def __init__(self): - AbstractCheck.AbstractFilesCheck.__init__(self, "PamCheck", - r"/etc/pam\.d/.*") - - def check_file(self, pkg, filename): - lines = pkg.grep(pam_stack_re, filename) - if lines: - printError(pkg, 'use-old-pam-stack', filename, - '(line %s)' % ", ".join(lines)) - - -check = PamCheck() - -addDetails( -'use-old-pam-stack', -'''Update pam file to use include instead of pam_stack.''', -) @@ -1,1037 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : Pkg.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Tue Sep 28 07:18:06 1999 -# Purpose : provide an API to handle a rpm package either by accessing -# the rpm file or by accessing the files contained inside. -############################################################################# - -from __future__ import print_function - -import os -import re -import stat -import subprocess -import sys -import tempfile -try: - from urlparse import urljoin -except ImportError: - from urllib.parse import urljoin - -try: - import magic - # TODO: magic.MAGIC_COMPRESS when PkgFile gets decompress support. - _magic = magic.open(magic.MAGIC_NONE) - _ = _magic.descriptor # magic >= 5.05 needed - _magic.load() -except ImportError: - _magic = None -import rpm - -import Filter - -# utilities - -# b2s(): bytes to str -if sys.version_info[0] > 2: - long = int - unicode = str - - def b2s(b): - if b is None or isinstance(b, str): - return b - if isinstance(b, (list, tuple)): - return [b2s(x) for x in b] - return b.decode(errors='replace') -else: - def b2s(b): - return b - -try: - from shlex import quote as shquote -except ImportError: - def shquote(s): - return '"%s"' % s - - -def warn(s): - """ - Print warning message to stderr. - """ - print(s, file=sys.stderr) - - -# 64: RPMSENSE_PREREQ is 0 with rpm 4.4..4.7, we want 64 here in order -# to do the right thing with those versions and packages built with other -# rpm versions -PREREQ_FLAG = (rpm.RPMSENSE_PREREQ or 64) | rpm.RPMSENSE_SCRIPT_PRE | \ - rpm.RPMSENSE_SCRIPT_POST | 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'), - # file triggers: rpm >= 4.12.90 - (getattr(rpm, "RPMTAG_FILETRIGGERSCRIPTS", 5066), - getattr(rpm, "RPMTAG_FILETRIGGERSCRIPTPROG", 5067), - '%filetrigger'), - (getattr(rpm, "RPMTAG_TRANSFILETRIGGERSCRIPTS", 5076), - getattr(rpm, "RPMTAG_TRANSFILETRIGGERSCRIPTPROG", 5077), - '%transfiletrigger'), -] - -RPM_SCRIPTLETS = ('pre', 'post', 'preun', 'postun', 'pretrans', 'posttrans', - 'trigger', 'triggerin', 'triggerprein', 'triggerun', - 'triggerpostun', 'verifyscript', 'filetriggerin', - 'filetrigger', 'filetriggerun', 'filetriggerpostun', - 'transfiletriggerin', 'transfiletrigger', - 'transfiletriggerun', 'transfiletriggerun', - 'transfiletriggerpostun') - -var_regex = re.compile(r'^(.*)\${?(\w+)}?(.*)$') - - -def shell_var_value(var, script): - assign_regex = re.compile(r'\b' + re.escape(var) + - r'\s*=\s*(.+)\s*(#.*)*$', re.MULTILINE) - res = assign_regex.search(script) - if res: - res2 = var_regex.search(res.group(1)) - if res2: - if res2.group(2) == var: # infinite loop - return None - return substitute_shell_vars(res.group(1), script) - else: - return None - - -def substitute_shell_vars(val, script): - res = var_regex.search(val) - if res: - value = shell_var_value(res.group(2), script) - if not value: - value = '' - return res.group(1) + value + \ - substitute_shell_vars(res.group(3), script) - else: - return val - - -def getstatusoutput(cmd, stdoutonly=False, shell=False, raw=False, lc_all="C"): - """ - A version of commands.getstatusoutput() which can take cmd as a - sequence, thus making it potentially more secure. - """ - env = dict(os.environ, LC_ALL=lc_all) - if stdoutonly: - proc = subprocess.Popen(cmd, shell=shell, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, close_fds=True, env=env) - else: - proc = subprocess.Popen(cmd, shell=shell, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, env=env, - stderr=subprocess.STDOUT, close_fds=True) - proc.stdin.close() - 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 - return sts, text - - -bz2_regex = re.compile(r'\.t?bz2?$') -xz_regex = re.compile(r'\.(t[xl]z|xz|lzma)$') - - -def catcmd(fname): - """Get a 'cat' command that handles possibly compressed files.""" - cat = 'gzip -dcf' - if bz2_regex.search(fname): - cat = 'bzip2 -dcf' - elif xz_regex.search(fname): - cat = 'xz -dc' - return cat - - -def is_utf8(fname): - (sts, output) = getstatusoutput(catcmd(fname).split() + [fname], raw=True) - return not sts and is_utf8_bytestr(output) - - -def is_utf8_bytestr(s): - try: - s.decode('UTF-8') - except UnicodeError: - return False - return True - - -def to_unicode(string): - if string is None: - return unicode('') - elif isinstance(string, unicode): - return string - for enc in ('utf-8', 'iso-8859-1', 'iso-8859-15', 'iso-8859-2'): - try: - x = unicode(string, enc) - except UnicodeError: - pass - else: - if x.encode(enc) == string: - return x - return unicode(string, "ascii", errors="replace") - - -def readlines(path): - with open(path, 'rb') as fobj: - for line in fobj: - yield b2s(line) - - -def mktemp(): - tmpfd, tmpname = tempfile.mkstemp(prefix='rpmlint.') - tmpfile = os.fdopen(tmpfd, 'w') - return tmpfile, tmpname - - -slash_regex = re.compile(r'/+') -slashdot_regex = re.compile(r'/(\.(/|$))+') -slashend_regex = re.compile(r'([^/])/+$') - - -def safe_normpath(path): - """ - Like os.path.normpath but normalizes less aggressively thus being - potentially safer for paths containing symlinks. - """ - ret = slash_regex.sub('/', path) - ret = slashdot_regex.sub('\\2', ret) - ret = slashend_regex.sub('\\1', ret) - return ret - - -def get_default_valid_rpmgroups(filename=None): - """ - Get default rpm groups from filename, or try to look them up from - the rpm package (if installed) if no filename is given. - """ - groups = [] - if not filename: - try: - with InstalledPkg("rpm") as p: - groupsfiles = [x for x in p.files() if x.endswith('/GROUPS')] - if groupsfiles: - filename = groupsfiles[0] - except KeyError: # the rpm package might not be installed - pass - if filename and os.path.exists(filename): - with open(filename) as fobj: - groups = fobj.read().strip().splitlines() - if 'Development/Debug' not in groups: - groups.append('Development/Debug') - if 'Unspecified' not in groups: # auto-added by rpm >= 4.6.0 - groups.append('Unspecified') - groups.sort() - return groups - - -# from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications -def compareEVR(evr1, evr2): - (e1, v1, r1) = evr1 - (e2, v2, r2) = evr2 - # return 1: a is newer than b - # 0: a and b are the same version - # -1: b is newer than a - # rpmlint mod: don't stringify None epochs to 'None' strings - if e1 is not None: - e1 = str(e1) - v1 = str(v1) - r1 = str(r1) - if e2 is not None: - e2 = str(e2) - v2 = str(v2) - r2 = str(r2) - rc = rpm.labelCompare((e1, v1, r1), (e2, v2, r2)) - return rc - - -# from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications -def rangeCompare(reqtuple, provtuple): - """returns true if provtuple satisfies reqtuple""" - (reqn, reqf, (reqe, reqv, reqr)) = reqtuple - (n, f, (e, v, r)) = provtuple - if reqn != n: - return 0 - - # unversioned satisfies everything - if not f or not reqf: - return 1 - - # and you thought we were done having fun - # if the requested release is left out then we have - # to remove release from the package prco to make sure the match - # is a success - ie: if the request is EQ foo 1:3.0.0 and we have - # foo 1:3.0.0-15 then we have to drop the 15 so we can match - if reqr is None: - r = None - # rpmlint mod: don't mess with provided Epoch, doing so breaks e.g. - # "Requires: foo < 1.0" should not be satisfied by "Provides: foo = 1:0.5" - # if reqe is None: - # e = None - if reqv is None: # just for the record if ver is None then we're going to segfault - v = None - - # if we just require foo-version, then foo-version-* will match - if r is None: - reqr = None - - rc = compareEVR((e, v, r), (reqe, reqv, reqr)) - - # does not match unless - if rc >= 1: - if reqf in ['GT', 'GE', 4, 12]: - return 1 - if reqf in ['EQ', 8]: - if f in ['LE', 10, 'LT', 2]: - return 1 - if reqf in ['LE', 'LT', 'EQ', 10, 2, 8]: - if f in ['LE', 'LT', 10, 2]: - return 1 - - if rc == 0: - if reqf in ['GT', 4]: - if f in ['GT', 'GE', 4, 12]: - return 1 - if reqf in ['GE', 12]: - if f in ['GT', 'GE', 'EQ', 'LE', 4, 12, 8, 10]: - return 1 - if reqf in ['EQ', 8]: - if f in ['EQ', 'GE', 'LE', 8, 12, 10]: - return 1 - if reqf in ['LE', 10]: - if f in ['EQ', 'LE', 'LT', 'GE', 8, 10, 2, 12]: - return 1 - if reqf in ['LT', 2]: - if f in ['LE', 'LT', 10, 2]: - return 1 - if rc <= -1: - if reqf in ['GT', 'GE', 'EQ', 4, 12, 8]: - if f in ['GT', 'GE', 4, 12]: - return 1 - if reqf in ['LE', 'LT', 10, 2]: - return 1 -# if rc >= 1: -# if reqf in ['GT', 'GE', 4, 12]: -# return 1 -# if rc == 0: -# if reqf in ['GE', 'LE', 'EQ', 8, 10, 12]: -# return 1 -# if rc <= -1: -# if reqf in ['LT', 'LE', 2, 10]: -# return 1 - - return 0 - - -# from yum 3.2.23, rpmUtils.miscutils, with rpmlint modifications -def formatRequire(name, flags, evr): - s = name - - if flags: - if flags & (rpm.RPMSENSE_LESS | rpm.RPMSENSE_GREATER | - rpm.RPMSENSE_EQUAL): - s = s + " " - if flags & rpm.RPMSENSE_LESS: - s = s + "<" - if flags & rpm.RPMSENSE_GREATER: - s = s + ">" - if flags & rpm.RPMSENSE_EQUAL: - s = s + "=" - s = "%s %s" % (s, versionToString(evr)) - return s - - -def versionToString(evr): - if not isinstance(evr, (list, tuple)): - # assume string - return evr - ret = "" - if evr[0] is not None and evr[0] != "": - ret += str(evr[0]) + ":" - if evr[1] is not None: - ret += evr[1] - if evr[2] is not None and evr[2] != "": - ret += "-" + evr[2] - return ret - - -# from yum 3.2.23, rpmUtils.miscutils, with some rpmlint modifications -def stringToVersion(verstring): - if verstring in (None, ''): - return (None, None, None) - epoch = None - i = verstring.find(':') - if i != -1: - try: - epoch = str(long(verstring[:i])) - except ValueError: - # garbage in epoch, ignore it - pass - i += 1 - j = verstring.find('-', i) - if j != -1: - if verstring[i:j] == '': - version = None - else: - version = verstring[i:j] - release = verstring[j + 1:] - else: - if verstring[i:] == '': - version = None - else: - version = verstring[i:] - release = None - return (epoch, version, release) - - -def parse_deps(line): - """ - Parse provides/requires/conflicts/obsoletes line to list of - (name, flags, (epoch, version, release)) tuples. - """ - - prcos = [] - tokens = re.split(r'[\s,]+', line.strip()) - - # Drop line continuation backslash in multiline macro definition (for - # spec file parsing), e.g. - # [...] \ - # Obsoletes: foo-%1 <= 1.0.0 \ - # [...] \ - # (yes, this is an ugly hack and we probably have other problems with - # multiline macro definitions elsewhere...) - if tokens[-1] == '\\': - del tokens[-1] - - prco = [] - while tokens: - token = tokens.pop(0) - if not token: - # skip empty tokens - continue - - plen = len(prco) - - if plen == 0: - prco.append(token) - - elif plen == 1: - flags = 0 - if token[0] in ("=", "<", "<=", ">", ">="): - # versioned, flags - if "=" in token: - flags |= rpm.RPMSENSE_EQUAL - if "<" in token: - flags |= rpm.RPMSENSE_LESS - if ">" in token: - flags |= rpm.RPMSENSE_GREATER - prco.append(flags) - else: - # no flags following name, treat as unversioned, add and reset - prco.extend((flags, (None, None, None))) - prcos.append(tuple(prco)) - prco = [token] - - elif plen == 2: - # last token of versioned one, add and reset - prco.append(stringToVersion(token)) - prcos.append(tuple(prco)) - prco = [] - - plen = len(prco) - if plen: - if plen == 1: - prco.extend((0, (None, None, None))) - elif plen == 2: - prco.append((None, None, None)) - prcos.append(tuple(prco)) - - return prcos - - -# classes representing package - -class AbstractPkg(object): - - 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(r'\([^)]+\s+compressed\s+data\b') - - def __init__(self, filename, dirname, header=None, is_source=False): - self.filename = filename - self.extracted = False - self.dirname = dirname - self.current_linenum = None - self._config_files = None - self._doc_files = None - self._noreplace_files = None - self._ghost_files = None - self._missingok_files = None - self._files = None - self._requires = None - self._req_names = -1 - - if header: - self.header = header - self.is_source = is_source - else: - # Create a package object from the file name - ts = rpm.TransactionSet() - # Don't check signatures here... - ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES) - fd = os.open(filename, os.O_RDONLY) - try: - self.header = ts.hdrFromFdno(fd) - finally: - os.close(fd) - self.is_source = not self.header[rpm.RPMTAG_SOURCERPM] - - self.name = self[rpm.RPMTAG_NAME] - if self.isNoSource(): - self.arch = 'nosrc' - elif self.isSource(): - self.arch = 'src' - else: - self.arch = self.header.format("%{ARCH}") - - # Return true if the package is a source package - def isSource(self): - return self.is_source - - # Return true if the package is a nosource package. - # NoSource files are ghosts in source packages. - def isNoSource(self): - return self.is_source and self.ghostFiles() - - # access the tags like an array - def __getitem__(self, key): - try: - val = self.header[key] - except KeyError: - val = [] - if val == []: - return None - else: - # 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, - rpm.RPMTAG_DISTRIBUTION, rpm.RPMTAG_VENDOR) \ - 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 - - # return the name of the directory where the package is extracted - def dirName(self): - if not self.extracted: - self._extract() - return self.dirname - - # extract rpm contents - def _extract(self): - if not os.path.isdir(self.dirname): - warn('Unable to access dir %s' % self.dirname) - return None - else: - self.dirname = tempfile.mkdtemp( - prefix='rpmlint.%s.' % os.path.basename(self.filename), - dir=self.dirname) - # TODO: sequence based command invocation - # TODO: warn some way if this fails (e.g. rpm2cpio not installed) - command_str = \ - 'rpm2cpio %(f)s | (cd %(d)s; cpio -id); chmod -R +rX %(d)s' % \ - {'f': shquote(self.filename), 'd': shquote(self.dirname)} - cmd = getstatusoutput(command_str, shell=True) - self.extracted = True - return cmd - - def checkSignature(self): - return getstatusoutput(('rpm', '-K', self.filename)) - - # remove the extracted files from the package - def cleanup(self): - if self.extracted and self.dirname: - getstatusoutput(('rm', '-rf', self.dirname)) - - def grep(self, regex, filename): - """Grep regex from a file, return matching line numbers.""" - ret = [] - lineno = 0 - try: - with open(os.path.join( - self.dirName() or '/', filename.lstrip('/'))) as in_file: - for line in in_file: - lineno += 1 - if regex.search(line): - ret.append(str(lineno)) - break - except Exception as e: - Filter.printWarning(self, 'read-error', filename, e) - return ret - - def langtag(self, tag, lang): - """Get value of tag in the given language.""" - # LANGUAGE trumps other env vars per GNU gettext docs, see also #166 - orig = os.environ.get('LANGUAGE') - os.environ['LANGUAGE'] = lang - ret = self[tag] - if orig is not None: - os.environ['LANGUAGE'] = orig - return ret - - # return the associative array indexed on file names with - # the values as: (file perm, file owner, file group, file link to) - def files(self): - if self._files is not None: - return self._files - - self._gatherFilesInfo() - return self._files - - # return the list of config files - def configFiles(self): - if self._config_files is not None: - return self._config_files - - self._config_files = [x.name for x in self.files().values() - if x.is_config] - return self._config_files - - # return the list of noreplace files - def noreplaceFiles(self): - if self._noreplace_files is not None: - return self._noreplace_files - - self._noreplace_files = [x.name for x in self.files().values() - if x.is_noreplace] - return self._noreplace_files - - # return the list of documentation files - def docFiles(self): - if self._doc_files is not None: - return self._doc_files - - self._doc_files = [x.name for x in self.files().values() if x.is_doc] - return self._doc_files - - # return the list of ghost files - def ghostFiles(self): - if self._ghost_files is not None: - return self._ghost_files - - self._ghost_files = [x.name for x in self.files().values() - if x.is_ghost] - return self._ghost_files - - def missingOkFiles(self): - if self._missingok_files is not None: - return self._missingok_files - - self._missingok_files = [x.name for x in self.files().values() - if x.is_missingok] - return self._missingok_files - - # extract information about the files - def _gatherFilesInfo(self): - - self._files = {} - flags = self.header[rpm.RPMTAG_FILEFLAGS] - modes = self.header[rpm.RPMTAG_FILEMODES] - users = self.header[rpm.RPMTAG_FILEUSERNAME] - groups = self.header[rpm.RPMTAG_FILEGROUPNAME] - links = [b2s(x) for x in self.header[rpm.RPMTAG_FILELINKTOS]] - sizes = self.header[rpm.RPMTAG_FILESIZES] - md5s = self.header[rpm.RPMTAG_FILEMD5S] - mtimes = self.header[rpm.RPMTAG_FILEMTIMES] - rdevs = self.header[rpm.RPMTAG_FILERDEVS] - langs = self.header[rpm.RPMTAG_FILELANGS] - inodes = self.header[rpm.RPMTAG_FILEINODES] - requires = [b2s(x) for x in self.header[rpm.RPMTAG_FILEREQUIRE]] - provides = [b2s(x) for x in self.header[rpm.RPMTAG_FILEPROVIDE]] - files = [b2s(x) for x in self.header[rpm.RPMTAG_FILENAMES]] - magics = [b2s(x) for x in self.header[rpm.RPMTAG_FILECLASS]] - try: # rpm >= 4.7.0 - filecaps = self.header[rpm.RPMTAG_FILECAPS] - except AttributeError: - filecaps = None - - # rpm-python < 4.6 does not return a list for this (or FILEDEVICES, - # FWIW) for packages containing exactly one file - if not isinstance(inodes, list): - inodes = [inodes] - - if files: - for idx in range(0, len(files)): - pkgfile = PkgFile(files[idx]) - pkgfile.path = os.path.normpath(os.path.join( - self.dirName() or '/', pkgfile.name.lstrip('/'))) - pkgfile.flags = flags[idx] - pkgfile.mode = modes[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.mtime = mtimes[idx] - pkgfile.rdev = rdevs[idx] - pkgfile.inode = inodes[idx] - pkgfile.requires = parse_deps(requires[idx]) - pkgfile.provides = parse_deps(provides[idx]) - pkgfile.lang = b2s(langs[idx]) - pkgfile.magic = magics[idx] - if not pkgfile.magic: - if stat.S_ISDIR(pkgfile.mode): - pkgfile.magic = 'directory' - elif stat.S_ISLNK(pkgfile.mode): - pkgfile.magic = "symbolic link to `%s'" % pkgfile.linkto - elif not pkgfile.size: - pkgfile.magic = 'empty' - if (not pkgfile.magic and - not pkgfile.is_ghost and _magic): - # file() method evaluates every file twice with python2, - # use descriptor() method instead - try: - fd = os.open(pkgfile.path, os.O_RDONLY) - pkgfile.magic = b2s(_magic.descriptor(fd)) - os.close(fd) - except OSError: - pass - if pkgfile.magic is None: - pkgfile.magic = '' - elif Pkg._magic_from_compressed_re.search(pkgfile.magic): - # Discard magic from inside compressed files ("file -z") - # until PkgFile gets decompression support. We may get - # such magic strings from package headers already now; - # for example Fedora's rpmbuild as of F-11's 4.7.1 is - # patched so it generates them. - pkgfile.magic = '' - if filecaps: - pkgfile.filecaps = filecaps[idx] - self._files[pkgfile.name] = pkgfile - - def readlink(self, pkgfile): - """ - Resolve symlinks for the given PkgFile, return the dereferenced - PkgFile if it is found in this package, None if not. - """ - result = pkgfile - while result and result.linkto: - linkpath = urljoin(result.name, result.linkto) - linkpath = safe_normpath(linkpath) - result = self.files().get(linkpath) - return result - - # API to access dependency information - def obsoletes(self): - """ - Get package Obsoletes as list of - (name, flags, (epoch, version, release)) tuples. - """ - self._gatherDepInfo() - return self._obsoletes - - def requires(self): - """ - Get package Requires as list of - (name, flags, (epoch, version, release)) tuples. - """ - self._gatherDepInfo() - return self._requires - - def prereq(self): - """ - Get package PreReqs as list of - (name, flags, (epoch, version, release)) tuples. - """ - self._gatherDepInfo() - return self._prereq - - def req_names(self): - if self._req_names == -1: - self._req_names = [x[0] for x in self.requires() + self.prereq()] - return self._req_names - - def check_versioned_dep(self, name, version): - # try to match name%_isa as well (e.g. "foo(x86-64)", "foo(x86-32)") - name_re = re.compile(r'^%s(\(\w+-\d+\))?$' % re.escape(name)) - for d in self.requires() + self.prereq(): - if name_re.match(d[0]): - if d[1] & rpm.RPMSENSE_EQUAL != rpm.RPMSENSE_EQUAL \ - or d[2][1] != version: - return False - return True - return False - - def conflicts(self): - """ - Get package Conflicts as list of - (name, flags, (epoch, version, release)) tuples. - """ - self._gatherDepInfo() - return self._conflicts - - def provides(self): - """ - Get package Provides as list of - (name, flags, (epoch, version, release)) tuples. - """ - 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): - names = header[nametag] - flags = header[flagstag] - versions = header[versiontag] - - if versions: - for loop in range(len(versions)): - name = b2s(names[loop]) - evr = stringToVersion(b2s(versions[loop])) - if prereq is not None and flags[loop] & PREREQ_FLAG: - prereq.append((name, flags[loop] & (~PREREQ_FLAG), evr)) - else: - list.append((name, flags[loop], evr)) - - def _gatherDepInfo(self): - if self._requires is None: - self._requires = [] - self._prereq = [] - 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_REQUIREFLAGS, - rpm.RPMTAG_REQUIREVERSION, - self._prereq) - self._gather_aux(self.header, self._conflicts, - rpm.RPMTAG_CONFLICTNAME, - rpm.RPMTAG_CONFLICTFLAGS, - rpm.RPMTAG_CONFLICTVERSION) - self._gather_aux(self.header, self._provides, - rpm.RPMTAG_PROVIDENAME, - rpm.RPMTAG_PROVIDEFLAGS, - rpm.RPMTAG_PROVIDEVERSION) - self._gather_aux(self.header, self._obsoletes, - 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. - Depending on rpm-python version, the string may or may not include - interpreter arguments, if any. - """ - if which is None: - return '' - prog = self[which] - if prog is None: - prog = '' - elif isinstance(prog, (list, tuple)): - # http://rpm.org/ticket/847#comment:2 - prog = "".join(prog) - return prog - - -def getInstalledPkgs(name): - """Get list of installed package objects by name.""" - - pkgs = [] - ts = rpm.TransactionSet() - if re.search(r'[?*]|\[.+\]', name): - mi = ts.dbMatch() - mi.pattern("name", rpm.RPMMIRE_GLOB, name) - else: - mi = ts.dbMatch("name", name) - - for hdr in mi: - pkgs.append(InstalledPkg(name, hdr)) - - return pkgs - - -# Class to provide an API to an installed package -class InstalledPkg(Pkg): - def __init__(self, name, hdr=None): - if not hdr: - ts = rpm.TransactionSet() - mi = ts.dbMatch('name', name) - if not mi: - raise KeyError(name) - try: - hdr = next(mi) - except StopIteration: - raise KeyError(name) - - Pkg.__init__(self, name, '/', hdr) - - self.extracted = True - # create a fake filename to satisfy some checks on the filename - self.filename = '%s-%s-%s.%s.rpm' % \ - (self.name, self[rpm.RPMTAG_VERSION], self[rpm.RPMTAG_RELEASE], - self[rpm.RPMTAG_ARCH]) - - def cleanup(self): - pass - - def checkSignature(self): - return (0, 'fake: pgp md5 OK') - - -# Class to provide an API to a "fake" package, eg. for specfile-only checks -class FakePkg(AbstractPkg): - def __init__(self, name): - self.name = name - self.arch = None - self.current_linenum = None - self.dirname = None - - def dirName(self): - if not self.dirname: - self.dirname = tempfile.mkdtemp( - prefix='rpmlint.%s.' % os.path.basename(self.name)) - return self.dirname - - def cleanup(self): - if self.dirname: - getstatusoutput(('rm', '-rf', self.dirname)) - - -# Class for files in packages -class PkgFile(object): - - def __init__(self, name): - self.name = name - # Real path to the file (taking extract dir into account) - self.path = name - self.flags = 0 - self.mode = 0 - self.user = None - self.group = None - self.linkto = '' - self.size = None - self.md5 = None - self.mtime = 0 - self.rdev = '' - self.inode = 0 - self.requires = [] - self.provides = [] - self.lang = '' - self.magic = '' - self.filecaps = None - - # TODO: decompression support - - is_config = property(lambda self: self.flags & rpm.RPMFILE_CONFIG) - is_doc = property(lambda self: self.flags & rpm.RPMFILE_DOC) - is_noreplace = property(lambda self: self.flags & rpm.RPMFILE_NOREPLACE) - is_ghost = property(lambda self: self.flags & rpm.RPMFILE_GHOST) - is_missingok = property(lambda self: self.flags & rpm.RPMFILE_MISSINGOK) - - -if __name__ == '__main__': - for p in sys.argv[1:]: - 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 diff --git a/PostCheck.py b/PostCheck.py deleted file mode 100644 index 456ebff..0000000 --- a/PostCheck.py +++ /dev/null @@ -1,234 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# Project : Mandriva Linux -# Module : rpmlint -# File : PostCheck.py -# Author : Frederic Lepied -# Created On : Wed Jul 5 13:30:17 2000 -# Purpose : Check post/pre scripts -############################################################################# - -import os -import re -import tempfile - -import rpm - -import AbstractCheck -import Config -from Filter import addDetails, printError, printWarning -import Pkg - - -DEFAULT_VALID_SHELLS = ('<lua>', - '/bin/sh', - '/bin/bash', - '/sbin/sash', - '/usr/bin/perl', - '/sbin/ldconfig', - ) - -DEFAULT_EMPTY_SHELLS = ('/sbin/ldconfig', - ) - -valid_shells = Config.getOption('ValidShells', DEFAULT_VALID_SHELLS) -empty_shells = Config.getOption('ValidEmptyShells', DEFAULT_EMPTY_SHELLS) -# shells that grok the -n switch for debugging -syntaxcheck_shells = ('/bin/sh', '/bin/bash') - -percent_regex = re.compile(r'^[^#]*%{?\w{3,}', re.MULTILINE) -bracket_regex = re.compile(r'^[^#]*if\s+[^ :\]]\]', re.MULTILINE) -home_regex = re.compile(r'[^a-zA-Z]+~/|\${?HOME(\W|$)', re.MULTILINE) -dangerous_command_regex = re.compile(r"(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(cp|mv|ln|tar|rpm|chmod|chown|rm|cpio|install|perl|userdel|groupdel)\s", re.MULTILINE) -selinux_regex = re.compile(r"(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(chcon|runcon)\s", re.MULTILINE) -single_command_regex = re.compile(r"^[ %s]*([^ %s]+)[ %s]*$" % (("\n",) * 3)) -tmp_regex = re.compile(r'^[^#]*\s(/var)?/tmp', re.MULTILINE) -menu_regex = re.compile(r'^/usr/lib/menu/|^/etc/menu-methods/|^/usr/share/applications/') -bogus_var_regex = re.compile(r'(\${?RPM_BUILD_(ROOT|DIR)}?)') - -prereq_assoc = ( - # ['chkconfig', ('chkconfig', '/sbin/chkconfig')], - ['chkfontpath', ('chkfontpath', '/usr/sbin/chkfontpath')], - ['rpm-helper', ('rpm-helper',)], -) - -for p in prereq_assoc: - p[0] = re.compile(r'^[^#]+' + p[0], re.MULTILINE) - -# pychecker fix -del p - - -def incorrect_shell_script(prog, shellscript): - return check_syntax_script(prog, '-n', shellscript) - - -def incorrect_perl_script(prog, perlscript): - return check_syntax_script(prog, '-wc', perlscript) - - -def check_syntax_script(prog, commandline, script): - if not script: - return False - # TODO: test that "prog" is available/executable - tmpfd, tmpname = tempfile.mkstemp(prefix='rpmlint.') - tmpfile = os.fdopen(tmpfd, 'wb') - try: - tmpfile.write(script) - tmpfile.close() - ret = Pkg.getstatusoutput((prog, commandline, tmpname)) - finally: - tmpfile.close() - os.remove(tmpname) - return ret[0] - - -class PostCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, 'PostCheck') - - def check_binary(self, pkg): - prereq = [x[0] for x in pkg.prereq()] - files = pkg.files() - - for tag in Pkg.SCRIPT_TAGS: - script = pkg[tag[0]] - - if not isinstance(script, list): - prog = pkg.scriptprog(tag[1]) - if prog: - prog = prog.split()[0] - self.check_aux(pkg, files, prog, pkg.header[tag[0]], - tag[2], prereq) - else: - prog = pkg[tag[1]] - for idx in range(0, len(prog)): - self.check_aux( - pkg, files, prog[idx], - pkg.header[tag[0]][idx], tag[2], prereq) - - ghost_files = pkg.ghostFiles() - if ghost_files: - postin = pkg[rpm.RPMTAG_POSTIN] - prein = pkg[rpm.RPMTAG_PREIN] - 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: - script_str = Pkg.b2s(script) - if prog: - if prog not in valid_shells: - printError(pkg, 'invalid-shell-in-' + tag, prog) - if prog in empty_shells: - printError(pkg, 'non-empty-' + tag, prog) - if prog in syntaxcheck_shells or prog == '/usr/bin/perl': - if percent_regex.search(script_str): - printWarning(pkg, 'percent-in-' + tag) - if bracket_regex.search(script_str): - printWarning(pkg, 'spurious-bracket-in-' + tag) - res = dangerous_command_regex.search(script_str) - if res: - printWarning(pkg, 'dangerous-command-in-' + tag, - res.group(2)) - res = selinux_regex.search(script_str) - if res: - printError(pkg, 'forbidden-selinux-command-in-' + tag, - res.group(2)) - - if 'update-menus' in script_str: - menu_error = True - for f in files: - if menu_regex.search(f): - menu_error = False - break - if menu_error: - printError( - pkg, - 'update-menus-without-menu-file-in-' + tag) - if tmp_regex.search(script_str): - printError(pkg, 'use-tmp-in-' + tag) - for c in prereq_assoc: - if c[0].search(script_str): - found = False - for p in c[1]: - if p in prereq or p in files: - found = True - break - if not found: - printError(pkg, 'no-prereq-on', c[1][0]) - - if prog in syntaxcheck_shells: - if incorrect_shell_script(prog, script): - printError(pkg, 'shell-syntax-error-in-' + tag) - if home_regex.search(script_str): - printError(pkg, 'use-of-home-in-' + tag) - res = bogus_var_regex.search(script_str) - if res: - printWarning(pkg, 'bogus-variable-use-in-' + tag, - res.group(1)) - - if prog == '/usr/bin/perl': - if incorrect_perl_script(prog, script): - printError(pkg, 'perl-syntax-error-in-' + tag) - elif prog.endswith('sh'): - res = single_command_regex.search(script_str) - if res: - printWarning(pkg, 'one-line-command-in-' + tag, - res.group(1)) - - elif prog not in empty_shells and prog in valid_shells: - printWarning(pkg, 'empty-' + tag) - - -# Create an object to enable the auto registration of the test -check = PostCheck() - -# Add information about checks -addDetails( -'postin-without-ghost-file-creation', -'''A file tagged as ghost is not created during %prein nor during %postin.''', -) -for scriptlet in map(lambda x: '%' + x, Pkg.RPM_SCRIPTLETS): - addDetails( -'one-line-command-in-%s' % scriptlet, -'''You should use %s -p <command> instead of using: - -%s -<command> - -It will avoid the fork of a shell interpreter to execute your command as -well as allows rpm to automatically mark the dependency on your command -for the execution of the scriptlet.''' % (scriptlet, scriptlet), - -'percent-in-%s' % scriptlet, -'''The %s scriptlet contains a "%%" in a context which might indicate it being -fallout from an rpm macro/variable which was not expanded during build. -Investigate whether this is the case and fix if appropriate.''' % scriptlet, - -'spurious-bracket-in-%s' % scriptlet, -'''The %s scriptlet contains an "if []" construct without a space before -the "]".''' % scriptlet, - -'forbidden-selinux-command-in-%s' % scriptlet, -'''A command which requires intimate knowledge about a specific SELinux -policy type was found in the scriptlet. These types are subject to change -on a policy version upgrade. Use the restorecon command which queries the -currently loaded policy for the correct type instead.''', - -'non-empty-%s' % scriptlet, -'''Scriptlets for the interpreter mentioned in the message should be empty. -One common case where they are unintentionally not is when the specfile -contains comments after the scriptlet and before the next section. Review -and clean up the scriptlet contents if appropriate.''', -) - -# PostCheck.py ends here diff --git a/README.devel b/README.devel deleted file mode 100644 index c1ef8ff..0000000 --- a/README.devel +++ /dev/null @@ -1,15 +0,0 @@ -The latest development version can be retrieved from anonymous Git: - -$ git clone git@github.com:rpm-software-management/rpmlint.git - -To run rpmlint from the working tree or in place from an unpacked tarball: - -$ ./rpmlint -C . <rpms> - -...and to run only a single check, for example to test only FHSCheck: - -$ ./rpmlint -C . -c FHSCheck <rpms> - -For profiling, use something like: - -$ python -O -u -m cProfile -s cumulative rpmlint -C . [...] @@ -1,71 +1,138 @@ -# rpmlint [![Build Status](https://travis-ci.org/rpm-software-management/rpmlint.svg)](https://travis-ci.org/rpm-software-management/rpmlint) +# rpmlint -rpmlint is a tool for checking common errors in rpm packages. rpmlint -can be used to test individual packages before uploading or to check -an entire distribution. By default all applicable checks are -performed but specific checks can be performed by using command line -parameters. +<h1 align="center"> + <img src="rpmlint.svg" width="200"> +</h1> -rpmlint can check binary rpms (files and installed ones), source rpms, -and plain specfiles, but all checks do not apply to all argument -types. For best check coverage, run rpmlint on source rpms instead of -plain specfiles, and installed binary rpms instead of uninstalled -binary rpm files. +[![Build and Test](https://github.com/rpm-software-management/rpmlint/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/rpm-software-management/rpmlint/actions/workflows/main.yml) +[![Build and Test 2](https://github.com/rpm-software-management/rpmlint/actions/workflows/main.yml/badge.svg?branch=opensuse)](https://github.com/rpm-software-management/rpmlint/actions/workflows/main.yml) +[![build result](https://build.opensuse.org/projects/devel:openSUSE:Factory:rpmlint/packages/rpmlint/badge.svg?type=default)](https://build.opensuse.org/package/show/devel:openSUSE:Factory:rpmlint/rpmlint) +[![repology](https://repology.org/badge/latest-versions/rpmlint.svg)](https://repology.org/project/rpmlint/versions) +[![Coverage Status](https://coveralls.io/repos/github/rpm-software-management/rpmlint/badge.svg)](https://coveralls.io/github/rpm-software-management/rpmlint) -The idea for rpmlint is from the lintian tool of the Debian project. +`rpmlint` is a tool for checking common errors in RPM packages. +`rpmlint` can be used to test individual packages before uploading or to check +an entire distribution. + +`rpmlint` can check binary RPMs, source RPMs, and plain specfiles, but all +checks do not apply to all argument types. +For best check coverage, run `rpmlint` on source RPMs instead of +plain specfiles. + +The idea for `rpmlint` is from the lintian tool of the Debian project. +All the checks reside in `rpmlint/checks` folder. Feel free to provide new +checks and suggestions at: -Comments and new checks welcome. See the project home page at https://github.com/rpm-software-management/rpmlint -Implemented checks: - -- Tag checks (TagsCheck). -- Distribution specific checks (MandrakeCheck). -- Binary checks (BinaryCheck). -- Configuration file checks (ConfigCheck). -- Location, permission, group and owner checks (FileCheck). -- suid warnings (FileCheck). -- Signature checks (SignatureCheck). -- FHS checks (FHSCheck). -- Source specific checks (SourceCheck). -- i18n checks (I18NCheck). -- Menu system checks (MenuCheck). -- Scriptlet checks (PostCheck). -- `/etc/rc.d/init.d` checks (InitScriptCheck). -- Spec file checks (SpecCheck). -- Zip/Jar file checks (ZipCheck). -- Pam configuration file checks (PamCheck). -- Rpm file checks (RpmFileCheck). -- Software Collections checks (SCLCheck). - -If you want to change configuration options or the list of checks, use -the global configuration files `/etc/rpmlint/*config` or the user -configuration file `$XDG_CONFIG_HOME/rpmlint` (`~/.config/rpmlint` if -`$XDG_CONFIG_HOME` is empty or not set). - -Configuration files are Python source files and should begin with the -following line: - -```python -from Config import * +## Install + +For installation on your machine you will need the following packages: + +Mandatory: +- Python 3.8 or newer +- python3-setuptools, python3-tomli (for `python3 < 3.11`), python3-tomli-w, + python3-pyxdg, python3-pybeam +- rpm and its python bindings +- binutils, cpio, gzip, bzip, xz and zstd + +Optional, for running the test suite: +- devscripts +- dash +- a 32-bit glibc if on a 64-bit architecture +- desktop-file-utils +- libmagic and its python bindings +- enchant and its python bindings, along with en_US and cs_CZ dictionaries +- appstream-util, part of appstream-glib + +`rpmlint` is part of most distributions and as an user you can simply + + dnf install rpmlint + +## Testing + +You will need to have all the required modules as listed on the Install section above. +You will also need `pytest`,`pytest-cov` and `pytest-xdist`, +which you can install individually or by running `pip install -e ".[test]"`. + +If all the dependencies are present you can just execute tests using: + +`python3 -m pytest` + +Or even pick one of the tests using `pytest`: + +`python3 -m pytest test/test_config.py` + +## Bugfixing and contributing + +Any help is, of course, welcome but honestly most probable cause for your visit +here is that `rpmlint` is marking something as invalid while it shouldn't or +it is marking something as correct while it should not either :) + +Now there is an easy way how to fix that. Our testsuite simply needs an +extension to take the above problem into the account. + +Primarily we just need the offending rpm file (best the smallest you can +find or we would soon take few GB to take a checkout) and some basic +expectation of what should happen. + +### Example workflow + +1) I have rpmfile that should report unreadable zip file +2) I store this file in git under `test/binary/texlive-codepage-doc-2018.151.svn21126-38.1.noarch.rpm` +3) Now I need to figure out what `check` should test this, in this case `test_zip.py` +4) For the testing I will have to devise a small function that validates my expectations: + ``` +@pytest.mark.parametrize('package', ['binary/texlive-codepage-doc']) +def test_zip2(tmpdir, package, zipcheck): + output, test = zipcheck + test.check(get_tested_package(package, tmpdir)) + out = output.print_results(output.results) + assert 'W: unable-to-read-zip' in out +``` + +As you can see it is not so hard and with each added test we get better +coverage on what is really expected from rpmlint and avoid naughty regressions +in the long run. + +Preferable approach for binary packages is to create artificial testcase (to keep binaries small and trivial). +We are currently using OBS to produce binaries: +https://build.opensuse.org/project/show/devel:openSUSE:Factory:rpmlint:tests + +For a sample package see: +https://build.opensuse.org/package/show/devel:openSUSE:Factory:rpmlint:tests/non-position-independent-exec + +## Configuration + +If you want to change configuration options or the list of checks you can +use the following locations: -to load configuration functions. +`/etc/xdg/rpmlint/*toml` -Configuration functions: +`$XDG_CONFIG_HOME/rpmlint/*toml` -`resetChecks()` resets the list of checks. +The configuration itself is a `toml` file where for some basic inspiration +you can check up [`rpmlint/configdefaults.toml`](rpmlint/configdefaults.toml) which specifies format/defaults. -`addCheck(check)` adds the check to the list of checks to try. +One can also include additional configuration files (or directories) by using the `--config` option. +Note that all TOML configuration values are merged and not overridden. +So e.g. values in a list are concatenated. If you need an override, +use `*.override.*toml` configuration file, where all defined values are selected as default. -`addCheckDir(path)` adds a path to look for checks. +Additional option to control `rpmlint` behaviour is the addition of `rpmlintrc` file +which uses old syntax for compatibility with old `rpmlint` releases, yet +it can be normal `toml` file if you wish: -`setOption(name, value)` sets the value of the configuration option. -See below for the list of available options. + setBadness('check', 0) + addFilter('test-i-ignore') -`addFilter(regexp)` adds a filter to remove the output of a check, and -`removeFilter(regexp)` removes one (for use eg. in per-user configuration -files to remove filters added in system config files). +The location of `rpmlintrc` can be set using `--rpmlintrc` option. +Or it can load any `*.rpmlintrc` or `*-rpmlintrc` that are located in the same +folder as check RPM file (or a specfile). Note the auto-loading happens only +when one RPM file (or a specfile) is used. +The best practice is to store the name in `$PACKAGE_NAME.rpmlintrc`. -See the file `config` shipped with rpmlint for examples, available -options and their default values. +`setBadness` overrides a default badness for a given check and `addFilter` ignores all errors +that match the given regular expression (one cannot filter out errors that are listed in `BlockedFilters` +in a configuration file). diff --git a/RpmFileCheck.py b/RpmFileCheck.py deleted file mode 100644 index da43df4..0000000 --- a/RpmFileCheck.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# check the rpm file for various errors. -# -# Copyright (C) 2006 Michael Scherer <misc@zarb.org> -# Ville Skyttä <ville.skytta@iki.fi> -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - - -import os - -import AbstractCheck -from Filter import addDetails, printWarning - - -class RpmFileCheck(AbstractCheck.AbstractCheck): - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "RpmFileCheck") - - def check(self, pkg): - # http://en.wikipedia.org/wiki/Joliet_(file_system) - rpmfile_name = os.path.basename(pkg.filename) - if len(rpmfile_name) > 64: - printWarning(pkg, 'filename-too-long-for-joliet', rpmfile_name) - - -check = RpmFileCheck() - -addDetails( -'filename-too-long-for-joliet', -'''This filename is too long to fit on a joliet filesystem (limit is 64 unicode -chars).''', -) diff --git a/SCLCheck.py b/SCLCheck.py deleted file mode 100644 index fde4323..0000000 --- a/SCLCheck.py +++ /dev/null @@ -1,417 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : SCLCheck.py -# Package : rpmlint -# Author : Miro Hrončok -# Created on : Wed Jul 24 20:25 2013 -# Purpose : Software Collections checks. -############################################################################# - -import os -import re - -import AbstractCheck -from Filter import addDetails, printError, printWarning -import Pkg - -# Compile all regexes here -allowed_etc = re.compile(r'^/etc/(cron|profile|logrotate)\.d/', re.M) -allowed_var = re.compile(r'^/var/(log|lock)/', re.M) -buildrequires = re.compile(r'^BuildRequires:\s*(.*)', re.M) -global_scl_definition = re.compile(r'(^|\s)%(define|global)\s+scl\s+\S+\s*$', re.M) -libdir = re.compile(r'%\{?\??_libdir\}?', re.M) -name = re.compile(r'^Name:\s*(.*)', re.M) -name_small = re.compile(r'^%\{?name\}?', re.M) -noarch = re.compile(r'^BuildArch:\s*noarch\s*$', re.M) -obsoletes_conflicts = re.compile(r'^(Obsoletes|(Build)?Conflicts):\s*(.*)', re.M) -pkg_name = re.compile(r'(^|\s)%\{!\?scl:%(define|global)\s+pkg_name\s+%\{name\}\}\s*$', re.M) -provides = re.compile(r'^Provides:\s*(.*)', re.M) -requires = re.compile(r'(^|:)Requires:\s*(.*)', re.M) -scl_files = re.compile(r'(^|\s)%\{?\??scl_files\}?\s*$', re.M) -scl_install = re.compile(r'(^|\s)%\{?\??scl_install\}?\s*$', re.M) -scl_macros = re.compile(r'(^|\s)%\{?\??_root_sysconfdir\}?/rpm/macros\.%\{?\??scl\}?-config\s*^', re.M) -scl_package_definition = re.compile(r'(^|\s)%\{\?scl\s*:\s*%scl_package\s+\S+\s*\}\s*$', re.M) -scl_prefix_noncond = re.compile(r'%\{?scl_prefix\}?', re.M) -scl_prefix = re.compile(r'%\{?\??scl_prefix\}?', re.M) -scl_prefix_start = re.compile(r'^%\{?\??scl_prefix\}?', re.M) -scl_runtime = re.compile(r'%\{?\??scl\}?-runtime\}?', re.M) -scl_use = re.compile(r'%\{?\??\!?\??scl') -setup = re.compile(r'^%setup(.*)', re.M) -startdir = re.compile(r'^/opt/[^/]+/', re.M) -subpackage_alien = re.compile(r'(^|\s)%package\s+(-n\s+)?(?!(build|runtime))\S+\s*$', re.M) -subpackage_any = re.compile(r'(^|\s)%package\s+(.*)', re.M) -subpackage_build = re.compile(r'(^|\s)%package\s+build\s*$', re.M) -subpackage_runtime = re.compile(r'(^|\s)%package\s+runtime\s*$', re.M) - - -def index_or_sub(source, word, sub=0): - """ - Helper function that returns index of word in source or sub when not found. - """ - try: - return source.index(word) - except ValueError: - return sub - - -class SCLCheck(AbstractCheck.AbstractCheck): - '''Software Collections checks''' - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "SCLCheck") - self._spec_file = None - - def check_source(self, pkg): - # lookup spec file - for fname, pkgfile in pkg.files().items(): - if fname.endswith('.spec'): - self._spec_file = pkgfile.path - self.check_spec(pkg, self._spec_file) - - def check_spec(self, pkg, spec_file, spec_lines=None): - '''SCL spec file checks''' - spec = '\n'.join(Pkg.readlines(spec_file)) - if global_scl_definition.search(spec): - self.check_metapackage(pkg, spec) - elif scl_package_definition.search(spec): - self.check_scl_spec(pkg, spec) - elif scl_use.search(spec): - printError(pkg, 'undeclared-scl') - - def check_binary(self, pkg): - '''SCL binary package checks''' - # Assume that no dash in package name means no SCL - splits = pkg.name.split('-') - if len(splits) < 2: - return - scl_name = splits[0] - # While we are here, check if it's a runtime/build package - is_runtime = splits[-1] == 'runtime' - is_build = splits[-1] == 'build' - del splits - - # Now test if there is /opt/foo/ dir - good = False - for fname in pkg.files().keys(): - if startdir.search(fname): - good = True - break - if not good: - return - - # Test if our dir is named the same way as scl - good = True - for fname in pkg.files().keys(): - if not startdir.search(fname): - if allowed_etc.search(fname) or allowed_var.search(fname) or \ - fname.startswith('/usr/bin/'): - continue - if fname.startswith('/etc/rpm/'): - if not is_build: - printWarning(pkg, 'scl-rpm-macros-outside-of-build', - fname) - continue - if is_runtime and \ - fname == os.path.join('/etc/scl/prefixes', scl_name): - continue - printError(pkg, 'file-outside-of-scl-tree', fname) - else: - if fname.split('/')[3] != scl_name: - good = False - - if not good: - printError(pkg, 'scl-name-screwed-up') - - def check_metapackage(self, pkg, spec): - '''SCL metapackage spec checks''' - - # Examine subpackages - runtime = subpackage_runtime.search(spec) - if not runtime: - printError(pkg, 'no-runtime-in-scl-metapackage') - - build = subpackage_build.search(spec) - if not build: - printError(pkg, 'no-build-in-scl-metapackage') - else: - # Get (B)Rs section for build subpackage - end = index_or_sub(spec[build.end():], '%package', -1) - if 'scl-utils-build' not in \ - ' '.join(self.get_requires(spec[build.end():end])): - printWarning(pkg, - 'scl-build-without-requiring-scl-utils-build') - - alien = subpackage_alien.search(spec) - if alien: - printError(pkg, 'weird-subpackage-in-scl-metapackage', - alien.group()[9:]) - - # Get (B)Rs section for main package - end = index_or_sub(spec, '%package', -1) - if 'scl-utils-build' not in \ - ' '.join(self.get_build_requires(spec[:end])): - printError(pkg, 'scl-metapackage-without-scl-utils-build-br') - - # Enter %install section - install_start = index_or_sub(spec, '%install') - install_end = index_or_sub(spec, '%check') - if not install_end: - install_end = index_or_sub(spec, '%clean') - if not install_end: - install_end = index_or_sub(spec, '%files') - if not install_end: - install_end = index_or_sub(spec, '%changelog', -1) - # Search %scl_install - if not scl_install.search(spec[install_start:install_end]): - printError(pkg, 'scl-metapackage-without-%scl_install') - if noarch.search(spec[:install_start]) and \ - libdir.search(spec[install_start:install_end]): - printError(pkg, 'noarch-scl-metapackage-with-libdir') - - # Analyze %files - files = self.get_files(spec) - if files: - printWarning(pkg, 'scl-main-metapackage-contains-files', - ', '.join(files)) - if runtime: - if not scl_files.search( - '\n'.join(self.get_files(spec, 'runtime'))): - printError(pkg, 'scl-runtime-package-without-%scl_files') - if build: - if not scl_macros.search( - '\n'.join(self.get_files(spec, 'build'))): - printError(pkg, 'scl-build-package-without-rpm-macros') - - def check_scl_spec(self, pkg, spec): - '''SCL ready spec checks''' - - # For the entire spec - if not pkg_name.search(spec): - printWarning(pkg, 'missing-pkg_name-definition') - if scl_prefix_noncond.search(self.remove_scl_conds(spec)): - printWarning(pkg, 'scl-prefix-without-condition') - if not scl_prefix.search(self.get_name(spec)): - printError(pkg, 'name-without-scl-prefix') - for item in self.get_obsoletes_and_conflicts(spec): - if not scl_prefix.search(item): - printError(pkg, 'obsoletes-or-conflicts-without-scl-prefix') - break - for item in self.get_provides(spec): - if not scl_prefix.search(item): - printError(pkg, 'provides-without-scl-prefix') - break - setup_opts = setup.search(spec) - if setup_opts: - if '-n' not in setup_opts.groups()[0]: - printError(pkg, 'scl-setup-without-n') - - # Examine main package and subpackages one by one - borders = [] - borders.append(0) # main package starts at the beginning - while True: - more = subpackage_any.search(spec[borders[-1]:]) - if not more: - break - splits = more.groups()[1].split() - if len(splits) > 1 and splits[0] == '-n': - if not scl_prefix_start.search(splits[-1]): - printError(pkg, 'subpackage-with-n-without-scl-prefix') - # current end is counted only from last one - borders.append(borders[-1] + more.end()) - subpackages = [(borders[i], borders[i + 1]) - for i in range(len(borders) - 1)] - for subpackage in subpackages: - ok = False - for require in self.get_requires(spec[subpackage[0]:subpackage[1]]): - # Remove flase entries - if not require or require == ':': - continue - # If it starts with %{name}, it,s fine - # If it starts with SCL prefix, it's fine - # If it is scl-runtime, it's the best - if name_small.search(require) or \ - scl_prefix_start.search(require) or \ - scl_runtime.match(require): - ok = True - break - if not ok: - printError(pkg, - 'doesnt-require-scl-runtime-or-other-scl-package') - break - - def get_requires(self, text, build=False): - '''For given piece of spec, find Requires (or BuildRequires)''' - if build: - search = buildrequires - else: - search = requires - res = [] - while True: - more = search.search(text) - if not more: - break - res.extend(more.groups()) - text = text[more.end():] - return res - - def get_build_requires(self, text): - '''Call get_requires() with build = True''' - return self.get_requires(text, True) - - def get_name(self, text): - '''For given piece of spec, get the Name of the main package''' - sname = name.search(text) - if not sname: - return None - return sname.groups()[0].strip() - - def get_obsoletes_and_conflicts(self, text): - '''For given piece of spec, find Obsoletes and Conflicts''' - res = [] - while True: - more = obsoletes_conflicts.search(text) - if not more: - break - # 1st group is 'Obsoletes' or 'Conflicts', 2nd is Build or None - res.extend(more.groups()[2:]) - text = text[more.end():] - return res - - def get_provides(self, text): - '''For given piece of spec, find Provides''' - res = [] - while True: - more = provides.search(text) - if not more: - break - res.extend(more.groups()) - text = text[more.end():] - return res - - def get_files(self, text, subpackage=None): - """ - Return the list of files in %files section for given subpackage or - main package. - """ - if subpackage: - pattern = r'%%\{?\??files\}?(\s+-n)?\s+%s\s*$' % subpackage - else: - pattern = r'%\{?\??files\}?\s*$' - search = re.search(pattern, text, re.M) - if not search: - return [] - - start = search.end() - end = index_or_sub(text[start:], '%files') - if not end: - end = index_or_sub(text[start:], '%changelog', -1) - return list(filter(None, text[start:start + end].strip().split('\n'))) - - def remove_scl_conds(self, text): - '''Returns given text without %scl conds blocks''' - while text.count('%{?scl:') > 0: - spos = text.index('%{?scl:') - pos = spos + 7 - counter = 1 - while counter: - if text[pos] == '{': - counter += 1 - if text[pos] == '}': - counter -= 1 - pos += 1 - text = text[:spos] + text[pos:] - return text - - -# Create an object to enable the auto registration of the test -check = SCLCheck() - -# Add information about checks -addDetails( -'undeclared-scl', -'''Specfile contains %scl* macros, but was not recognized as SCL metapackage or -SCL ready package. If this should be an SCL metapackage, don't forget to define -the %scl macro. If this should be an SCL ready package, run %scl -conditionalized %scl_package macro, e.g. %{?scl:%scl_package foo}.''', - -'no-runtime-in-scl-metapackage', -'SCL metapackage must have runtime subpackage.', - -'no-build-in-scl-metapackage', -'SCL metapackage must have build subpackage.', - -'weird-subpackage-in-scl-metapackage', -'Only allowed subpackages in SCL metapackage are build and runtime.', - -'scl-metapackage-without-scl-utils-build-br', -'SCL metapackage must BuildRequire scl-utils-build.', - -'scl-build-without-requiring-scl-utils-build', -'SCL runtime package should Require scl-utils-build.', - -'scl-metapackage-without-%scl_install', -'SCL metapackage must call %scl_install in the %install section.', - -'noarch-scl-metapackage-with-libdir', -'''If "enable" script of SCL metapackage contains %{_libdir}, the package must -be arch specific, otherwise it may be noarch.''', - -'scl-main-metapackage-contains-files', -'Main package of SCL metapackage should not contain any files.', - -'scl-runtime-package-without-%scl_files', -'SCL runtime package must contain %scl_files in %files section.', - -'scl-build-package-without-rpm-macros', -'''SCL build package must contain %{_root_sysconfdir}/rpm/macros. %{scl}-config -in %files section.''', - -'missing-pkg_name-definition', -'%{!?scl:%global pkg_name %{name}} is missing in the specfile.', - -'name-without-scl-prefix', -'Name of SCL package must start with %{?scl_prefix}.', - -'scl-prefix-without-condition', -'''The SCL prefix is used without condition - this won't work if the package is -build outside of SCL - use %{?scl_prefix} with questionmark.''', - -'obsoletes-or-conflicts-without-scl-prefix', -'''Obsoletes, Conflicts and Build Conflicts must always be prefixed with -%{?scl_prefix}. This is extremely important, as the SCLs are often used for -deploying new packages on older systems (that may contain old packages, now -obsoleted by the new ones), but they shouldn't Obsolete or Conflict with the -non-SCL RPMs installed on the system (that's the idea of SCL).''', - -'provides-without-scl-prefix', -'Provides tag must always be prefixed with %{?scl_prefix}.', - -'doesnt-require-scl-runtime-or-other-scl-package', -'''The package must require %{scl}-runtime, unless it depends on another -package that requires %{scl}-runtime. It's impossible to check what other -packages require, so this simply checks if this package requires at least -something from its collection.''', - -'subpackage-with-n-without-scl-prefix', -'''If (and only if) a package defines its name with -n, the name must be -prefixed with %{?scl_prefix}.''', - -'scl-setup-without-n', -'''The %setup macro needs the -n argument for SCL builds, because the directory -with source probably doesn't include SCL prefix in its name.''', - -'scl-name-screwed-up', -'''SCL package's name starts with SCL prefix. That prefix is used as a -directory, where files are stored: If the prefix is foo, the directory is -/opt/provides/foo. This package doesn't respect that. This means either the -name of the package is wrong, or the directory.''', - -'file-outside-of-scl-tree', -'''SCL package should only contain files in /opt/provider/scl-name directory or -in other allowed directories such as some directories in /etc or /var. Wrapper -scripts in /usr/bin are also allowed.''', - -'scl-rpm-macros-outside-of-build', -'''RPM macros in SCL packages should belong to -build subpackage of the SCL -metapackage.''', -) diff --git a/SignatureCheck.py b/SignatureCheck.py deleted file mode 100644 index 483579d..0000000 --- a/SignatureCheck.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : SignatureCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Thu Oct 7 17:06:14 1999 -# Purpose : check the presence of a PGP signature. -############################################################################# - -import re - -import AbstractCheck -from Filter import addDetails, printError -import Pkg - - -class SignatureCheck(AbstractCheck.AbstractCheck): - pgp_regex = re.compile(r"pgp|gpg", re.IGNORECASE) - unknown_key_regex = re.compile(r"\(MISSING KEYS:(?:\([^)]+\))?\s+([^\)]+)\)") - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "SignatureCheck") - - def check(self, pkg): - res = pkg.checkSignature() - if not res or res[0] != 0: - if res and res[1]: - kres = SignatureCheck.unknown_key_regex.search(res[1]) - else: - kres = None - if kres: - printError(pkg, "unknown-key", kres.group(1)) - else: - Pkg.warn("Error checking signature of %s: %s" % - (pkg.filename, res[1])) - else: - if not SignatureCheck.pgp_regex.search(res[1]): - printError(pkg, "no-signature") - - -# Create an object to enable the auto registration of the test -check = SignatureCheck() - -addDetails( -'no-signature', -'''You have to include your pgp or gpg signature in your package. -For more information on signatures, please refer to www.gnupg.org.''', - -'unknown-key', -'''The package was signed, but with an unknown key. -See the rpm --import option for more information.''', -) - -# SignatureCheck.py ends here diff --git a/SourceCheck.py b/SourceCheck.py deleted file mode 100644 index fa67164..0000000 --- a/SourceCheck.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : SourceCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Wed Oct 27 21:17:03 1999 -# Purpose : verify source package correctness. -############################################################################# - -import re - -import AbstractCheck -import Config -from Filter import addDetails, printError, printWarning - - -DEFAULT_VALID_SRC_PERMS = (0o644, 0o755) - -source_regex = re.compile(r'\\.(tar|patch|tgz|diff)$') -compress_ext = Config.getOption("CompressExtension", "bz2") -valid_src_perms = Config.getOption("ValidSrcPerms", DEFAULT_VALID_SRC_PERMS) - -compressed_fileext_magic = { - 'xz': 'XZ compressed', - 'gz': 'gzip compressed', - 'tgz': 'gzip compressed', - 'bz2': 'bzip2 compressed' -} - - -class SourceCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, 'SourceCheck') - - def check_source(self, pkg): - # process file list - spec_file = None - for fname, pkgfile in pkg.files().items(): - file_ext = fname.rpartition('.')[2] - if (file_ext in compressed_fileext_magic and - pkgfile.magic and - compressed_fileext_magic[file_ext] not in pkgfile.magic): - printWarning(pkg, 'inconsistent-file-extension', fname) - - if fname.endswith('.spec'): - if spec_file: - printError(pkg, 'multiple-specfiles', spec_file, fname) - else: - spec_file = fname - elif source_regex.search(fname) and compress_ext and \ - not fname.endswith(compress_ext): - printWarning(pkg, 'source-or-patch-not-compressed', - compress_ext, fname) - perm = pkgfile.mode & 0o7777 - if perm not in valid_src_perms: - printWarning(pkg, 'strange-permission', fname, "%o" % perm) - - -check = SourceCheck() - -addDetails( -'multiple-specfiles', -'''Your package contains multiple spec files. To build a -correct package, you need to have only one spec file containing -all your RPM information.''', - -'source-or-patch-not-compressed', -'''A source archive or file in your package is not compressed using the %s -compression method (doesn't have the %s extension).''' % -(compress_ext, compress_ext), - -'strange-permission', -'''A file that you listed to include in your package has strange -permissions. Usually, a file should have 0644 permissions.''', - -'inconsistent-file-extension', -'''The file name extension indicates a different compression format than -what is actually used (as checked by file(1))''', -) - -# SourceCheck.py ends here diff --git a/SpecCheck.py b/SpecCheck.py deleted file mode 100644 index 6b74e7f..0000000 --- a/SpecCheck.py +++ /dev/null @@ -1,845 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : SpecCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Thu Oct 7 17:06:14 1999 -# Purpose : check the spec file of a source rpm. -############################################################################# - -import re -import sys -try: - from urlparse import urlparse -except ImportError: # Python 3 - from urllib.parse import urlparse - -import rpm - -import AbstractCheck -import Config -from Filter import addDetails, printError, printWarning -import Pkg -from TagsCheck import VALID_GROUPS - -# Don't check for hardcoded library paths in biarch packages -DEFAULT_BIARCH_PACKAGES = '^(gcc|glibc)' - -# Don't check for hardcoded library paths in packages which can have -# their noarch files in /usr/lib/<package>/*, or packages that can't -# be installed on biarch systems -DEFAULT_HARDCODED_LIB_PATH_EXCEPTIONS = r'/lib/(modules|cpp|perl5|rpm|hotplug|firmware|systemd)($|[\s/,])' - - -def re_tag_compile(tag): - r = r"^%s\s*:\s*(\S.*?)\s*$" % tag - return re.compile(r, re.IGNORECASE) - - -patch_regex = re_tag_compile(r'Patch(\d*)') -applied_patch_regex = re.compile(r"^%patch(\d*)") -applied_patch_p_regex = re.compile(r"\s-P\s+(\d+)\b") -applied_patch_pipe_regex = re.compile(r'\s%\{PATCH(\d+)\}\s*\|\s*(%\{?__)?patch\b') -applied_patch_i_regex = re.compile(r"(?:%\{?__)?patch\}?.*?\s+(?:<|-i)\s+%\{PATCH(\d+)\}") -source_dir_regex = re.compile(r"^[^#]*(\$RPM_SOURCE_DIR|%{?_sourcedir}?)") -obsolete_tags_regex = re_tag_compile(r'(?:Serial|Copyright)') -buildroot_regex = re_tag_compile('BuildRoot') -prefix_regex = re_tag_compile('Prefix') -packager_regex = re_tag_compile('Packager') -buildarch_regex = re_tag_compile('BuildArch(?:itectures)?') -buildprereq_regex = re_tag_compile('BuildPreReq') -prereq_regex = re_tag_compile(r'PreReq(\(.*\))') - -make_check_regex = re.compile(r'(^|\s|%{?__)make}?\s+(check|test)') -rm_regex = re.compile(r'(^|\s)((.*/)?rm|%{?__rm}?) ') -rpm_buildroot_regex = re.compile(r'^[^#]*(?:(\\\*)\${?RPM_BUILD_ROOT}?|(%+){?buildroot}?)') -configure_libdir_spec_regex = re.compile(r'ln |\./configure[^#]*--libdir=(\S+)[^#]*') -lib_package_regex = re.compile(r'^%package.*\Wlib') -ifarch_regex = re.compile(r'^\s*%ifn?arch\s') -if_regex = re.compile(r'^\s*%if\s') -endif_regex = re.compile(r'^\s*%endif\b') -biarch_package_regex = re.compile(DEFAULT_BIARCH_PACKAGES) -hardcoded_lib_path_exceptions_regex = re.compile(Config.getOption('HardcodedLibPathExceptions', DEFAULT_HARDCODED_LIB_PATH_EXCEPTIONS)) -use_utf8 = Config.getOption('UseUTF8', Config.USEUTF8_DEFAULT) -libdir_regex = re.compile(r'%{?_lib(?:dir)?\}?\b') -section_regexs = dict( - ([x, re.compile('^%' + x + r'(?:\s|$)')] - for x in ('build', 'changelog', 'check', 'clean', 'description', 'files', - 'install', 'package', 'prep') + Pkg.RPM_SCRIPTLETS)) -deprecated_grep_regex = re.compile(r'\b[ef]grep\b') - -# Only check for /lib, /usr/lib, /usr/X11R6/lib -# TODO: better handling of X libraries and modules. -hardcoded_library_paths = '(/lib|/usr/lib|/usr/X11R6/lib/(?!([^/]+/)+)[^/]*\\.([oa]|la|so[0-9.]*))' -hardcoded_library_path_regex = re.compile(r'^[^#]*((^|\s+|\.\./\.\.|\${?RPM_BUILD_ROOT}?|%{?buildroot}?|%{?_prefix}?)' + hardcoded_library_paths + r'(?=[\s;/])([^\s,;]*))') - -# Requires(pre,post) is broken in some rpm versions, see -# https://bugzilla.redhat.com/118780 and bugs linked to that one. -scriptlet_requires_regex = re.compile(r'^(PreReq|Requires)\([^\)]*,', re.IGNORECASE) - -DEFINE_RE = r'(^|\s)%(define|global)\s+' -depscript_override_regex = re.compile(DEFINE_RE + r'__find_(requires|provides)\s') -depgen_disable_regex = re.compile(DEFINE_RE + r'_use_internal_dependency_generator\s+0') -patch_fuzz_override_regex = re.compile(DEFINE_RE + r'_default_patch_fuzz\s+(\d+)') - -# See https://bugzilla.redhat.com/488146 for details -indent_spaces_regex = re.compile('( \t|(^|\t)([^\t]{8})*[^\t]{4}[^\t]?([^\t][^\t.!?]|[^\t]?[.!?] ) )') - -requires_regex = re.compile(r'^(?:Build)?(?:Pre)?Req(?:uires)?(?:\([^\)]+\))?:\s*(.*)', re.IGNORECASE) -provides_regex = re.compile(r'^Provides(?:\([^\)]+\))?:\s*(.*)', re.IGNORECASE) -obsoletes_regex = re.compile(r'^Obsoletes:\s*(.*)', re.IGNORECASE) -conflicts_regex = re.compile(r'^(?:Build)?Conflicts:\s*(.*)', re.IGNORECASE) - -compop_regex = re.compile(r'[<>=]') - -setup_regex = re.compile(r'%setup\b') # intentionally no whitespace before! -setup_q_regex = re.compile(r' -[A-Za-z]*q') -setup_t_regex = re.compile(r' -[A-Za-z]*T') -setup_ab_regex = re.compile(r' -[A-Za-z]*[ab]') -autosetup_regex = re.compile(r'^\s*%autosetup(\s.*|$)') -autosetup_n_regex = re.compile(r' -[A-Za-z]*N') -autopatch_regex = re.compile(r'^\s*%autopatch(?:\s|$)') - -filelist_regex = re.compile(r'\s+-f\s+\S+') -pkgname_regex = re.compile(r'\s+(?:-n\s+)?(\S+)') -tarball_regex = re.compile(r'\.(?:t(?:ar|[glx]z|bz2?)|zip)\b', re.IGNORECASE) - -UNICODE_NBSP = u'\xa0' - - -def unversioned(deps): - '''Yield unversioned dependency names from the given list.''' - for dep in deps: - if not dep[1]: - yield dep[0] - - -def contains_buildroot(line): - '''Check if the given line contains use of rpm buildroot.''' - res = rpm_buildroot_regex.search(line) - if res and \ - (not res.group(1) or len(res.group(1)) % 2 == 0) and \ - (not res.group(2) or len(res.group(2)) % 2 != 0): - return True - return False - - -class SpecCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "SpecCheck") - self._spec_file = None - self._spec_name = None - - def check_source(self, pkg): - wrong_spec = False - - # lookup spec file - for fname, pkgfile in pkg.files().items(): - if fname.endswith('.spec'): - self._spec_file = pkgfile.path - self._spec_name = pkgfile.name - if fname == pkg.name + ".spec": - wrong_spec = False - break - else: - wrong_spec = True - if not self._spec_file: - printError(pkg, "no-spec-file") - else: - if wrong_spec: - printError(pkg, "invalid-spec-name") - - # check content of spec file - self.check_spec(pkg, self._spec_file) - - def check_spec(self, pkg, spec_file, spec_lines=None): - 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 = [] - patches_auto_applied = False - source_dir = False - buildroot = False - configure_linenum = None - configure_cmdline = "" - mklibname = False - is_lib_pkg = False - if_depth = 0 - ifarch_depth = -1 - current_section = 'package' - buildroot_clean = {'clean': False, 'install': False} - depscript_override = False - depgen_disabled = False - patch_fuzz_override = False - indent_spaces = 0 - indent_tabs = 0 - section = {} - # None == main package - current_package = None - package_noarch = {} - - is_utf8 = False - if self._spec_file and use_utf8: - if Pkg.is_utf8(self._spec_file): - is_utf8 = True - else: - printError(pkg, "non-utf8-spec-file", - self._spec_name or self._spec_file) - - # gather info from spec lines - - pkg.current_linenum = 0 - - nbsp = UNICODE_NBSP if is_utf8 else chr(0xA0) - do_unicode = is_utf8 and sys.version_info[0] <= 2 - - for line in spec_lines: - - pkg.current_linenum += 1 - - if do_unicode: - line = unicode(line, "utf-8", "replace") # noqa false positive - - char = line.find(nbsp) - if char != -1: - printWarning(pkg, "non-break-space", "line %s, char %d" % - (pkg.current_linenum, char)) - - section_marker = False - for sec, regex in section_regexs.items(): - res = regex.search(line) - if res: - current_section = sec - section_marker = True - section[sec] = section.get(sec, 0) + 1 - if sec in ('package', 'files'): - rest = filelist_regex.sub('', line[res.end() - 1:]) - res = pkgname_regex.search(rest) - if res: - current_package = res.group(1) - else: - current_package = None - break - - if section_marker: - - if not is_lib_pkg and lib_package_regex.search(line): - is_lib_pkg = True - - continue - - if (current_section in Pkg.RPM_SCRIPTLETS + ('prep', 'build') and - contains_buildroot(line)): - printWarning(pkg, 'rpm-buildroot-usage', '%' + current_section, - line[:-1].strip()) - - if make_check_regex.search(line) and current_section not in \ - ('check', 'changelog', 'package', 'description'): - printWarning(pkg, 'make-check-outside-check-section', - line[:-1]) - - if current_section in buildroot_clean and \ - not buildroot_clean[current_section] and \ - contains_buildroot(line) and rm_regex.search(line): - buildroot_clean[current_section] = True - - if ifarch_regex.search(line): - if_depth = if_depth + 1 - ifarch_depth = if_depth - - if if_regex.search(line): - if_depth = if_depth + 1 - - 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 setup_ab_regex.search(line): - printWarning(pkg, 'setup-not-quiet') - else: - printWarning(pkg, 'setup-not-quiet') - if current_section != 'prep': - printWarning(pkg, 'setup-not-in-prep') - elif autopatch_regex.search(line): - patches_auto_applied = True - if current_section != 'prep': - printWarning(pkg, '%autopatch-not-in-prep') - else: - res = autosetup_regex.search(line) - if res: - if not autosetup_n_regex.search(res.group(1)): - patches_auto_applied = True - if current_section != 'prep': - printWarning(pkg, '%autosetup-not-in-prep') - - if endif_regex.search(line): - if ifarch_depth == if_depth: - ifarch_depth = -1 - if_depth = if_depth - 1 - - res = applied_patch_regex.search(line) - if res: - pnum = res.group(1) or 0 - for tmp in applied_patch_p_regex.findall(line) or [pnum]: - pnum = int(tmp) - applied_patches.append(pnum) - if ifarch_depth > 0: - applied_patches_ifarch.append(pnum) - else: - res = applied_patch_pipe_regex.search(line) - if res: - pnum = int(res.group(1)) - applied_patches.append(pnum) - if ifarch_depth > 0: - applied_patches_ifarch.append(pnum) - else: - res = applied_patch_i_regex.search(line) - if res: - pnum = int(res.group(1)) - applied_patches.append(pnum) - if ifarch_depth > 0: - applied_patches_ifarch.append(pnum) - if not res and not source_dir: - res = source_dir_regex.search(line) - if res: - source_dir = True - printError(pkg, "use-of-RPM_SOURCE_DIR") - - if configure_linenum: - if configure_cmdline[-1] == "\\": - configure_cmdline = configure_cmdline[:-1] + line.strip() - else: - res = configure_libdir_spec_regex.search(configure_cmdline) - if not res: - # Hack to get the correct (start of ./configure) line - # number displayed: - real_linenum = pkg.current_linenum - pkg.current_linenum = configure_linenum - printWarning(pkg, "configure-without-libdir-spec") - pkg.current_linenum = real_linenum - elif res.group(1): - res = re.match(hardcoded_library_paths, res.group(1)) - if res: - printError(pkg, "hardcoded-library-path", - res.group(1), "in configure options") - configure_linenum = None - - hashPos = line.find("#") - - if current_section != 'changelog': - cfgPos = line.find('./configure') - if cfgPos != -1 and (hashPos == -1 or hashPos > cfgPos): - # store line where it started - configure_linenum = pkg.current_linenum - configure_cmdline = line.strip() - - res = hardcoded_library_path_regex.search(line) - if current_section != 'changelog' and res and not \ - (biarch_package_regex.match(pkg.name) or - hardcoded_lib_path_exceptions_regex.search( - res.group(1).lstrip())): - printError(pkg, "hardcoded-library-path", "in", - res.group(1).lstrip()) - - if '%mklibname' in line: - mklibname = True - - if current_section == 'package': - - # Would be cleaner to get sources and patches from the - # specfile parsed in Python (see below), but we want to - # catch %ifarch'd etc ones as well, and also catch these when - # the specfile is not parseable. - - res = patch_regex.search(line) - if res: - pnum = int(res.group(1) or 0) - patches[pnum] = res.group(2) - - res = obsolete_tags_regex.search(line) - if res: - printWarning(pkg, "obsolete-tag", res.group(1)) - - res = buildroot_regex.search(line) - if res: - buildroot = True - if res.group(1).startswith('/'): - printWarning(pkg, 'hardcoded-path-in-buildroot-tag', - res.group(1)) - - res = buildarch_regex.search(line) - if res: - if res.group(1) != "noarch": - printError(pkg, - 'buildarch-instead-of-exclusivearch-tag', - res.group(1)) - else: - package_noarch[current_package] = True - - res = packager_regex.search(line) - if res: - printWarning(pkg, 'hardcoded-packager-tag', res.group(1)) - - res = prefix_regex.search(line) - if res: - if not res.group(1).startswith('%'): - printWarning(pkg, 'hardcoded-prefix-tag', res.group(1)) - - res = prereq_regex.search(line) - if res: - printError(pkg, 'prereq-use', res.group(2)) - - res = buildprereq_regex.search(line) - if res: - printError(pkg, 'buildprereq-use', res.group(1)) - - if scriptlet_requires_regex.search(line): - printError(pkg, 'broken-syntax-in-scriptlet-requires', - line.strip()) - - res = requires_regex.search(line) - if res: - reqs = Pkg.parse_deps(res.group(1)) - for req in unversioned(reqs): - if compop_regex.search(req): - printWarning(pkg, - 'comparison-operator-in-deptoken', - req) - - res = provides_regex.search(line) - if res: - provs = Pkg.parse_deps(res.group(1)) - for prov in unversioned(provs): - if not prov.startswith('/'): - printWarning(pkg, 'unversioned-explicit-provides', - prov) - if compop_regex.search(prov): - printWarning(pkg, - 'comparison-operator-in-deptoken', - prov) - - res = obsoletes_regex.search(line) - if res: - obses = Pkg.parse_deps(res.group(1)) - for obs in unversioned(obses): - if not obs.startswith('/'): - printWarning(pkg, 'unversioned-explicit-obsoletes', - obs) - if compop_regex.search(obs): - printWarning(pkg, - 'comparison-operator-in-deptoken', - obs) - - res = conflicts_regex.search(line) - if res: - confs = Pkg.parse_deps(res.group(1)) - for conf in unversioned(confs): - if compop_regex.search(conf): - printWarning(pkg, - 'comparison-operator-in-deptoken', - conf) - - if current_section == 'changelog': - for match in AbstractCheck.macro_regex.findall(line): - res = re.match('%+', match) - if len(res.group(0)) % 2: - printWarning(pkg, 'macro-in-%changelog', match) - else: - if not depscript_override: - depscript_override = \ - depscript_override_regex.search(line) is not None - if not depgen_disabled: - depgen_disabled = \ - depgen_disable_regex.search(line) is not None - if not patch_fuzz_override: - patch_fuzz_override = \ - patch_fuzz_override_regex.search(line) is not None - - if current_section == 'files': - # TODO: check scriptlets for these too? - if package_noarch.get(current_package) or \ - (current_package not in package_noarch and - package_noarch.get(None)): - res = libdir_regex.search(line) - if res: - pkgname = current_package - if pkgname is None: - pkgname = '(main package)' - printWarning(pkg, 'libdir-macro-in-noarch-package', - pkgname, line.rstrip()) - - if not indent_tabs and '\t' in line: - indent_tabs = pkg.current_linenum - if not indent_spaces and indent_spaces_regex.search(line): - indent_spaces = pkg.current_linenum - - # Check if egrep or fgrep is used - if current_section not in \ - ('package', 'changelog', 'description', 'files'): - greps = deprecated_grep_regex.findall(line) - if greps: - printWarning(pkg, "deprecated-grep", greps) - - # If not checking spec file only, we're checking one inside a - # SRPM -> skip this check to avoid duplicate warnings (#167) - if spec_only and VALID_GROUPS and \ - line.lower().startswith("group:"): - group = line[6:].strip() - if group not in VALID_GROUPS: - printWarning(pkg, 'non-standard-group', group) - - # Test if there are macros in comments - if hashPos != -1 and \ - (hashPos == 0 or line[hashPos - 1] in (" ", "\t")): - for match in AbstractCheck.macro_regex.findall( - line[hashPos + 1:]): - res = re.match('%+', match) - if len(res.group(0)) % 2: - printWarning(pkg, 'macro-in-comment', match) - - # Last line read is not useful after this point - pkg.current_linenum = None - - for sect in (x for x in buildroot_clean if not buildroot_clean[x]): - printWarning(pkg, 'no-cleaning-of-buildroot', '%' + sect) - - if not buildroot: - printWarning(pkg, 'no-buildroot-tag') - - for sec in ('prep', 'build', 'install', 'clean'): - if not section.get(sec): - printWarning(pkg, 'no-%%%s-section' % sec) - for sec in ('changelog',): - # prep, build, install, clean, check prevented by rpmbuild 4.4 - if section.get(sec, 0) > 1: - printWarning(pkg, 'more-than-one-%%%s-section' % sec) - - if is_lib_pkg and not mklibname: - printError(pkg, 'lib-package-without-%mklibname') - - if depscript_override and not depgen_disabled: - printWarning(pkg, 'depscript-without-disabling-depgen') - - if patch_fuzz_override: - printWarning(pkg, 'patch-fuzz-is-changed') - - if indent_spaces and indent_tabs: - pkg.current_linenum = max(indent_spaces, indent_tabs) - printWarning(pkg, 'mixed-use-of-spaces-and-tabs', - '(spaces: line %d, tab: line %d)' % - (indent_spaces, indent_tabs)) - pkg.current_linenum = None - - # process gathered info - if not patches_auto_applied: - for pnum, pfile in patches.items(): - if pnum in applied_patches_ifarch: - printWarning(pkg, "%ifarch-applied-patch", - "Patch%d:" % pnum, pfile) - if pnum not in applied_patches: - printWarning(pkg, "patch-not-applied", - "Patch%d:" % pnum, pfile) - - # Rest of the checks require a real spec file - if not self._spec_file: - return - - # We'd like to parse the specfile only once using python bindings, - # but it seems errors from rpmlib get logged to stderr and we can't - # capture and print them nicely, so we do it once each way :P - - out = Pkg.getstatusoutput( - ('rpm', '-q', '--qf=', '-D', '_sourcedir %s' % pkg.dirName(), '--specfile', self._spec_file)) - parse_error = False - for line in out[1].splitlines(): - # No such file or dir hack: https://bugzilla.redhat.com/487855 - if 'No such file or directory' not in line: - parse_error = True - printError(pkg, 'specfile-error', line) - - if not parse_error: - # grab sources and patches from parsed spec object to get - # them with macros expanded for URL checking - - spec_obj = None - rpm.addMacro('_sourcedir', pkg.dirName()) - try: - ts = rpm.TransactionSet() - spec_obj = ts.parseSpec(self._spec_file) - except (ValueError, rpm.error): - # errors logged above already - pass - rpm.delMacro('_sourcedir') - if spec_obj: - try: - # rpm < 4.8.0 - sources = spec_obj.sources() - except TypeError: - # rpm >= 4.8.0 - sources = spec_obj.sources - for src in sources: - (url, num, flags) = src - (scheme, netloc) = urlparse(url)[0:2] - if flags & 1: # rpmspec.h, rpm.org ticket #123 - srctype = "Source" - else: - srctype = "Patch" - tag = '%s%s' % (srctype, num) - if scheme and netloc: - info = self.check_url(pkg, tag, url) - if not info or not hasattr(pkg, 'files'): - continue - clen = info.get("Content-Length") - if clen is not None: - clen = int(clen) - cmd5 = info.get("Content-MD5") - if cmd5 is not None: - cmd5 = cmd5.lower() - if clen is not None or cmd5 is not None: - # Not using path from urlparse results to match how - # rpm itself parses the basename. - pkgfile = pkg.files().get(url.split("/")[-1]) - if pkgfile: - if clen is not None and pkgfile.size != clen: - printWarning(pkg, 'file-size-mismatch', - '%s = %s, %s = %s' % - (pkgfile.name, pkgfile.size, - url, clen)) - # pkgfile.md5 could be some other digest than - # MD5, treat as MD5 only if it's 32 chars long - if cmd5 and len(pkgfile.md5) == 32 \ - and pkgfile.md5 != cmd5: - printWarning(pkg, 'file-md5-mismatch', - '%s = %s, %s = %s' % - (pkgfile.name, pkgfile.md5, - url, cmd5)) - elif srctype == "Source" and tarball_regex.search(url): - printWarning(pkg, 'invalid-url', '%s:' % tag, url) - - -# Create an object to enable the auto registration of the test -check = SpecCheck() - -# Add information about checks -addDetails( -'no-spec-file', -'''No spec file was specified in your RPM building. Please specify a valid -SPEC file to build a valid RPM package.''', - -'invalid-spec-name', -'''The spec file name (without the .spec suffix) must match the package name -("Name:" tag). Either rename your package or the specfile.''', - -'non-utf8-spec-file', -'''The character encoding of the spec file is not UTF-8. Convert it for -example using iconv(1).''', - -'use-of-RPM_SOURCE_DIR', -'''You use $RPM_SOURCE_DIR or %{_sourcedir} in your spec file. If you have to -use a directory for building, use $RPM_BUILD_ROOT instead.''', - -'patch-not-applied', -'''A patch is included in your package but was not applied. Refer to the -patches documentation to see what's wrong.''', - -'obsolete-tag', -'''The following tags are obsolete: Copyright and Serial. They must -be replaced by License and Epoch respectively.''', - -'deprecated-grep', -'''Direct use of grep as egrep or fgrep is deprecated in GNU grep and -historical in POSIX, use grep -E and grep -F instead.''', - -'no-buildroot-tag', -'''The BuildRoot tag isn't used in your spec. It must be used in order to -allow building the package as non root on some systems. For some rpm versions -(e.g. rpm.org >= 4.6) the BuildRoot tag is not necessary in specfiles and is -ignored by rpmbuild; if your package is only going to be built with such rpm -versions you can ignore this warning.''', - -'hardcoded-path-in-buildroot-tag', -'''A path is hardcoded in your Buildroot tag. It should be replaced -by something like %{_tmppath}/%{name}-%{version}-build.''', - -'hardcoded-packager-tag', -'''The Packager tag is hardcoded in your spec file. It should be removed, so -as to use rebuilder's own defaults.''', - -'buildarch-instead-of-exclusivearch-tag', -'''Use ExclusiveArch instead of BuildArch (or BuildArchitectures) -to restrict build on some specific architectures. -Only use BuildArch with noarch''', - -'hardcoded-prefix-tag', -'''The Prefix tag is hardcoded in your spec file. It should be removed, so as -to allow package relocation.''', - -'hardcoded-library-path', -'''A library path is hardcoded to one of the following paths: /lib, -/usr/lib. It should be replaced by something like /%{_lib} or %{_libdir}.''', - -'configure-without-libdir-spec', -'''A configure script is run without specifying the libdir. configure -options must be augmented with something like --libdir=%{_libdir} whenever -the script supports it.''', - -'no-%prep-section', -'''The spec file does not contain a %prep section. Even if some packages don't -directly need it, section markers may be overridden in rpm's configuration -to provide additional "under the hood" functionality. Add the section, even -if empty.''', - -'no-%build-section', -'''The spec file does not contain a %build section. Even if some packages -don't directly need it, section markers may be overridden in rpm's -configuration to provide additional "under the hood" functionality, such as -injection of automatic -debuginfo subpackages. Add the section, even if -empty.''', - -'no-%install-section', -'''The spec file does not contain an %install section. Even if some packages -don't directly need it, section markers may be overridden in rpm's -configuration to provide additional "under the hood" functionality. Add the -section, even if empty.''', - -'no-%clean-section', -'''The spec file doesn't contain a %clean section to remove the files installed -by the %install section.''', - -'more-than-one-%changelog-section', -'''The spec file unnecessarily contains more than one %changelog section; -remove the extra ones.''', - -'lib-package-without-%mklibname', -'''The package name must be built using %mklibname to allow lib64 and lib32 -coexistence.''', - -'%ifarch-applied-patch', -'''A patch is applied inside an %ifarch block. Patches must be applied -on all architectures and may contain necessary configure and/or code -patch to be effective only on a given arch.''', - -'prereq-use', -'''The use of PreReq is deprecated. In the majority of cases, a plain Requires -is enough and the right thing to do. Sometimes Requires(pre), Requires(post), -Requires(preun) and/or Requires(postun) can also be used instead of PreReq.''', - -'buildprereq-use', -'''The use of BuildPreReq is deprecated, build dependencies are always required -before a package can be built. Use plain BuildRequires instead.''', - -'broken-syntax-in-scriptlet-requires', -'''Comma separated context marked dependencies are silently broken in some -versions of rpm. One way to work around it is to split them into several ones, -eg. replace "Requires(post,preun): foo" with "Requires(post): foo" and -"Requires(preun): foo".''', - -'setup-not-in-prep', -'''The %setup macro should only be used within the %prep section because it may -not expand to anything outside of it and can break the build in unpredictable -ways.''', - -'setup-not-quiet', -'''Use the -q option to the %setup macro to avoid useless build output from -unpacking the sources.''', - -'no-cleaning-of-buildroot', -'''You should clean $RPM_BUILD_ROOT in the %clean section and in the beginning -of the %install section. Use "rm -rf $RPM_BUILD_ROOT". Some rpm configurations -do this automatically; if your package is only going to be built in such -configurations, you can ignore this warning for the section(s) where your rpm -takes care of it.''', - -'rpm-buildroot-usage', -'''$RPM_BUILD_ROOT should not be touched during %build or %prep stage, as it -may break short circuit builds.''', - -'make-check-outside-check-section', -'''Make check or other automated regression test should be run in %check, as -they can be disabled with a rpm macro for short circuiting purposes.''', - -'macro-in-%changelog', -'''Macros are expanded in %changelog too, which can in unfortunate cases lead -to the package not building at all, or other subtle unexpected conditions that -affect the build. Even when that doesn\'t happen, the expansion results in -possibly "rewriting history" on subsequent package revisions and generally -odd entries eg. in source rpms, which is rarely wanted. Avoid use of macros -in %changelog altogether, or use two '%'s to escape them, like '%%foo'.''', - -'depscript-without-disabling-depgen', -'''In some common rpm configurations/versions, defining __find_provides and/or -__find_requires has no effect if rpm's internal dependency generator has not -been disabled for the build. %define _use_internal_dependency_generator to 0 -to disable it in the specfile, or don't define __find_provides/requires.''', - -'mixed-use-of-spaces-and-tabs', -'''The specfile mixes use of spaces and tabs for indentation, which is a -cosmetic annoyance. Use either spaces or tabs for indentation, not both.''', - -'unversioned-explicit-provides', -'''The specfile contains an unversioned Provides: token, which will match all -older, equal, and newer versions of the provided thing. This may cause -update problems and will make versioned dependencies, obsoletions and conflicts -on the provided thing useless -- make the Provides versioned if possible.''', - -'unversioned-explicit-obsoletes', -'''The specfile contains an unversioned Obsoletes: token, which will match all -older, equal and newer versions of the obsoleted thing. This may cause update -problems, restrict future package/provides naming, and may match something it -was originally not inteded to match -- make the Obsoletes versioned if -possible.''', - -'libdir-macro-in-noarch-package', -'''The %{_libdir} or %{_lib} macro was found in a noarch package in a section -that gets included in binary packages. This is most likely an error because -these macros are expanded on the build host and their values vary between -architectures, probably resulting in a package that does not work properly -on all architectures at runtime. Investigate whether the package is really -architecture independent or if some other dir/macro should be instead.''', - -'non-break-space', -'''The spec file contains a non-break space, which looks like a regular space -in some editors but can lead to obscure errors. It should be replaced by a -regular space.''', - -'non-standard-group', -'''The value of the Group tag in the package is not valid. Valid groups are: -"%s".''' % '", "'.join(VALID_GROUPS), - -'specfile-error', -'''This error occurred when rpmlint used rpm to query the specfile. The error -is output by rpm and the message should contain more information.''', - -'comparison-operator-in-deptoken', -'''This dependency token contains a comparison operator (<, > or =). This is -usually not intended and may be caused by missing whitespace between the -token's name, the comparison operator and the version string.''', - -'macro-in-comment', -'''There is a unescaped macro after a shell style comment in the specfile. -Macros are expanded everywhere, so check if it can cause a problem in this -case and escape the macro with another leading % if appropriate.''', - -'file-size-mismatch', -'''The size of the file in the package does not match the size indicated by -peeking at its URL. Verify that the file in the package has the intended -contents.''', - -'file-md5-mismatch', -'''The MD5 hash of the file in the package does not match the MD5 hash -indicated by peeking at its URL. Verify that the file in the package has the -intended contents.''', - -'patch-fuzz-is-changed', -'''The internal patch fuzz value was changed, and could hide patchs issues, or -could lead to applying a patch at the wrong location. Usually, this is often -the sign that someone didn't check if a patch is still needed and do not want -to rediff it. It is usually better to rediff the patch and try to send it -upstream.''' -) - -# SpecCheck.py ends here diff --git a/TagsCheck.py b/TagsCheck.py deleted file mode 100644 index 4e32520..0000000 --- a/TagsCheck.py +++ /dev/null @@ -1,1166 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : TagsCheck.py -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Tue Sep 28 00:03:24 1999 -# Purpose : Check a package to see if some rpm tags are present -############################################################################# - -import calendar -import os -import re -import time -try: - from urlparse import urlparse -except ImportError: # Python 3 - from urllib.parse import urlparse - -import rpm - -import AbstractCheck -import Config -import FilesCheck -from Filter import addDetails, printError, printInfo, printWarning -import Pkg - -_use_enchant = Config.getOption("UseEnchant", None) -if _use_enchant or _use_enchant is None: - try: - import enchant - import enchant.checker - except ImportError: - enchant = None -else: - enchant = None -del _use_enchant - -DEFAULT_VALID_LICENSES = ( - # OSI approved licenses, http://www.opensource.org/licenses/ (unversioned, - # trailing "license" dropped based on fuzzy logic, and in well-known cases, - # the abbreviation used instead of the full name, but list kept sorted by - # the full name). Updated 2010-02-01. - 'Academic Free License', - 'Adaptive Public License', - 'AGPLv3', # Affero GNU Public License - 'AGPLv3+', # Affero GNU Public License - 'Apache License', - 'Apache Software License', - 'Apple Public Source License', - 'Artistic', - 'Attribution Assurance License', - 'BSD', - 'Boost Software License', - 'Computer Associates Trusted Open Source License', - 'CDDL', # Common Development and Distribution License - 'Common Public Attribution License', - 'CUA Office Public License', - 'EU DataGrid Software License', - 'Eclipse Public License', - 'Educational Community License', - 'Eiffel Forum License', - 'Entessa Public License', - 'European Union Public License', - 'Fair License', - 'Frameworx License', - 'GPLv1', - 'GPLv1+', - 'GPLv2', - 'GPLv2+', - 'GPLv3', - 'GPLv3+', - 'LGPLv2', - 'LGPLv2+', - 'LGPLv3', - 'LGPLv3+', - 'Historical Permission Notice and Disclaimer', - 'IBM Public License', - 'IPA Font License', - 'ISC License', - 'Lucent Public License', - 'Microsoft Public License', - 'Microsoft Reciprocal License', - 'MirOS License', - 'MIT', - 'Motosoto License', - 'MPL', # Mozilla Public License - 'Multics License', - 'NASA Open Source Agreement', - 'Naumen Public License', - 'Nethack General Public License', - 'Nokia Open Source License', - 'Non-profit Open Software License', - 'NTP License', - 'OCLC Research Public License', - 'OFL', # Open Font License - 'Open Group Test Suite License', - 'Open Software License', - 'PHP License', - 'Python license', # CNRI Python License - 'Python Software Foundation License', - 'QPL', # Qt Public License - 'RealNetworks Public Source License', - 'Reciprocal Public License', - 'Ricoh Source Code Public License', - 'Simple Public License', - 'Sleepycat License', - 'Sun Public License', - 'Sybase Open Watcom Public License', - 'University of Illinois/NCSA Open Source License', - 'Vovida Software License', - 'W3C License', - 'wxWindows Library License', - 'X.Net License', - 'Zope Public License', - 'zlib/libpng License', - # Creative commons licenses, http://creativecommons.org/licenses/: - 'Creative Commons Attribution', - 'Creative Commons Attribution-NoDerivs', - 'Creative Commons Attribution-NonCommercial-NoDerivs', - 'Creative Commons Attribution-NonCommercial', - 'Creative Commons Attribution-NonCommercial-ShareAlike', - 'Creative Commons Attribution-ShareAlike', - # Others: - 'Design Public License', # ??? - 'FSFAP', # FSF All Permissive license - 'GFDL', # GNU Free Documentation License - 'LaTeX Project Public License', - 'OpenContent License', - 'Open Publication License', - 'Public Domain', - 'Ruby License', - 'SIL Open Font License', - # Non open source licences: - 'Charityware', - 'Commercial', - 'Distributable', - 'Freeware', - 'Non-distributable', - 'Proprietary', - 'Shareware', -) - -BAD_WORDS = { - 'alot': 'a lot', - 'accesnt': 'accent', - 'accelleration': 'acceleration', - 'accessable': 'accessible', - 'accomodate': 'accommodate', - 'acess': 'access', - 'acording': 'according', - 'additionaly': 'additionally', - 'adress': 'address', - 'adresses': 'addresses', - 'adviced': 'advised', - 'albumns': 'albums', - 'alegorical': 'allegorical', - 'algorith': 'algorithm', - 'allpication': 'application', - 'altough': 'although', - 'alows': 'allows', - 'amoung': 'among', - 'amout': 'amount', - 'analysator': 'analyzer', - 'ang': 'and', - 'appropiate': 'appropriate', - 'arraival': 'arrival', - 'artifical': 'artificial', - 'artillary': 'artillery', - 'attemps': 'attempts', - 'automatize': 'automate', - 'automatized': 'automated', - 'automatizes': 'automates', - 'auxilliary': 'auxiliary', - 'availavility': 'availability', - 'availble': 'available', - 'avaliable': 'available', - 'availiable': 'available', - 'backgroud': 'background', - 'baloons': 'balloons', - 'becomming': 'becoming', - 'becuase': 'because', - 'cariage': 'carriage', - 'challanges': 'challenges', - 'changable': 'changeable', - 'charachters': 'characters', - 'charcter': 'character', - 'choosen': 'chosen', - 'colorfull': 'colorful', - 'comand': 'command', - 'commerical': 'commercial', - 'comminucation': 'communication', - 'commoditiy': 'commodity', - 'compability': 'compatibility', - 'compatability': 'compatibility', - 'compatable': 'compatible', - 'compatibiliy': 'compatibility', - 'compatibilty': 'compatibility', - 'compleatly': 'completely', - 'complient': 'compliant', - 'compres': 'compress', - 'containes': 'contains', - 'containts': 'contains', - 'contence': 'contents', - 'continous': 'continuous', - 'contraints': 'constraints', - 'convertor': 'converter', - 'convinient': 'convenient', - 'cryptocraphic': 'cryptographic', - 'deamon': 'daemon', - 'debians': 'Debian\'s', - 'decompres': 'decompress', - 'definate': 'definite', - 'definately': 'definitely', - 'dependancies': 'dependencies', - 'dependancy': 'dependency', - 'dependant': 'dependent', - 'developement': 'development', - 'developped': 'developed', - 'deveolpment': 'development', - 'devided': 'divided', - 'dictionnary': 'dictionary', - 'diplay': 'display', - 'disapeared': 'disappeared', - 'dissapears': 'disappears', - 'documentaion': 'documentation', - 'docuentation': 'documentation', - 'documantation': 'documentation', - 'dont': 'don\'t', - 'easilly': 'easily', - 'ecspecially': 'especially', - 'edditable': 'editable', - 'editting': 'editing', - 'eletronic': 'electronic', - 'enchanced': 'enhanced', - 'encorporating': 'incorporating', - 'enlightnment': 'enlightenment', - 'enterily': 'entirely', - 'enviroiment': 'environment', - 'environement': 'environment', - 'excellant': 'excellent', - 'exlcude': 'exclude', - 'exprimental': 'experimental', - 'extention': 'extension', - 'failuer': 'failure', - 'familar': 'familiar', - 'fatser': 'faster', - 'fetaures': 'features', - 'forse': 'force', - 'fortan': 'fortran', - 'framwork': 'framework', - 'fuction': 'function', - 'fuctions': 'functions', - 'functionnality': 'functionality', - 'functonality': 'functionality', - 'functionaly': 'functionally', - 'futhermore': 'furthermore', - 'generiously': 'generously', - 'grahical': 'graphical', - 'grahpical': 'graphical', - 'grapic': 'graphic', - 'guage': 'gauge', - 'halfs': 'halves', - 'heirarchically': 'hierarchically', - 'helpfull': 'helpful', - 'hierachy': 'hierarchy', - 'hierarchie': 'hierarchy', - 'howver': 'however', - 'implemantation': 'implementation', - 'incomming': 'incoming', - 'incompatabilities': 'incompatibilities', - 'indended': 'intended', - 'indendation': 'indentation', - 'independant': 'independent', - 'informatiom': 'information', - 'initalize': 'initialize', - 'inofficial': 'unofficial', - 'integreated': 'integrated', - 'integrety': 'integrity', - 'integrey': 'integrity', - 'intendet': 'intended', - 'interchangable': 'interchangeable', - 'intermittant': 'intermittent', - 'jave': 'java', - 'langage': 'language', - 'langauage': 'language', - 'langugage': 'language', - 'lauch': 'launch', - 'lesstiff': 'lesstif', - 'libaries': 'libraries', - 'licenceing': 'licencing', - 'loggin': 'login', - 'logile': 'logfile', - 'loggging': 'logging', - 'mandrivalinux': 'Mandriva Linux', - 'maintainance': 'maintenance', - 'maintainence': 'maintenance', - 'makeing': 'making', - 'managable': 'manageable', - 'manoeuvering': 'maneuvering', - 'ment': 'meant', - 'modulues': 'modules', - 'monochromo': 'monochrome', - 'multidimensionnal': 'multidimensional', - 'navagating': 'navigating', - 'nead': 'need', - 'neccesary': 'necessary', - 'neccessary': 'necessary', - 'necesary': 'necessary', - 'nescessary': 'necessary', - 'noticable': 'noticeable', - 'optionnal': 'optional', - 'orientied': 'oriented', - 'pacakge': 'package', - 'pachage': 'package', - 'packacge': 'package', - 'packege': 'package', - 'packge': 'package', - 'pakage': 'package', - 'particularily': 'particularly', - 'persistant': 'persistent', - 'plattform': 'platform', - 'ploting': 'plotting', - 'posible': 'possible', - 'powerfull': 'powerful', - 'prefered': 'preferred', - 'prefferably': 'preferably', - 'prepaired': 'prepared', - 'princliple': 'principle', - 'priorty': 'priority', - 'proccesors': 'processors', - 'proces': 'process', - 'processsing': 'processing', - 'processessing': 'processing', - 'progams': 'programs', - 'programers': 'programmers', - 'programm': 'program', - 'programms': 'programs', - 'promps': 'prompts', - 'pronnounced': 'pronounced', - 'prononciation': 'pronunciation', - 'pronouce': 'pronounce', - 'protcol': 'protocol', - 'protocoll': 'protocol', - 'recieve': 'receive', - 'recieved': 'received', - 'redircet': 'redirect', - 'regulamentations': 'regulations', - 'remoote': 'remote', - 'repectively': 'respectively', - 'replacments': 'replacements', - 'requiere': 'require', - 'runnning': 'running', - 'safly': 'safely', - 'savable': 'saveable', - 'searchs': 'searches', - 'separatly': 'separately', - 'seperate': 'separate', - 'seperately': 'separately', - 'seperatly': 'separately', - 'serveral': 'several', - 'setts': 'sets', - 'similiar': 'similar', - 'simliar': 'similar', - 'speach': 'speech', - 'standart': 'standard', - 'staically': 'statically', - 'staticly': 'statically', - 'succesful': 'successful', - 'succesfully': 'successfully', - 'suplied': 'supplied', - 'suport': 'support', - 'suppport': 'support', - 'supportin': 'supporting', - 'synchonized': 'synchronized', - 'syncronize': 'synchronize', - 'syncronizing': 'synchronizing', - 'syncronus': 'synchronous', - 'syste': 'system', - 'sythesis': 'synthesis', - 'taht': 'that', - 'throught': 'through', - 'useable': 'usable', - 'usefull': 'useful', - 'usera': 'users', - 'usetnet': 'Usenet', - 'utilites': 'utilities', - 'utillities': 'utilities', - 'utilties': 'utilities', - 'utiltity': 'utility', - 'utitlty': 'utility', - 'variantions': 'variations', - 'varient': 'variant', - 'verson': 'version', - 'vicefersa': 'vice-versa', - 'yur': 'your', - 'wheter': 'whether', - 'wierd': 'weird', - 'xwindows': 'X' -} - -CAPITALIZED_IGNORE_LIST = ( - 'jQuery', 'openSUSE', 'wxWidgets', 'a', 'an', 'uWSGI') - -DEFAULT_INVALID_REQUIRES = ('^is$', '^not$', '^owned$', '^by$', '^any$', - '^package$', r'^libsafe\.so\.') - -VALID_GROUPS = Config.getOption('ValidGroups', None) -if VALID_GROUPS is None: # get defaults from rpm package only if it's not set - VALID_GROUPS = Pkg.get_default_valid_rpmgroups() -VALID_LICENSES = Config.getOption('ValidLicenses', DEFAULT_VALID_LICENSES) -INVALID_REQUIRES = map(re.compile, Config.getOption('InvalidRequires', DEFAULT_INVALID_REQUIRES)) -packager_regex = re.compile(Config.getOption('Packager')) -changelog_version_regex = re.compile(r'[^>]([^ >]+)\s*$') -changelog_text_version_regex = re.compile(r'^\s*-\s*((\d+:)?[\w\.]+-[\w\.]+)') -release_ext = Config.getOption('ReleaseExtension') -extension_regex = release_ext and re.compile(release_ext) -use_version_in_changelog = Config.getOption('UseVersionInChangelog', True) -devel_number_regex = re.compile(r'(.*?)([0-9.]+)(_[0-9.]+)?-devel') -lib_devel_number_regex = re.compile(r'^lib(.*?)([0-9.]+)(_[0-9.]+)?-devel') -invalid_url_regex = re.compile(Config.getOption('InvalidURL'), re.IGNORECASE) -lib_package_regex = re.compile(r'(?:^(?:compat-)?lib.*?(\.so.*)?|libs?[\d-]*)$', re.IGNORECASE) -leading_space_regex = re.compile(r'^\s+') -license_regex = re.compile(r'\(([^)]+)\)|\s(?:and|or|AND|OR)\s') -invalid_version_regex = re.compile(r'([0-9](?:rc|alpha|beta|pre).*)', re.IGNORECASE) -# () are here for grouping purpose in the regexp -forbidden_words_regex = re.compile(r'(%s)' % Config.getOption('ForbiddenWords'), re.IGNORECASE) -valid_buildhost_regex = re.compile(Config.getOption('ValidBuildHost')) -use_epoch = Config.getOption('UseEpoch', False) -use_utf8 = Config.getOption('UseUTF8', Config.USEUTF8_DEFAULT) -max_line_len = Config.getOption('MaxLineLength', 79) -tag_regex = re.compile(r'^((?: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) -punct = '.,:;!?' -sentence_break_regex = re.compile(r'(^|[.:;!?])\s*$') -so_dep_regex = re.compile(r'\.so(\.[0-9a-zA-z]+)*(\([^)]*\))*$') -# we assume that no rpm packages existed before rpm itself existed... -oldest_changelog_timestamp = calendar.timegm(time.strptime("1995-01-01", "%Y-%m-%d")) - -private_so_paths = set() -for path in ('%perl_archlib', '%perl_vendorarch', '%perl_sitearch', - '%python_sitearch', '%python2_sitearch', '%python3_sitearch', - '%ruby_sitearch', '%php_extdir'): - epath = rpm.expandMacro(path) - if epath and epath != path: - private_so_paths.add(epath) - private_so_paths.add(re.sub(r'/lib64(?=/|$)', '/lib', epath)) - private_so_paths.add(re.sub(r'/lib(?=/|$)', '/lib64', epath)) - -_enchant_checkers = {} - - -def spell_check(pkg, str, fmt, lang, ignored): - - dict_found = True - warned = set() - if enchant: - if lang == 'C': - lang = 'en_US' - - checker = _enchant_checkers.get(lang) - if not checker and lang not in _enchant_checkers: - try: - checker = enchant.checker.SpellChecker( - lang, filters=[enchant.tokenize.EmailFilter, - enchant.tokenize.URLFilter, - enchant.tokenize.WikiWordFilter]) - except enchant.DictNotFoundError: - printInfo(pkg, 'enchant-dictionary-not-found', lang) - pass - _enchant_checkers[lang] = checker - - if checker: - # squeeze whitespace to ease leading context check - checker.set_text(re.sub(r'\s+', ' ', str)) - if use_utf8: - 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] - upperparts.extend(ups) - for err in checker: - - # Skip already warned and ignored words - if err.word in warned or err.word in ignored: - continue - - # Skip all capitalized words that do not start a sentence - if err.word[0].isupper() and not \ - sentence_break_regex.search(checker.leading_context(3)): - continue - - upperword = err.word.upper() - - # Skip all uppercase words - if err.word == upperword: - continue - - # Skip errors containing package name or equal to a - # "component" of it, case insensitively - if uppername in upperword or upperword in upperparts: - continue - - # Work around enchant's digit tokenizing behavior: - # http://github.com/rfk/pyenchant/issues/issue/3 - if checker.leading_context(1).isdigit() or \ - checker.trailing_context(1).isdigit(): - continue - - # Warn and suggest - sug = ', '.join(checker.suggest()[:3]) - if sug: - sug = '-> %s' % sug - printWarning(pkg, 'spelling-error', fmt % lang, err.word, sug) - warned.add(err.word) - - else: - dict_found = False - - if not enchant or not dict_found: - for seq in str.split(): - for word in re.split(r'[^a-z]+', seq.lower()): - if len(word) == 0: - continue - correct = BAD_WORDS.get(word) - if not correct: - continue - if word[0] == '\'': - word = word[1:] - if word[-1] == '\'': - word = word[:-1] - if word in warned or word in ignored: - continue - printWarning(pkg, 'spelling-error', fmt % lang, word, '->', - correct) - warned.add(word) - - -class TagsCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, 'TagsCheck') - - def _unexpanded_macros(self, pkg, tagname, value, is_url=False): - if not value: - return - 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[rpm.RPMTAG_PACKAGER] - if packager: - self._unexpanded_macros(pkg, 'Packager', packager) - if Config.getOption('Packager') and \ - not packager_regex.search(packager): - printWarning(pkg, 'invalid-packager', packager) - else: - printError(pkg, 'no-packager-tag') - - version = pkg[rpm.RPMTAG_VERSION] - if version: - self._unexpanded_macros(pkg, 'Version', version) - res = invalid_version_regex.search(version) - if res: - printError(pkg, 'invalid-version', version) - else: - printError(pkg, 'no-version-tag') - - release = pkg[rpm.RPMTAG_RELEASE] - if release: - self._unexpanded_macros(pkg, 'Release', release) - if release_ext and not extension_regex.search(release): - printWarning(pkg, 'not-standard-release-extension', release) - else: - printError(pkg, 'no-release-tag') - - epoch = pkg[rpm.RPMTAG_EPOCH] - if epoch is None: - if use_epoch: - printError(pkg, 'no-epoch-tag') - else: - if epoch > 99: - printWarning(pkg, 'unreasonable-epoch', epoch) - epoch = str(epoch) - - if use_epoch: - for tag in ("obsoletes", "conflicts", "provides", "recommends", - "suggests", "enhances", "supplements"): - for x in (x for x in getattr(pkg, tag)() - if x[1] and x[2][0] is None): - printWarning(pkg, 'no-epoch-in-%s' % tag, - Pkg.formatRequire(*x)) - - name = pkg.name - deps = pkg.requires() + pkg.prereq() - devel_depend = False - is_devel = FilesCheck.devel_regex.search(name) - is_source = pkg.isSource() - for d in deps: - value = Pkg.formatRequire(*d) - if use_epoch and d[1] and d[2][0] is None and \ - not d[0].startswith('rpmlib('): - printWarning(pkg, 'no-epoch-in-dependency', value) - for r in INVALID_REQUIRES: - if r.search(d[0]): - printError(pkg, 'invalid-dependency', d[0]) - - if d[0].startswith('/usr/local/'): - printError(pkg, 'invalid-dependency', d[0]) - - if is_source: - if lib_devel_number_regex.search(d[0]): - printError(pkg, 'invalid-build-requires', d[0]) - elif not is_devel: - if not devel_depend and FilesCheck.devel_regex.search(d[0]): - printError(pkg, 'devel-dependency', d[0]) - devel_depend = True - if not d[1]: - res = lib_package_regex.search(d[0]) - if res and not res.group(1): - printError(pkg, 'explicit-lib-dependency', d[0]) - - if d[1] == rpm.RPMSENSE_EQUAL and d[2][2] is not None: - printWarning(pkg, 'requires-on-release', value) - self._unexpanded_macros(pkg, 'dependency %s' % (value,), value) - - self._unexpanded_macros(pkg, 'Name', name) - if not name: - printError(pkg, 'no-name-tag') - else: - if is_devel and not is_source: - base = is_devel.group(1) - dep = None - has_so = False - for fname in pkg.files(): - if fname.endswith('.so'): - has_so = True - break - if has_so: - base_or_libs = base + '/' + base + '-libs/lib' + base - # try to match *%_isa as well (e.g. "(x86-64)", "(x86-32)") - base_or_libs_re = re.compile( - r'^(lib)?%s(-libs)?(\(\w+-\d+\))?$' % re.escape(base)) - for d in deps: - if base_or_libs_re.match(d[0]): - dep = d - break - if not dep: - printWarning(pkg, 'no-dependency-on', base_or_libs) - elif version: - exp = (epoch, version, None) - sexp = Pkg.versionToString(exp) - if not dep[1]: - printWarning(pkg, 'no-version-dependency-on', - base_or_libs, sexp) - elif dep[2][:2] != exp[:2]: - printWarning(pkg, - 'incoherent-version-dependency-on', - base_or_libs, - Pkg.versionToString((dep[2][0], - dep[2][1], None)), - sexp) - res = devel_number_regex.search(name) - if not res: - printWarning(pkg, 'no-major-in-name', name) - else: - if res.group(3): - prov = res.group(1) + res.group(2) + '-devel' - else: - prov = res.group(1) + '-devel' - - if prov not in (x[0] for x in pkg.provides()): - printWarning(pkg, 'no-provides', prov) - - # List of words to ignore in spell check - ignored_words = set() - for pf in pkg.files(): - ignored_words.update(pf.split('/')) - ignored_words.update((x[0] for x in pkg.provides())) - ignored_words.update((x[0] for x in pkg.requires())) - ignored_words.update((x[0] for x in pkg.conflicts())) - ignored_words.update((x[0] for x in pkg.obsoletes())) - - langs = pkg[rpm.RPMTAG_HEADERI18NTABLE] - - summary = pkg[rpm.RPMTAG_SUMMARY] - if summary: - if not langs: - 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[rpm.RPMTAG_DESCRIPTION] - if description: - if not langs: - self._unexpanded_macros(pkg, '%description', - Pkg.b2s(description)) - else: - for lang in langs: - self.check_description(pkg, lang, ignored_words) - - if len(Pkg.b2s(description)) < len(pkg[rpm.RPMTAG_SUMMARY]): - printWarning(pkg, 'description-shorter-than-summary') - else: - printError(pkg, 'no-description-tag') - - group = pkg[rpm.RPMTAG_GROUP] - self._unexpanded_macros(pkg, 'Group', group) - if not group: - printError(pkg, 'no-group-tag') - elif VALID_GROUPS and group not in VALID_GROUPS: - printWarning(pkg, 'non-standard-group', group) - - buildhost = pkg[rpm.RPMTAG_BUILDHOST] - self._unexpanded_macros(pkg, 'BuildHost', buildhost) - if not buildhost: - printError(pkg, 'no-buildhost-tag') - elif Config.getOption('ValidBuildHost') and \ - not valid_buildhost_regex.search(buildhost): - printWarning(pkg, 'invalid-buildhost', buildhost) - - changelog = pkg[rpm.RPMTAG_CHANGELOGNAME] - if not changelog: - printError(pkg, 'no-changelogname-tag') - else: - clt = pkg[rpm.RPMTAG_CHANGELOGTEXT] - if use_version_in_changelog: - 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(Pkg.b2s(clt[0])) - if not ret: - printWarning(pkg, 'no-version-in-last-changelog') - elif version and release: - srpm = pkg[rpm.RPMTAG_SOURCERPM] or '' - # only check when source name correspond to name - if srpm[0:-8] == '%s-%s-%s' % (name, version, release): - expected = [version + '-' + release] - if epoch is not None: # regardless of use_epoch - expected[0] = str(epoch) + ':' + expected[0] - # Allow EVR in changelog without release extension, - # the extension is often a macro or otherwise dynamic. - if release_ext: - expected.append( - extension_regex.sub('', expected[0])) - if ret.group(1) not in expected: - if len(expected) == 1: - expected = expected[0] - printWarning(pkg, 'incoherent-version-in-changelog', - ret.group(1), expected) - - 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: - clt -= clt % (24 * 3600) # roll back to 00:00:00, see #246 - if clt < oldest_changelog_timestamp: - printWarning(pkg, 'changelog-time-overflow', - time.strftime("%Y-%m-%d", time.gmtime(clt))) - elif clt > time.time(): - printError(pkg, 'changelog-time-in-future', - time.strftime("%Y-%m-%d", time.gmtime(clt))) - -# for provide_name in (x[0] for x in pkg.provides()): -# if name == provide_name: -# printWarning(pkg, 'package-provides-itself') -# break - - def split_license(license): - return (x.strip() for x in - (l for l in license_regex.split(license) if l)) - - rpm_license = pkg[rpm.RPMTAG_LICENSE] - if not rpm_license: - printError(pkg, 'no-license') - else: - valid_license = True - if rpm_license not in VALID_LICENSES: - for l1 in split_license(rpm_license): - if l1 in VALID_LICENSES: - continue - for l2 in split_license(l1): - if l2 not in VALID_LICENSES: - printWarning(pkg, 'invalid-license', l2) - valid_license = False - if not valid_license: - self._unexpanded_macros(pkg, 'License', rpm_license) - - for tag in ('URL', 'DistURL', 'BugURL'): - if hasattr(rpm, 'RPMTAG_%s' % tag.upper()): - url = Pkg.b2s(pkg[getattr(rpm, 'RPMTAG_%s' % tag.upper())]) - self._unexpanded_macros(pkg, tag, url, is_url=True) - if url: - (scheme, netloc) = urlparse(url)[0:2] - if not scheme or not netloc or "." not in netloc or \ - scheme not in ('http', 'https', 'ftp') or \ - (Config.getOption('InvalidURL') and - invalid_url_regex.search(url)): - printWarning(pkg, 'invalid-url', tag, url) - else: - self.check_url(pkg, tag, url) - elif tag == 'URL': - printWarning(pkg, 'no-url-tag') - - obs_names = [x[0] for x in pkg.obsoletes()] - prov_names = [x[0] for x in pkg.provides()] - - for o in (x for x in obs_names if x not in prov_names): - printWarning(pkg, 'obsolete-not-provided', o) - for o in pkg.obsoletes(): - value = Pkg.formatRequire(*o) - self._unexpanded_macros(pkg, 'Obsoletes %s' % (value,), value) - - # TODO: should take versions, <, <=, =, >=, > into account here - # https://bugzilla.redhat.com/460872 - useless_provides = set() - for p in prov_names: - if (prov_names.count(p) != 1 and - not p.startswith('debuginfo(') and - p not in useless_provides): - useless_provides.add(p) - for p in sorted(useless_provides): - printError(pkg, 'useless-provides', p) - - for tagname, items in ( - ('Provides', pkg.provides()), - ('Conflicts', pkg.conflicts()), - ('Obsoletes', pkg.obsoletes()), - ('Supplements', pkg.supplements()), - ('Suggests', pkg.suggests()), - ('Enhances', pkg.enhances()), - ('Recommends', pkg.recommends())): - for p in items: - value = Pkg.formatRequire(*p) - self._unexpanded_macros(pkg, '%s %s' % (tagname, value), value) - - obss = pkg.obsoletes() - if obss: - provs = pkg.provides() - for prov in provs: - for obs in obss: - if Pkg.rangeCompare(obs, prov): - printWarning(pkg, 'self-obsoletion', - '%s obsoletes %s' % - (Pkg.formatRequire(*obs), - Pkg.formatRequire(*prov))) - - expfmt = rpm.expandMacro("%{_build_name_fmt}") - if pkg.isSource(): - # _build_name_fmt often (always?) ends up not outputting src/nosrc - # as arch for source packages, do it ourselves - expfmt = re.sub(r'(?i)%\{?ARCH\b\}?', pkg.arch, expfmt) - expected = pkg.header.sprintf(expfmt).split("/")[-1] - basename = os.path.basename(pkg.filename) - if basename != expected: - printWarning(pkg, 'non-coherent-filename', basename, expected) - - for tag in ('Distribution', 'DistTag', 'ExcludeArch', 'ExcludeOS', - 'Vendor'): - if hasattr(rpm, 'RPMTAG_%s' % tag.upper()): - res = Pkg.b2s(pkg[getattr(rpm, 'RPMTAG_%s' % tag.upper())]) - self._unexpanded_macros(pkg, tag, res) - - for path in private_so_paths: - for fname, pkgfile in pkg.files().items(): - if fname.startswith(path): - for prov in pkgfile.provides: - if so_dep_regex.search(prov[0]): - printWarning(pkg, "private-shared-object-provides", - fname, Pkg.formatRequire(*prov)) - - def check_description(self, pkg, lang, ignored_words): - description = pkg.langtag(rpm.RPMTAG_DESCRIPTION, lang) - if use_utf8: - 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) - if res and Config.getOption('ForbiddenWords'): - printWarning(pkg, 'description-use-invalid-word', lang, - res.group(1)) - res = tag_regex.search(l) - if res: - printWarning(pkg, 'tag-in-description', lang, res.group(1)) - - def check_summary(self, pkg, lang, ignored_words): - summary = pkg.langtag(rpm.RPMTAG_SUMMARY, lang) - if use_utf8: - 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() and - summary.partition(' ')[0] not in CAPITALIZED_IGNORE_LIST): - printWarning(pkg, 'summary-not-capitalized', lang, summary) - if summary[-1] == '.': - printWarning(pkg, 'summary-ended-with-dot', lang, summary) - 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) - res = forbidden_words_regex.search(summary) - if res and Config.getOption('ForbiddenWords'): - printWarning(pkg, 'summary-use-invalid-word', lang, res.group(1)) - if pkg.name: - sepchars = r'[\s%s]' % punct - res = re.search(r'(?:^|\s)(%s)(?:%s|$)' % - (re.escape(pkg.name), sepchars), - summary, re.IGNORECASE | re.UNICODE) - if res: - printWarning(pkg, 'name-repeated-in-summary', lang, - res.group(1)) - - -# Create an object to enable the auto registration of the test -check = TagsCheck() - -# Add information about checks -addDetails( -'summary-too-long', -'The "Summary:" must not exceed %d characters.' % max_line_len, - -'invalid-version', -'''The version string must not contain the pre, alpha, beta or rc suffixes -because when the final version will be out, you will have to use an Epoch tag -to make the package upgradable. Instead put it in the release tag, prefixed -with something you have control over.''', - -'spelling-error', -'''The value of this tag appears to be misspelled. Please double-check.''', - -'no-packager-tag', -'''There is no Packager tag in your package. You have to specify a packager -using the Packager tag. Ex: Packager: John Doe <john.doe@example.com>.''', - -'invalid-packager', -'''The packager email must end with an email compatible with the Packager -option of rpmlint. Please change it and rebuild your package.''', - -'no-version-tag', -'''There is no Version tag in your package. You have to specify a version using -the Version tag.''', - -'no-release-tag', -'''There is no Release tag in your package. You have to specify a release using -the Release tag.''', - -'not-standard-release-extension', -'Your release tag must match the regular expression ' + release_ext + '.', - -'no-name-tag', -'''There is no Name tag in your package. You have to specify a name using the -Name tag.''', - -'non-coherent-filename', -'''The file which contains the package should be named -<NAME>-<VERSION>-<RELEASE>.<ARCH>.rpm.''', - -'no-dependency-on', -''' -''', - -'incoherent-version-dependency-on', -''' -''', - -'no-version-dependency-on', -''' -''', - -'no-major-in-name', -'''The major number of the library isn't included in the package's name. -''', - -'description-shorter-than-summary', -'''The package description should be longer than the summary. Be a bit more -verbose, please.''', - -'no-provides', -'''Your library package doesn't provide the -devel name without the major -version included.''', - -'no-summary-tag', -'''There is no Summary tag in your package. You have to describe your package -using this tag. To insert it, just insert a tag 'Summary'.''', - -'summary-on-multiple-lines', -'''Your summary must fit on one line. Please make it shorter and rebuild the -package.''', - -'summary-not-capitalized', -'''Summary doesn't begin with a capital letter.''', - -'summary-ended-with-dot', -'''Summary ends with a dot.''', - -'summary-has-leading-spaces', -'''Summary begins with whitespace which will waste space when displayed.''', - -'no-description-tag', -'''The description of the package is empty or missing. To add it, insert a -%description section in your spec file, add a textual description of the -package after it, and rebuild the package.''', - -'description-line-too-long', -'''Your description lines must not exceed %d characters. If a line is exceeding -this number, cut it to fit in two lines.''' % max_line_len, - -'tag-in-description', -'''Something that looks like a tag was found in the package's description. -This may indicate a problem where the tag was not actually parsed as a tag -but just textual description content, thus being a no-op. Verify if this is -the case, and move the tag to a place in the specfile where %description -won't fool the specfile parser, and rebuild the package.''', - -'no-group-tag', -'''There is no Group tag in your package. You have to specify a valid group -in your spec file using the Group tag.''', - -'non-standard-group', -'''The value of the Group tag in the package is not valid. Valid groups are: -"%s".''' % '", "'.join(VALID_GROUPS), - -'no-changelogname-tag', -'''There is no %changelog tag in your spec file. To insert it, just insert a -'%changelog' in your spec file and rebuild it.''', - -'no-version-in-last-changelog', -'''The latest changelog entry doesn't contain a version. Please insert the -version that is coherent with the version of the package and rebuild it.''', - -'incoherent-version-in-changelog', -'''The latest entry in %changelog contains a version identifier that is not -coherent with the epoch:version-release tuple of the package.''', - -'changelog-time-overflow', -'''The timestamp of the latest entry in %changelog is suspiciously far away in -the past; it is possible that it is actually so much in the future that it -has overflowed rpm's timestamp representation.''', - -'changelog-time-in-future', -'''The timestamp of the latest entry in %changelog is in the future.''', - -'no-license', -'''There is no License tag in your spec file. You have to specify one license -for your program (eg. GPL). To insert this tag, just insert a 'License' in -your specfile.''', - -'invalid-license', -'''The value of the License tag was not recognized. Known values are: -"%s".''' % '", "'.join(VALID_LICENSES), - -'obsolete-not-provided', -'''If a package is obsoleted by a compatible replacement, the obsoleted package -should also be provided in order to not cause unnecessary dependency breakage. -If the obsoleting package is not a compatible replacement for the old one, -leave out the Provides.''', - -'invalid-dependency', -'''An invalid dependency has been detected. It usually means that the build of -the package was buggy.''', - -'no-epoch-tag', -'''There is no Epoch tag in your package. You have to specify an epoch using -the Epoch tag.''', - -'unreasonable-epoch', -'''The value of your Epoch tag is unreasonably large (> 99).''', - -'no-epoch-in-dependency', -'''Your package contains a versioned dependency without an Epoch.''', - -'devel-dependency', -'''Your package has a dependency on a devel package but it's not a devel -package itself.''', - -'invalid-build-requires', -'''Your source package contains a dependency not compliant with the lib64 -naming. This BuildRequires dependency will not be resolved on lib64 platforms -(eg. amd64).''', - -'explicit-lib-dependency', -'''You must let rpm find the library dependencies by itself. Do not put -unneeded explicit Requires: tags.''', - -'useless-provides', -'''This package provides 2 times the same capacity. It should only provide it -once.''', - -'tag-not-utf8', -'''The character encoding of the value of this tag is not UTF-8.''', - -'requires-on-release', -'''This rpm requires a specific release of another package.''', - -'no-url-tag', -'''The URL tag is missing. Please add a http or ftp link to the project location.''', - -'name-repeated-in-summary', -'''The name of the package is repeated in its summary. This is often redundant -information and looks silly in various programs' output. Make the summary -brief and to the point without including redundant information in it.''', - -'enchant-dictionary-not-found', -'''A dictionary for the Enchant spell checking library is not available for -the language given in the info message. Spell checking will proceed with -rpmlint's built-in implementation for localized tags in this language. -For better spell checking results in this language, install the appropriate -dictionary that Enchant will use for this language, often for example -hunspell-* or aspell-*.''', - -'self-obsoletion', -'''The package obsoletes itself. This is known to cause errors in various -tools and should thus be avoided, usually by using appropriately versioned -Obsoletes and/or Provides and avoiding unversioned ones.''', - -'unexpanded-macro', -'''This tag contains something that looks like an unexpanded macro; this is -often the sign of a misspelling. Please check your specfile.''', - -'private-shared-object-provides', -'''A shared object soname provides is provided by a file in a path from which -other packages should not directly load shared objects from. Such shared -objects should thus not be depended on and they should not result in provides -in the containing package. Get rid of the provides if appropriate, for example -by filtering it out during build. Note that in some cases this may require -disabling rpmbuild's internal dependency generator.''', -) - -for i in ("obsoletes", "conflicts", "provides", "recommends", "suggests", - "enhances", "supplements"): - addDetails("no-epoch-in-%s" % i, - "Your package contains a versioned %s entry without an Epoch." - % i.capitalize()) - -# TagsCheck.py ends here diff --git a/ZipCheck.py b/ZipCheck.py deleted file mode 100644 index a6f927a..0000000 --- a/ZipCheck.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################# -# File : ZipCheck.py -# Package : rpmlint -# Author : Ville Skyttä -# Created on : Thu Oct 30 00:14:45 EET 2003 -# Purpose : Verify Zip/Jar file correctness -############################################################################# - -import os -import re -import stat -import sys -import zipfile - -import AbstractCheck -import Config -from Filter import addDetails, printError, printWarning -import Pkg - - -zip_regex = re.compile(r'\.(zip|[ewj]ar)$') -jar_regex = re.compile(r'\.[ewj]ar$') -classpath_regex = re.compile(r'^\s*Class-Path\s*:', re.M | re.I) - -want_indexed_jars = Config.getOption('UseIndexedJars', True) - - -class ZipCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, "ZipCheck") - - def check(self, pkg): - for fname, pkgfile in pkg.files().items(): - path = pkgfile.path - if zip_regex.search(fname) and os.path.exists(path) and \ - stat.S_ISREG(os.lstat(path)[stat.ST_MODE]) and \ - zipfile.is_zipfile(path): - z = None # TODO ZipFile is context manager in 2.7+ - try: - z = zipfile.ZipFile(path, 'r') - badcrc = z.testzip() - if badcrc: - printError(pkg, 'bad-crc-in-zip', badcrc, fname) - except zipfile.error: - printWarning(pkg, 'unable-to-read-zip', '%s: %s' % - (fname, sys.exc_info()[1])) - else: - compressed = False - for zinfo in z.infolist(): - if zinfo.compress_type != zipfile.ZIP_STORED: - compressed = True - break - if not compressed: - printWarning(pkg, 'uncompressed-zip', fname) - - # additional jar checks - if jar_regex.search(fname): - try: - mf = Pkg.b2s(z.read('META-INF/MANIFEST.MF')) - if classpath_regex.search(mf): - printWarning(pkg, - 'class-path-in-manifest', fname) - except KeyError: - # META-INF/* are optional: - # http://java.sun.com/j2se/1.4/docs/guide/jar/jar.html - pass - try: - zinfo = z.getinfo('META-INF/INDEX.LIST') - if not want_indexed_jars: - printWarning(pkg, 'jar-indexed', fname) - except KeyError: - if want_indexed_jars: - printWarning(pkg, 'jar-not-indexed', fname) - pass - - z and z.close() - - -check = ZipCheck() - -addDetails( -'bad-crc-in-zip', -'''The reported file in the zip fails the CRC check. Usually this is a -sign of a corrupt zip file.''', - -'uncompressed-zip', -'''The zip file is not compressed.''', - -'class-path-in-manifest', -'''The META-INF/MANIFEST.MF file in the jar contains a hardcoded Class-Path. -These entries do not work with older Java versions and even if they do work, -they are inflexible and usually cause nasty surprises.''', - -'jar-indexed', -'''The jar file is indexed, ie. it contains the META-INF/INDEX.LIST file. -These files are known to cause problems with some older Java versions.''', - -'jar-not-indexed', -'''The jar file is not indexed, ie. it does not contain the META-INF/INDEX.LIST -file. Indexed jars speed up the class searching process of classloaders -in some situations.''', -) - -# ZipCheck.py ends here @@ -1,252 +0,0 @@ -# -*- python -*- - -# Example configuration file for rpmlint. - -# This line is mandatory to access the configuration functions -from Config import * - -# Additional paths to look for checks. -# ------------------------------------ - -#addCheckDir("~/mandrake/rpmlint") - -# Configure the checks if you don't want the default ones. -# -------------------------------------------------------- - -#addCheck("FHSCheck") -#addCheck("BinariesCheck") - -# Configuration options used by the checks shipped with rpmlint. -# The values in the commented out setOption() calls represent default -# or typical example values for the option. -# ------------------------------------------------------------------- - -# Type: integer, default: -1 (less than 0 means disabled) -#setOption("BadnessThreshold", -1) - -# When checking that various files that should be compressed are -# indeed compressed, look for this filename extension (no dot here). -# Type: string, default: "bz2" -#setOption("CompressExtension", "bz2") - -# Exception list for dangling symlink checks. The first in each pair -# is a regexp, and the second the package in which the target of the -# dangling symlink is shipped. -# Type: tuple of lists, default: (['consolehelper$', 'usermode-consoleonly']) -#setOption("DanglingSymlinkExceptions", ()) - -# Value for the Distribution tag. -# Type: string, default: "" (the empty string disables checking) -#setOption("Distribution", "") - -# Base directory where to extract uninstalled packages while checking. -# Type: string, default: tempfile.gettempdir() -#setOption("ExtractDir", "/tmp") - -# Standard "needs" values for non-XDG legacy menu items. -# Type: tuple of strings, default: ('gnome', 'icewm', 'kde', 'wmaker') -#setOption("ExtraMenuNeeds", ('gnome', 'icewm', 'kde', 'wmaker')) - -# Words that must not exist in various tag values. -# Type: regexp, default: '' ('' effectively disables this check) -#setOption("ForbiddenWords", '') - -# Exceptions for hardcoded library paths. -# Type: regexp, default: see DEFAULT_HARDCODED_LIB_PATH_EXCEPTIONS in SpecCheck -#setOption("HardcodedLibPathExceptions", '/lib/modules/') - -# Accepted non-XDG legacy icon filenames. -# Type: regexp, default: '.*\.png$' -#setOption("IconFilename", '.*\.png$') - -# Paths in which non-XDG legacy icons should be installed. The first -# item in the tuples is a filesystem path, and the second the icon type. -# Type: tuple of string tuples, default: see DEFAULT_ICON_PATH in MenuCheck -#setOption("IconPath", ()) - -# Disallowed dependencies. -# Type: tuple of regexps, default: see DEFAULT_INVALID_REQUIRES in TagsCheck -#setOption("InvalidRequires", ()) - -# Strings to disallow in various URL tags. -# Type: regexp, default: '' ('' effectively disables this check) -#setOption("InvalidURL", '') - -# Whether to allow packaging kernel modules in non-kernel packages. -# Type: boolean, default: True -#setOption("KernelModuleRPMsOK", True) - -# Maximum line length for summaries and descriptions. -# Type: integer, default: 79 -#setOption("MaxLineLength", 79) - -# Type: tuple of string,tuple lists, default: see DEFAULT_LAUNCHERS in MenuCheck -#setOption("MenuLaunchers", ()) - -# Names of packages to treat as "meta" ones. -# Type: regexp, default: '^(bundle|task)-' -#setOption("MetaPackageRegexp", '^(bundle|task)-') - -# Whether to enable checks that require networking. -# Type: boolean, default: False -#setOption("NetworkEnabled", False) - -# Timeout for network operations in seconds. -# Type: integer, default: 10 -#setOption("NetworkTimeout", 10) - -# Value for the Packager tag. -# Type: regexp, default: '' ('' effectively disables this check) -#setOption("Packager", '') - -# Type: boolean, default: True -#setOption("PerlVersionTrick", True) - -# Assumed default version of Python if one cannot be determined from files. -# Type: string, default: None -#setOption("PythonDefaultVersion", None) - -# Expected suffix in Release tags. -# Type: regexp, default: '' ('' effectively disables this check) -#setOption("ReleaseExtension", '') - -# Group tag for games. -# Type: regexp, default: 'Games' -#setOption("RpmGamesGroup", 'Games') - -# Doc files to which end of line and UTF-8 checks should not be applied. -# Type: regexp, default: \.(?:rtf|x?html?|svg|ml[ily]?)$' -#setOption("SkipDocsRegexp", '\.(?:rtf|x?html?|svg|ml[ily]?)$') - -# Standard OS groups. -# Type: tuple of strings, default: see DEFAULT_STANDARD_GROUPS in FilesCheck -#setOption("StandardGroups", ()) - -# Standard OS users. -# Type: tuple of strings, see DEFAULT_STANDARD_USERS in FilesCheck -#setOption("StandardUsers", ()) - -# List of directory prefixes that are not allowed in packages -# Type: tuple of strings, see DEFAULT_DISALLOWED_DIRS in FilesCheck -#setOption("DisallowedDirs", ('/home', '/mnt')) - -# List of directories considered to be system default library search paths. -# Type: tuple of strings, default: see DEFAULT_SYSTEM_LIB_PATHS in BinariesCheck -#setOption("SystemLibPaths", ('/lib', '/lib64', '/usr/lib', '/usr/lib64')) - -# Executables that must be compiled as position independent. -# Type: regex, default: None -#setOption("PieExecutables", '^/bin/(ping6?|su)$') - -# Whether to want default start/stop runlevels specified in init scripts. -# Type: boolean, default: True -#setOption("UseDefaultRunlevels", True) - -# Whether to use the Enchant spell checker (if available) for spell checking. -# Type: boolean, default: True -#setOption("UseEnchant", True) - -# Whether an explicit Epoch should always be specified. -# Type: boolean, default: False -#setOption("UseEpoch", False) - -# Whether jars should be indexed. -# Type: boolean, default: True -#setOption("UseIndexedJars", True) - -# Whether symlinks between directories should be relative. -# Type: boolean, default: True -#setOption("UseRelativeSymlinks", True) - -# Whether the UTF-8 character encoding should be used where applicable. -# Type: boolean, default: autodetected from environment -#setOption("UseUTF8", True) - -# Whether debug sources are expected to be in separate packages from -# -debuginfo, typically -debugsource. -# Type: boolean, default: False -#setOption("UseDebugSource", False) - -# Whether %changelog entries should contain a version. -# Type: boolean, default: True -#setOption("UseVersionInChangelog", True) - -# Whether init scripts must use /var/lock/subsys -# Type: boolean, default: True -#setOption("UseVarLockSubsys", True) - -# Architecture dependent paths in which packages are allowed to install files -# even if they are all non-binary. -# Type: regexp, default: see BinariesCheck -#setOption("UsrLibBinaryException", '^/usr/lib(64)?/(perl|python|ruby)') - -# Value for the BuildHost tag. -# Type: regexp, default '' ('' effectively disables this check) -#setOption("ValidBuildHost", '') - -# Interpreters whose scriptlets are allowed to be empty. -# Type: tuple of strings, default: ('/sbin/ldconfig',) -#setOption("ValidEmptyShells", ('/sbin/ldconfig',)) - -# Values for the Group tag. -# Type: list of strings, default: extracted from GROUPS file shipped with rpm -#setOption("ValidGroups", []) - -# Values for the License tag. -# Type: tuple of strings, default: see DEFAULT_VALID_LICENSES in TagsCheck -#setOption("ValidLicenses", ()) - -# Values for non-XDG legacy menu item sections. -# Type: tuple of strings, default: see DEFAULT_VALID_SECTIONS in MenuCheck -#setOption("ValidMenuSections", ()) - -# Package scriptlet interpreters. -# Type: tuple of strings, default: see DEFAULT_VALID_SHELLS in PostCheck -#setOption("ValidShells", ('/bin/sh', '/bin/bash')) - -# Permissions for files in source packages. -# Type: tuple of modes, default: (0644, 0755) -#setOption("ValidSrcPerms", (0644, 0755)) - -# Value for the Vendor tag. -# Type: string, default: "" ("" effectively disables this check) -#setOption("Vendor", "") - -# Man page warning category, passed to groff -w while checking man pages. -# See the groff(1) or troff(1) man pages for available categories. -# 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')) - -# Check if application performs calls to blacklisted methods -# the 'f_name' is the regexp for the blacklisted method -# 'good_param' is the optional parameter that will waive the result if this -# regexp matches output of `strings` from the binary file -# 'description' is the explanation of why the call is blacklisted -#bad_crypto_warning = \ -#'''This application package calls a function to explicitly set crypto ciphers -#for SSL/TLS. That may cause the application not to use the system-wide set -#cryptographic policy and should be modified in accordance to: -#https://fedoraproject.org/wiki/Packaging:CryptoPolicies''' -# -#call_blacklist = {'crypto-policy-non-compliance-openssl' : -# {'f_name' : 'SSL_CTX_set_cipher_list', -# 'good_param' : '^PROFILE=SYSTEM$', -# 'description' : bad_crypto_warning}, -# 'crypto-policy-non-compliance-gnutls-1' : -# {'f_name' : 'gnutls_priority_set_direct', -# 'good_param' : '^@SYSTEM$', -# 'description' : bad_crypto_warning}, -# 'crypto-policy-non-compliance-gnutls-2' : -# {'f_name' : 'gnutls_priority_init', -# 'description' : bad_crypto_warning} -# } -#setOption("WarnOnFunction", call_blacklist) - -# Output filters. -# --------------- - -#addFilter("E: .* no-signature") diff --git a/configs/Fedora/fedora.toml b/configs/Fedora/fedora.toml new file mode 100644 index 0000000..e006e99 --- /dev/null +++ b/configs/Fedora/fedora.toml @@ -0,0 +1,353 @@ +# Fedora's configuration for the rpmlint utility. + +# When checking that various files that should be compressed are +# indeed compressed, look for this filename extension +CompressExtension = "gz" + +# simple error is enough; warnings are fine +BadnessThreshold = -1 + +# Whether to allow packaging kernel modules in non-kernel packages. +KernelModuleRPMsOK = false + +# Maximum allowed line length for Summary and Description tags +MaxLineLength = 80 + +# Assumed default version of Python if one cannot be determined from files +# FIXME this should be sys.version[:3] but I have no idea how to implement it +# here without changing it every other release +PythonDefaultVersion = "" + +# Regexp string with expected suffix in Release tags. +ReleaseExtension = '\.(fc|rhe?l|el)\d+(?=\.|$)' + +# Whether to want default start/stop runlevels specified in init scripts +UseDefaultRunlevels = false + +ValidSrcPerms = [ + "0o644", + "0o664", +] + +# List of directories considered to be system default library search paths. +SystemLibPaths = [ + "/lib", + "/usr/lib", + "/lib64", + "/usr/lib64", +] + +# Enabled checks for the rpmlint to be run (besides the default set) +Checks = [ + "BashismsCheck", + "PAMModulesCheck", + "TmpFilesCheck", + "SysVInitOnSystemdCheck", + "SharedLibraryPolicyCheck", +] + +# Interpreters whose scriptlets are allowed to be empty +ValidEmptyShells = [ + "/usr/sbin/ldconfig", +] + +# Package scriptlet interpreters +ValidShells = [ + "<lua>", + "/usr/bin/sh", + "/usr/bin/bash", + "/usr/sbin/ldconfig", + "/usr/bin/perl", + "/usr/bin/python", + "/usr/bin/python3", +] + +Filters = [ +# FIXME - the commented lines are from openSUSE config +# Are they relevant for Fedora too? +# PR which enables them or remove them is welcome +## Stuff autobuild takes care about +# '.*invalid-version.*', +# '.*invalid-packager.*', + '.*not-standard-release-extension.*', +# '.*invalid-buildhost.*', + '.*executable-in-library-package.*', + '.*non-versioned-file-in-library-package.*', + '.*shlib-policy-name-error.*', +# '.*hardcoded-path-in-buildroot-tag.*', + '.*no-buildroot-tag.*', +# '.*cross-directory-hard-link.*', + +# Do not validate package rpm groups + '.*devel-package-with-non-devel-group.*', + '.*no-group-tag.*', + '.*non-standard-group.*', + +# Output filters +# '.*spurious-bracket-in-.*', +# '.*one-line-command-in-.*', +# ' dir-or-file-in-opt ', # handled by CheckFilelist.py +# ' dir-or-file-in-usr-local ', # handled by CheckFilelist.py + ' non-standard-dir-in-usr ', # handled by CheckFilelist.py + ' no-signature', +# ' symlink-crontab-file', #bnc591431 +# ' without-chkconfig', +# 'unstripped-binary-or-object.*\.ko', +# ' no-chkconfig', +# ' subsys-not-used', +# ' dangerous-command.*', +# ' setuid-binary.*', +# 'subdir-in-bin /sbin/conf.d/', +# '.* nss_db non-standard-dir-in-var db', +# 'non-standard-dir-in-usr openwin', +# 'ibcs2 non-standard-dir-in-usr i486-sysv4', +# 'shlibs5 non-standard-dir-in-usr i486-linux-libc5', +# 'explicit-lib-dependency libtool', +# +## Filesystem package needs special exceptions +# '^filesystem\..*: dir-or-file-in-var-run', +# '^filesystem\..*: dir-or-file-in-var-lock', +# '^filesystem\..*: dir-or-file-in-var-tmp', +# '^filesystem\..*: dir-or-file-in-var-run', +# '^filesystem\..*: dir-or-file-in-var-lock', +# '^filesystem\..*: dir-or-file-in-usr-tmp', +# '^filesystem\..*: dir-or-file-in-tmp', +# '^filesystem\..*: dir-or-file-in-mnt', +# '^filesystem\..*: dir-or-file-in-home', +# '^filesystem\..*: hidden-file-or-dir /root/.gnupg', +# '^filesystem\..*: hidden-file-or-dir /root/.gnupg', +# '^filesystem\..*: hidden-file-or-dir /etc/skel/.config', +# '^filesystem\..*: hidden-file-or-dir /etc/skel/.local', +# '^filesystem\..*: hidden-file-or-dir /tmp/.X11-unix', +# '^filesystem\..*: hidden-file-or-dir /tmp/.ICE-unix', +# '^filesystem\..*: hidden-file-or-dir /etc/skel/.fonts', +# '^filesystem\..*: filelist-forbidden-fhs23', +# '^filesystem\..*: filelist-forbidden-opt', +# '^filesystem\..*: non-standard-uid /var/lib/nobody nobody', +# '^filesystem\..*: missing-dependency-to-cron', +## has arch specific dirs in /usr +# '^filesystem\..*: no-binary', +# +## Suppress any errors about internal packages +# '^qa\S+: [EWI]:', +# '^\S*(?:INTERNAL|internal)\.\S+: [EWI]:', +# +## Exceptions for devel-files +# 'devel-file-in-non-devel-package.*/boot/vmlinuz-.*autoconf.h', +# 'devel-file-in-non-devel-package.*/usr/src/linux-', +# 'devel-file-in-non-devel-package.*/usr/share/systemtap', +# '-(?:examples|doc)\.\S+: \w: devel-file-in-non-devel-package', +# 'java\S+-demo\.\S+: \w: devel-file-in-non-devel-package', +# 'avr-libc\.\S+: \w: devel-file-in-non-devel-package', +# 'cross-.*devel-file-in-non-devel-package', +# 'cmake.*devel-file-in-non-devel-package', +# 'gcc\d\d.*devel-file-in-non-devel-package', +# 'OpenOffice_org-sdk\.\S+: \w: devel-file-in-non-devel-package', +# 'wnn-sdk\.\S+: \w: devel-file-in-non-devel-package', +# 'ocaml\.\S+: \w: devel-file-in-non-devel-package', +# 'xorg-x11-server-sdk\.\S+: \w: devel-file-in-non-devel-package', +# 'linux-kernel-headers\.\S+: \w: devel-file-in-non-devel-package', +# ' devel-file-in-non-devel-package.*-config', +# 'libtool\.\S+: \w: devel-file-in-non-devel-package', +# 'sdb.* dangling-relative-symlink /usr/share/doc/sdb/.*/gifs ../gifs', +# 'kernel-modules-not-in-kernel-packages', +# +## SUSE kmp's don't need manual depmod (bnc#456048) +# 'module-without-depmod-postin', +# 'postin-with-wrong-depmod', +# 'module-without-depmod-postun', +# 'postun-with-wrong-depmod', +# 'configure-without-libdir-spec', +# 'conffile-without-noreplace-flag /etc/init.d', +# 'use-of-RPM_SOURCE_DIR', +# 'use-tmp-in-', +# 'symlink-contains-up-and-down-segments /var/lib/named', +# 'no-ldconfig-symlink', +# 'aaa_base\.\S+: \w: use-of-home-in-%post', +# 'description-line-too-long', + 'hardcoded-library-path', +# +## Doesn't seem to make sense +# 'invalid-ldconfig-symlink', +# 'invalid-soname', +# 'only-non-binary-in-usr-lib', + 'outside-libdir-files', +# +## We want these files +# ' perl-temp-file ', +# ' hidden-file-or-dir .*/\.packlist', +# ' hidden-file-or-dir .*/\.directory', +# 'perl-.*no-binary', + ' no-major-in-name ', +# +## We check for that already +# 'dangling-relative-symlink', + ' lib-package-without-%mklibname', + ' requires-on-release', +# ' non-executable-script /etc/profile.d/', +# ' non-executable-script /var/adm/fillup-templates/', +# ' init-script-name-with-dot ', +# '.* statically-linked-binary /sbin/ldconfig', +# '.* statically-linked-binary /sbin/init', +# 'valgrind.* statically-linked-binary', +# 'ldconfig-post.*/ddiwrapper/wine/', +# 'glibc\.\S+: \w: statically-linked-binary /usr/sbin/glibc_post_upgrade', + ' symlink-should-be-relative ', +# ' binary-or-shlib-defines-rpath .*ORIGIN', +# 'libzypp.*shlib-policy-name-error.*libzypp', +# 'libtool.*shlib-policy.*', +# +## Stuff that is currently too noisy, but might become relevant in the future +# ' prereq-use', +# ' file-not-utf8', +# ' tag-not-utf8', +# ' setup-not-quiet', +# ' mixed-use-of-spaces-and-tabs ', +# ' prereq-use ', +# +## An issue with OBS, works with autobuild + ' no-packager-tag', +# ' unversioned-explicit-provides ', +# ' unversioned-explicit-obsoletes ', +# ' service-default-enabled ', +# ' non-standard-dir-perm ', +# ' conffile-without-noreplace-flag ', +# ' non-standard-executable-perm ', + ' jar-not-indexed ', +# ' uncompressed-zip ', +# ' %ifarch-applied-patch ', +# ' read-error ', +# ' init-script-without-chkconfig-postin ', +# ' init-script-without-chkconfig-preun ', +# ' postin-without-chkconfig ', +# ' preun-without-chkconfig ', + ' no-dependency-on locales', + ' no-dependency-on perl-base', + ' no-dependency-on python-base', + ' python-naming-policy-not-applied', + # FIXME does this really exists? + ' perl-naming-policy-not-applied', +# ' shlib-policy-name-error', +# ' binary-or-shlib-defines-rpath', +# ' executable-marked-as-config-file', +# ' log-files-without-logrotate', +# ' hardcoded-prefix-tag', + ' -debug(info|source).* no-documentation', +# ' multiple-specfiles', +# ' no-default-runlevel ', +# ' setgid-binary ', +# ' non-readable ', + ' postin-without-ghost-file-creation ', +# +## Exceptions for filelist checks +# 'nfs-client\.\S+: \w: filelist-forbidden-backup-file /var/lib/nfs/sm.bak', +# 'perl\.\S+: \w: filelist-forbidden-perl-dir ', +# 'info\.\S+: \w: info-dir-file .*/usr/share/info/dir', +# +## These packages are used for CD creation and are not supposed to be +## installed. It's still a dirty hack to make an exception. The +## packages should either be built in a separate project with +## different config or file be put somewhere below /opt/suse/* +# '(?:dosutils|skelcd|installation-images|yast2-slide-show|instlux|skelcd-.*|patterns-.*)\.\S+: \w: filelist-forbidden-fhs23 /CD1', +# +## Too noisy, and usually not something downstream packagers can fix +# ' incorrect-fsf-address ', +# ' no-manual-page-for-binary ', +# ' static-library-without-debuginfo /usr/lib(?:64)?/ghc-[\d\.]+/', +# +## Many places have shorter paths +# ' non-coherent-filename ', + +# Mandriva specific stuff that Fedora do not want either + ' invalid-build-requires ', + +# Fedora specific stuff that we don't want + ' ghost-files-without-postin', + ' no-provides ', + ' -debuginfo.* /usr/lib/debug/', + ' -debugsource.* /usr/src/debug/', + '^gpg-pubkey:', + ' doc-file-dependency .* /bin/sh$', + 'explicit-lib-dependency (liberation-fonts|libertas-.*-firmware|libvirt$|.*-(java|python|utils)$)', + 'explicit-lib-dependency (python-.*lib.*|python2-.*lib.*|python3-.*lib.*)$', + 'explicit-lib-dependency libreoffice.*$', + 'dangling-\S*symlink /usr/share/doc/HTML/\S+/common .+/common$', + 'hidden-file-or-dir .*/man5/\.k5login\.5[^/]+$', + 'blender.+ (wrong-script-interpreter|non-executable-script) .+/blender/.+\.py.*BPY.*', + # Only EL4 needs the files-attr-not-set check, because rpm 4.4 and newer no longer need a %defattr line + # (it automatically provides one). + 'files-attr-not-set', + # Don't bother with the non-ghost-in-run checks, /var/lock and /var/run are + # symlinks to /run/lock and /run respectively, and /run is a tmpfs + 'non-ghost-in-run', + # Someone thought it was a good idea to make .desktop files executable. They were wrong. + # Nevertheless, I do not yet control the universe, so we squelch the error here. + 'script-without-shebang .*\.desktop$', + # Some files in /etc/ are not meant to be modified by the sysadmin + 'non-conffile-in-etc /etc/rpm/.*$', + # Files that are intentionally not supposed to be readable + # Contains passwords + 'non-readable /etc/ovirt-engine/isouploader.conf', + ## Ignore webservers which are just broken. + 'invalid-url .*\.googlecode\.com/.*HTTP Error 404', + 'invalid-url .*\.jboss\.org/.*HTTP Error 403', + 'invalid-url .*bitbucket\.org/.*HTTP Error 403', + 'invalid-url .*github\.com/.*HTTP Error 403', + # Don't care about long descriptions on debuginfo packages + # They automatically include the package name and are always + # quite long. + '-debuginfo.* description-line-too-long', + # ignore "common" jargon words + # https://bugzilla.redhat.com/show_bug.cgi?id=1424684#c9 + 'spelling-error.* \b(runtime|Runtime|metadata|cryptographic|multi|linux|filesystem|filesystems|backend|backends|userspace|addon|wayland|Wayland|util|utils|lossless|virtualization|toolkits|libvirtd|crypto|glyphs|GStreamer|http|extensibility|codec|codecs|truetype|scalable|pluggable|pixbuf|Kerberos|customizable|bitstream|tcp|libXss|libs|libc|encodings|GLib|udev|posix|libpng|glapi|gbm|freedesktop|spi|realtime|preprocessor|libaudit|hypervisor|embeddable|distributable|devel|config|cairo|bootloader|adaptors|pragma|passphrase|malloc|libvirt|libmagic|io|datetime|boolean|argparse|py|pinentry|namespace|middleware|lowlevel|libxcb|libudev|libsoup|libgcrypt|libcom|iSCSI|initramfs|GObject|executables|dialogs|checkpolicy|bitmapped|assistive|btrfs|crypttab|defrag|dracut|hostname|luks|mountpoints|netdev|rpmnew|rpmsave|storaged|tss|unlocker)\b', + # Fedora no longer uses explicit ldconfig %post/%postun as of Fedora 28 + 'postin-without-ldconfig', + 'postun-without-ldconfig', + 'library-without-ldconfig-postin', + 'library-without-ldconfig-postun', + # Ignore 700 dir perms here + 'non-standard-dir-perm /etc/.* 700', + 'non-standard-dir-perm /var/lib/.* 700', + # pip 20.2 generates PEP 376 "REQUESTED" marker (empty) + 'zero-length .+/site-packages/.+\.dist-info/REQUESTED\b', + # py.typed files are empty + 'zero-length .+/site-packages/.+/py\.typed\b', + # https://bugzilla.redhat.com/496737, https://bugzilla.redhat.com/646455 + 'coreutils.* (setuid-binary|non-standard-executable-perm) /bin/su (root )?04', + 'krb5-workstation.* (setuid-binary|non-standard-executable-perm) /usr/kerberos/bin/ksu (root )?04', + 'passwd.* (setuid-binary|non-standard-executable-perm) /usr/bin/passwd (root )?04', + 'sudo.* (setuid-binary|non-standard-executable-perm) /usr/bin/sudo(edit)? (root )?04', + 'upstart.* (setuid-binary|non-standard-executable-perm) /sbin/initctl (root )?04', + 'usermode.* (setuid-binary|non-standard-executable-perm) /usr/sbin/userhelper (root )?04', + +## Bash completion files are not scripts, do not require them marked as %config +# 'W: non-conffile-in-etc /etc/bash_completion.d/', +# + +# Info uses file triggers now (boo#1152169) + ' info-files-without-install-info-postin', + ' info-files-without-install-info-postun ', + ' postin-without-install-info ', +] + +[DanglingSymlinkExceptions."/usr/share/doc/licenses/"] +path = "/usr/share/doc/licenses/" +name = "licenses" +[DanglingSymlinkExceptions."consolehelper$"] +path = "consolehelper$" +name = "usermode" +[DanglingSymlinkExceptions."consolehelper-gtk$"] +path = "consolehelper-gtk$" +name = "usermode-gtk" + +[Descriptions] +non-standard-uid = '''A file in this package is owned by an unregistered user id. +To register the user, please make a pull request to the rpmlint config file +configs/Fedora/fedora.toml in the rpmlint repository. +''' +non-standard-gid = '''A file in this package is owned by an unregistered group id. +To register the group, please make a pull request to the rpmlint config file +configs/Fedora/fedora.toml in the rpmlint repository. +''' +no-changelogname-tag = '''There is no changelog. Please insert a '%changelog' section heading in your +spec file and prepare your changelog entry using e.g. the 'rpmdev-bumpspec' command.''' diff --git a/configs/Fedora/licenses.toml b/configs/Fedora/licenses.toml new file mode 100644 index 0000000..42efe82 --- /dev/null +++ b/configs/Fedora/licenses.toml @@ -0,0 +1,314 @@ +# taken from Fedora's rpmlint 1.11 +# FIXME should be revisited and updated + +# Package scriptlet interpreters +ValidLicenses = [ + # These are the short names for all of the Fedora approved licenses. + # The master list is kept here: http://fedoraproject.org/wiki/Licensing + # Last synced with revision "2.53, May 27, 2021" of that page. + # Note that Bcotton is no longer bumping revision. + '0BSD', + 'AAL', + 'Abstyles', + 'Adobe', + 'ADSL', + 'AFL', + 'Afmparse', + 'AGPLv1', + 'AGPLv3', + 'AGPLv3+', + 'AGPLv3 with exceptions', + 'AMDPLPA', + 'AML', + 'AMPAS BSD', + 'ANTLR-PD', + 'APAFML', + 'App-s2p', + 'APSL 2.0', + 'ARL', + 'Array', + 'Artistic 2.0', + 'Artistic clarified', + 'ASL 1.0', + 'ASL 1.1', + 'ASL 2.0', + 'Bahyph', + 'Barr', + 'Beerware', + 'BeOpen', + 'Bibtex', + 'BitTorrent', + 'Boost', + 'Borceux', + 'BSD', + 'BSD-2-Clause-Patent', + 'BSD Protection', + 'BSD with advertising', + 'BSD with attribution', + 'CATOSL', + 'CC0', + 'CeCILL', + 'CeCILL-B', + 'CeCILL-C', + 'CDDL-1.0', + 'CDDL-1.1', + 'CNRI', + 'Condor', + 'Copyright only', + 'CPAL', + 'CPL', + 'CPM', + 'CRC32', + 'Crossword', + 'Crystal Stacker', + 'Cube', + 'diffmark', + 'DMIT', + 'DOC', + 'Dotseqn', + 'DSDP', + 'dvipdfm', + 'DWPL', + 'ECL 1.0', + 'ECL 2.0', + 'eCos', + 'EFL 2.0', + 'eGenix', + 'Entessa', + 'EPICS', + 'EPL-1.0', + 'EPL-2.0', + 'ERPL', + 'EU Datagrid', + 'EUPL 1.1', + 'EUPL 1.2', + 'Eurosym', + 'Fair', + 'FDK-AAC', + 'FSFAP', + 'FSFUL', + 'FSFULLR', + 'FTL', + 'Giftware', + 'GL2PS', + 'Glide', + 'Glulxe', + 'gnuplot', + 'GPL+', + 'GPL+ or Artistic', + 'GPL+ with exceptions', + 'GPLv1', + 'GPLv2 or Artistic', + 'GPLv2+ or Artistic', + 'GPLv2', + 'GPLv2 with exceptions', + 'GPLv2+', + 'GPLv2+ with exceptions', + 'GPLv3', + 'GPLv3 with exceptions', + 'GPLv3+', + 'GPLv3+ with exceptions', + 'HaskellReport', + 'HSRL', + 'IBM', + 'IJG', + 'ImageMagick', + 'iMatix', + 'Imlib2', + 'Inner-Net', + 'Intel ACPI', + 'Interbase', + 'ISC', + 'Jabber', + 'JasPer', + 'JPython', + 'Julius', + 'Knuth', + 'Latex2e', + 'LBNL BSD', + 'Leptonica', + 'LGPLv2', + 'LGPLv2 with exceptions', + 'LGPLv2+', + 'LGPLv2+ or Artistic', + 'LGPLv2+ with exceptions', + 'LGPLv3', + 'LGPLv3 with exceptions', + 'LGPLv3+', + 'LGPLv3+ with exceptions', + 'Lhcyr', + 'libtiff', + 'LLGPL', + 'Logica', + 'LOSLA', + 'LPL', + 'LPPL', + 'MakeIndex', + 'mecab-ipadic', + 'midnight', + 'Minpack', + 'MirOS', + 'MIT', + 'MIT-0', + 'MITNFA', + 'MIT with advertising', + 'mod_macro', + 'Motosoto', + 'MPLv1.0', + 'MPLv1.1', + 'MPLv2.0', + 'MS-PL', + 'MS-RL', + 'MTLL', + 'Mup', + 'Naumen', + 'NCSA', + 'NetCDF', + 'Netscape', + 'Newmat', + 'Newsletr', + 'NGPL', + 'NISTSL', + 'NLPL', + 'Nmap', + 'Nokia', + 'NOSL', + 'Noweb', + 'OGL', + 'OML', + 'OpenLDAP', + 'OpenPBS', + 'OpenSSL', + 'OReilly', + 'OSL 1.0', + 'OSL 1.1', + 'OSL 2.0', + 'OSL 2.1', + 'OSL 3.0', + 'Par', + 'Phorum', + 'PHP', + 'PlainTeX', + 'Plexus', + 'PostgreSQL', + 'psfrag', + 'psutils', + 'Public Domain', + 'Python', + 'Qhull', + 'QPL', + 'radvd', + 'Rdisc', + 'REX', + 'RiceBSD', + 'Romio', + 'RPSL', + 'RSA', + 'Rsfs', + 'Ruby', + 'Saxpath', + 'Sequence', + 'SCEA', + 'SCRIP', + 'Sendmail', + 'Sleepycat', + 'SISSL', + 'SLIB', + 'SNIA', + 'softSurfer', + 'SPL', + 'STMPL', + 'SWL', + 'TCGL', + 'TCL', + 'Teeworlds', + 'TGPPL', + 'TGPPL with exceptions', + 'Threeparttable', + 'TMate', + 'Tolua', + 'TORQUEv1.1', + 'TOSL', + 'TPDL', + 'TPL', + 'TTWL', + 'Tumbolia', + 'UCAR', + 'UCD', + 'Unicode', + 'Unlicense', + 'UPL', + 'Vim', + 'VNLSL', + 'VOSTROM', + 'VSL', + 'W3C', + 'Webmin', + 'Wsuipa', + 'WTFPL', + 'wxWidgets', + 'wxWindows', + 'Xerox', + 'xinetd', + 'xpp', + 'XSkat', + 'YPLv1.1', + 'Zed', + 'Zend', + 'zlib', + 'zlib with acknowledgement', + 'ZPLv1.0', + 'ZPLv2.0', + 'ZPLv2.1', + # Documentation licenses + 'CDL', + 'FBSDDL', + 'GFDL', + 'IEEE', + 'LDPL', + 'OFSFDL', + 'Open Publication', + 'Public Use', + 'Verbatim', + # Content licenses + 'CC-BY', + 'CC-BY-ND', + 'CC-BY-SA', + 'DL-DE-BY', + 'DMTF', + 'DSL', + 'EFML', + 'Free Art', + 'GeoGratis', + 'Green OpenMusic', + 'OAL', + 'Ouverte', + 'PDDL-1.0', + # Font licenses + 'AMS', + 'Arphic', + 'AHFL', + 'Baekmuk', + 'Bitstream Vera', + 'Charter', + 'DoubleStroke', + 'ec', + 'Elvish', + 'Hershey', + 'HOFL', + 'IPA', + 'Liberation', + 'Lucida', + 'MgOpen', + 'mplus', + 'OFL', + 'PTFL', + 'Punknova', + 'STIX', + 'Utopia', + 'Wadalab', + 'XANO', + # Others + 'Redistributable, no modification permitted', + 'Freely redistributable without restriction', +] diff --git a/configs/Fedora/scoring.toml b/configs/Fedora/scoring.toml new file mode 100644 index 0000000..170082c --- /dev/null +++ b/configs/Fedora/scoring.toml @@ -0,0 +1,6 @@ +[Scoring] +# This can set how bad each error is. +# But we set BadnessThreshold to -1 so anything above 0 +# is actually fatal. +# You can check configs/openSUSE/scoring.toml for more fine graded scoring. +no-group-tag = 1 diff --git a/configs/Fedora/users-groups.toml b/configs/Fedora/users-groups.toml new file mode 100644 index 0000000..706805f --- /dev/null +++ b/configs/Fedora/users-groups.toml @@ -0,0 +1,5 @@ +# generated by tools/generate-fedora-users-groups.py on 2022-09-07 + +StandardUsers = ['abrt', 'activemq', 'adm', 'aeolus', 'amandabackup', 'apache', 'arpwatch', 'ats', 'avahi', 'avahi-autoipd', 'bacula', 'beagleindex', 'bin', 'cassandra', 'ceilometer', 'ceph', 'cimsrvr', 'cinder', 'clamav', 'condor', 'cyrus', 'daemon', 'dbus', 'desktop', 'dhcpd', 'distcache', 'dovecot', 'elasticsearch', 'exim', 'fax', 'frontpage', 'ftp', 'games', 'gdm', 'glance', 'gopher', 'hacluster', 'haldaemon', 'halt', 'haproxy', 'heat', 'hsqldb', 'ident', 'jbosson-agent', 'jetty', 'jonas', 'keystone', 'ldap', 'lp', 'luci', 'mail', 'mailman', 'mailnull', 'majordomo', 'mongodb', 'myproxy', 'mysql', 'named', 'netdump', 'news', 'nobody', 'nocpulse', 'nova', 'nscd', 'nslcd', 'ntp', 'nut', 'operator', 'oprofile', 'ovirt', 'ovirtagent', 'pegasus', 'piranha', 'pkiuser', 'polkituser', 'postfix', 'postgres', 'prelude-manager', 'privoxy', 'pulse', 'puppet', 'pvm', 'qemu', 'quagga', 'quantum', 'radiusd', 'radvd', 'retrace', 'rhevm', 'ricci', 'root', 'rpc', 'rpcuser', 'rpm', 'rtkit', 'sabayon', 'saned', 'sanlock', 'shutdown', 'smmsp', 'snortd', 'squid', 'sshd', 'stap-server', 'swift', 'sync', 'systemd-network', 'systemd-resolve', 'tcpdump', 'tomcat', 'tss', 'usbmuxd', 'uucp', 'vcsa', 'vdsm', 'vhostmd', 'wallaby', 'webalizer', 'wildfly', 'wnn', 'xfs'] + +StandardGroups = ['abrt', 'activemq', 'adm', 'aeolus', 'apache', 'arpwatch', 'ats', 'audio', 'avahi', 'avahi-autoipd', 'bacula', 'beagleindex', 'bin', 'cassandra', 'cdrom', 'ceilometer', 'ceph', 'cimsrvr', 'cinder', 'clamav', 'condor', 'console', 'daemon', 'dbus', 'desktop', 'dhcpd', 'dialout', 'dip', 'disk', 'distcache', 'dovecot', 'elasticsearch', 'exim', 'fax', 'floppy', 'frontpage', 'ftp', 'games', 'gdm', 'glance', 'gopher', 'haclient', 'haldaemon', 'haproxy', 'heat', 'hsqldb', 'ident', 'input', 'jbosson', 'jetty', 'jonas', 'keystone', 'kmem', 'kvm', 'ldap', 'lock', 'lp', 'luci', 'mail', 'mailman', 'mailnull', 'majordomo', 'man', 'mem', 'mock', 'mongodb', 'myproxy', 'mysql', 'named', 'netdump', 'news', 'nobody', 'nocpulse', 'nova', 'nscd', 'ntp', 'nut', 'oprofile', 'ovirt', 'ovirtagent', 'pegasus', 'piranha', 'pkiuser', 'polkituser', 'popusers', 'postdrop', 'postfix', 'postgres', 'pppusers', 'prelude-manager', 'privoxy', 'pulse', 'puppet', 'pvm', 'qemu', 'quagga', 'quaggavt', 'quantum', 'radiusd', 'radvd', 'realtime', 'render', 'retrace', 'rhevm', 'ricci', 'root', 'rpc', 'rpcuser', 'rpm', 'rtkit', 'sabayon', 'saned', 'sanlock', 'saslauth', 'screen', 'sgx', 'slipusers', 'slocate', 'smmsp', 'snortd', 'squid', 'sshd', 'stap-server', 'stapdev', 'stapsys', 'stapusr', 'swift', 'sys', 'systemd-journal', 'systemd-network', 'systemd-resolve', 'tape', 'tcpdump', 'tomcat', 'tss', 'tty', 'usbmuxd', 'users', 'utempter', 'utmp', 'uucp', 'vcsa', 'vhostmd', 'video', 'wallaby', 'wbpriv', 'webalizer', 'wheel', 'wildfly', 'wine', 'wnn', 'xfs'] diff --git a/configs/Fedora/warn-on-functions.toml b/configs/Fedora/warn-on-functions.toml new file mode 100644 index 0000000..58d848d --- /dev/null +++ b/configs/Fedora/warn-on-functions.toml @@ -0,0 +1,25 @@ +# Additional warnings on specific function calls +[WarnOnFunction] + +[WarnOnFunction.crypto-policy-non-compliance-openssl] +f_name = "SSL_CTX_set_cipher_list" +good_param = "PROFILE=SYSTEM" +description = """This application package calls a function to explicitly set crypto ciphers +for SSL/TLS. That may cause the application not to use the system-wide set +cryptographic policy and should be modified in accordance to: +https://fedoraproject.org/wiki/Packaging:CryptoPolicies""" + +[WarnOnFunction.crypto-policy-non-compliance-gnutls-1] +f_name = "gnutls_priority_set_direct" +description = """This application package calls a function to explicitly set crypto ciphers +for SSL/TLS. That may cause the application not to use the system-wide set +cryptographic policy and should be modified in accordance to: +https://fedoraproject.org/wiki/Packaging:CryptoPolicies""" + +[WarnOnFunction.crypto-policy-non-compliance-gnutls-2] +f_name = "gnutls_priority_init" +good_param = "SYSTEM" +description = """This application package calls a function to explicitly set crypto ciphers +for SSL/TLS. That may cause the application not to use the system-wide set +cryptographic policy and should be modified in accordance to: +https://fedoraproject.org/wiki/Packaging:CryptoPolicies""" diff --git a/configs/openSUSE/dbus-services.toml b/configs/openSUSE/dbus-services.toml new file mode 100644 index 0000000..f309b7c --- /dev/null +++ b/configs/openSUSE/dbus-services.toml @@ -0,0 +1,270 @@ +DBUSServices.WhiteList = [ + "cups.conf", # bnc#515977 + # + # the following are not audited. We accept them as legacy for now + # + # upower + "org.freedesktop.UPower.service", + "org.freedesktop.UPower.conf", + # PackageKit + "org.freedesktop.PackageKit.conf", + # PackageKit + "org.freedesktop.PackageKit.service", + # NetworkManager-pptp + "nm-pptp-service.conf", + # gdm + "gdm.conf", + # udisks2 (bnc#742751) + "org.freedesktop.UDisks2.service", + "org.freedesktop.UDisks2.conf", + # scmon + "com.novell.Pkcs11Monitor.conf", + # systemd (bnc#641924) + "org.freedesktop.systemd1.service", + "org.freedesktop.systemd1.conf", + "org.freedesktop.hostname1.service", + "org.freedesktop.hostname1.conf", + "org.freedesktop.login1.conf", + "org.freedesktop.login1.service", + "org.freedesktop.timedate1.conf", + "org.freedesktop.timedate1.service", + "org.freedesktop.locale1.conf", + "org.freedesktop.locale1.service", + # gconf2 + "org.gnome.GConf.Defaults.service", + "org.gnome.GConf.Defaults.conf", + # system-config-printer (bnc#694640) + "com.redhat.NewPrinterNotification.conf", + "com.redhat.PrinterDriversInstaller.conf", + # rtkit + "org.freedesktop.RealtimeKit1.conf", + "org.freedesktop.RealtimeKit1.service", + # wpa_supplicant + "fi.epitest.hostap.WPASupplicant.service", + # bnc#681116 + "fi.w1.wpa_supplicant1.service", + "wpa_supplicant.conf", + # kdebase4-workspace + "org.kde.fontinst.service", + "org.kde.fontinst.conf", + "org.kde.ksysguard.processlisthelper.service", + "org.kde.kcontrol.kcmclock.service", + "org.kde.kcontrol.kcmclock.conf", + "org.kde.ksysguard.processlisthelper.conf", + # pulseaudio + "pulseaudio-system.conf", + # avahi + "avahi-dbus.conf", + "org.freedesktop.Avahi.service", + # hp-drive-guard + "hp-drive-guard-dbus.conf", + # NetworkManager + "nm-dispatcher.conf", + "org.freedesktop.nm_dispatcher.service", + # bnc#747780 + "org.freedesktop.NetworkManager.conf", + # bluez (bnc#768062) + "bluetooth.conf", + "org.bluez.service", + # dnsmasq + "dnsmasq.conf", + # gypsy + "Gypsy.conf", + "org.freedesktop.Gypsy.service", + # pommed + "pommed.conf", + # NetworkManager-openvpn + "nm-openvpn-service.conf", + # polkit + "org.freedesktop.PolicyKit1.conf", + "org.freedesktop.PolicyKit1.service", + # cups-pk-helper + "org.opensuse.CupsPkHelper.Mechanism.service", + "org.opensuse.CupsPkHelper.Mechanism.conf", + # NetworkManager-vpnc + "nm-vpnc-service.conf", + # NetworkManager-strongswan, bnc#656222 + "nm-strongswan-service.conf", + # mumble, bnc#660784 + "mumble-server.conf", + # kdebase4-runtime, bnc#672145 + "org.kde.powerdevil.backlighthelper.service", + "org.kde.powerdevil.backlighthelper.conf", + # urfkill (bnc#688328) + "org.freedesktop.URfkill.service", + "org.freedesktop.URfkill.conf", + # account services (bnc#676638) + "org.freedesktop.Accounts.service", + "org.freedesktop.Accounts.conf", + # colord (bnc#698250) + "org.freedesktop.ColorManager.service", + "org.freedesktop.ColorManager.conf", + # lightdm (bnc#708205) + "org.freedesktop.DisplayManager.conf", + # sddm (boo#897788) + "sddm_org.freedesktop.DisplayManager.conf", + # NetworkManager-openvpn (bnc#732915) + "nm-openconnect-service.conf", + # snapper (bnc#759391) + "org.opensuse.Snapper.conf", + "org.opensuse.Snapper.service", + # autofs-udisk interaction (bnc#782691) + "org.freedesktop.AutoMount.conf", + # NetworkManager-iodine (bnc#781071) + "nm-iodine-service.conf", + # new ModemManager (bnc#798273) + "org.freedesktop.ModemManager1.conf", + "org.freedesktop.ModemManager1.service", + # fprintd 0.4.1 (finger print dbus service) (bnc#792095) + "net.reactivated.Fprint.service", + "net.reactivated.Fprint.conf", + # wicked network management (bnc#783932) + "org.opensuse.Network.conf", + "org.opensuse.Network.AUTO4.conf", + "org.opensuse.Network.DHCP6.conf", + "org.opensuse.Network.DHCP4.conf", + "org.opensuse.Network.Nanny.conf", + # systemd machined service (bnc#828207) + "org.freedesktop.machine1.service", + "org.freedesktop.machine1.conf", + # systemd importd service (bnc#964935) + "org.freedesktop.import1.service", + "org.freedesktop.import1.conf", + # GeoClue2 DBUS Service (bnc#838360) + "org.freedesktop.GeoClue2.service", + "org.freedesktop.GeoClue2.conf", + # GeoClue2 DBUS Service more (bnc#862216) + "org.freedesktop.GeoClue2.Agent.conf", + # mate dbus serice (bnc#831404) + "org.mate.SettingsDaemon.DateTimeMechanism.service", + "org.mate.SettingsDaemon.DateTimeMechanism.conf", + # tuned DBUS service (bnc#787379, bnc#1007279) + "com.redhat.tuned.conf", + # bluez (bnc#768062) + "bluetooth.conf", + "org.bluez.service", + # kwallet (bnc#1033296) + "org.kde.kcontrol.kcmkwallet5.conf", + "org.kde.kcontrol.kcmkwallet5.service", + # neard (bnc#837978) + "org.neard.conf", + # oFono (bnc#862354) + "ofono.conf", + # libKF5Auth4 (bnc#864716) + "org.kde.kf5auth.conf", + # firewalld (bnc#907625) + "FirewallD.conf", + # systemd networkd (bnc#918799) + "org.freedesktop.network1.conf", + "org.freedesktop.network1.service", + # realmd (bnc#916766) + "org.freedesktop.realmd.service", + "org.freedesktop.realmd.conf", + # teamd (bnc#941993) + "org.libteam.teamd.conf", + # cinnamon settings daemon (bsc#951830) + "org.cinnamon.SettingsDaemon.DateTimeMechanism.conf", + "org.cinnamon.SettingsDaemon.DateTimeMechanism.service", + # thermald (bsc#954771) + "org.freedesktop.thermald.conf", + "org.freedesktop.thermald.service", + # iio-sensor-proxy (bsc#939191) + "net.hadess.SensorProxy.conf", + # TEMPORARY APPROVAL ONLY (meissner 20160519) tcmu-runner (bsc#978903) + "tcmu-runner.conf", + "org.kernel.TCMUService1.service", + # sysprof (bsc#996111) + "org.gnome.Sysprof2.service", + "org.gnome.Sysprof2.conf", + # sysprof (bsc#1151418) + "org.gnome.Sysprof3.service", + "org.gnome.Sysprof3.conf", + # flatpak (bsc#984817) + "org.freedesktop.Flatpak.SystemHelper.service", + "org.freedesktop.Flatpak.SystemHelper.conf", + # systemd resolver, but dont add automatically to nsswitch.conf! (bsc#917781) + "org.freedesktop.resolve1.conf", + "org.freedesktop.resolve1.service", + # powerdevil discretegpuhelper (bsc#1019748) + "org.kde.powerdevil.discretegpuhelper.conf", + "org.kde.powerdevil.discretegpuhelper.service", + # rebootmgr (bsc#1019644) + "org.opensuse.RebootMgr.conf", + # blueman (bsc#987141) + "org.blueman.Mechanism.conf", + "org.blueman.Mechanism.service", + # os-autoinst (bsc#1032649) + "org.opensuse.os_autoinst.switch.conf", + # backintime (bsc#1007723, bsc#1032717) + "net.launchpad.backintime.serviceHelper.conf", + "net.launchpad.backintime.serviceHelper.service", + # switchroo-control (bsc#1034309) + "net.hadess.SwitcherooControl.conf", + # pam_dbus (bsc#1039709). Take care to + # never enable/integrate this by default (see bsc comments) + "pam_dbus.conf", + # tpm2-abrmd (bnc#1049694) + "tpm2-abrmd.conf", + "com.intel.tss2.Tabrmd.service", + # nfs-ganesha (bsc#997880) + "org.ganesha.nfsd.conf", + # NetworkManager-l2tp (bsc#846337) + "nm-l2tp-service.conf", + # fwupd (bsc#932807) + "org.freedesktop.fwupd.conf", + "org.freedesktop.fwupd.service", + # connman (bsc#1057697) + "connman-nmcompat.conf", + "connman.conf", + "connman-vpn-dbus.conf", + "net.connman.vpn.service", + # kcmsddm (bsc#1065563) + "org.kde.kcontrol.kcmsddm.conf", + "org.kde.kcontrol.kcmsddm.service", + # usbauth (bsc#1066877) + "org.opensuse.usbauth.conf", + # kalarm (bnc#1087714, renamed from kalarmrtcwake) + "org.kde.kalarm.rtcwake.conf", + "org.kde.kalarm.rtcwake.service", + # NetworkManager-libreswan (bnc#1089340) + "nm-libreswan-service.conf", + # libratbag (bnc#1076467) + "org.freedesktop.ratbag1.service", + "org.freedesktop.ratbag1.conf", + # xpra (bsc#1102836) + "xpra.conf", + # iwd (bsc#1108037) + "net.connman.iwd.service", + "iwd-dbus.conf", + # NetworkManager-fortisslvpn (bsc#1109938) + "nm-fortisslvpn-service.conf", + # systemd-timesyncd (bsc#1111254) + "org.freedesktop.timesync1.service", + "org.freedesktop.timesync1.conf", + # keepalived (bsc#1015141) + "org.keepalived.Vrrp1.conf", + # boltd (bsc#1119975) + "org.freedesktop.bolt.conf", + "org.freedesktop.bolt.service", + # certmonger (bsc#1129452) + "org.fedorahosted.certmonger.service", + "certmonger.conf", + # systemd-portabled (boo#1145639) + "org.freedesktop.portable1.service", + "org.freedesktop.portable1.conf", + # sssd (bsc#1157663, bsc#1106600) + "org.freedesktop.sssd.infopipe.service", + "org.freedesktop.sssd.infopipe.conf", + # oddjob (bsc#1169494) + "oddjob.conf", + "oddjob-mkhomedir.conf", + # libvirt-dbus (bsc#1173093) + "org.libvirt.service", + "org.libvirt.conf", + # powerdevil chargethreshold helper (bsc#1176474) + "org.kde.powerdevil.chargethresholdhelper.service", + "org.kde.powerdevil.chargethresholdhelper.conf", + # plasma5-disks smartmon helper + "org.kde.kded.smart.service", + "org.kde.kded.smart.conf" +] diff --git a/configs/openSUSE/licenses.toml b/configs/openSUSE/licenses.toml new file mode 100644 index 0000000..8e0536d --- /dev/null +++ b/configs/openSUSE/licenses.toml @@ -0,0 +1,1173 @@ +# Generated with generate-suse-licenses.py script: +ValidLicenses = [ + "0BSD", + "0BSD+", + "AAL", + "AAL+", + "ADSL", + "ADSL+", + "AFL-1.1", + "AFL-1.1+", + "AFL-1.2", + "AFL-1.2+", + "AFL-2.0", + "AFL-2.0+", + "AFL-2.1", + "AFL-2.1+", + "AFL-3.0", + "AFL-3.0+", + "AGPL-1.0-only", + "AGPL-1.0-only+", + "AGPL-1.0-or-later", + "AGPL-1.0-or-later+", + "AGPL-3.0-only", + "AGPL-3.0-only+", + "AGPL-3.0-or-later", + "AGPL-3.0-or-later+", + "AMDPLPA", + "AMDPLPA+", + "AML", + "AML+", + "AMPAS", + "AMPAS+", + "ANTLR-PD", + "ANTLR-PD+", + "ANTLR-PD-fallback", + "ANTLR-PD-fallback+", + "APAFML", + "APAFML+", + "APL-1.0", + "APL-1.0+", + "APSL-1.0", + "APSL-1.0+", + "APSL-1.1", + "APSL-1.1+", + "APSL-1.2", + "APSL-1.2+", + "APSL-2.0", + "APSL-2.0+", + "Abstyles", + "Abstyles+", + "Adobe-2006", + "Adobe-2006+", + "Adobe-Glyph", + "Adobe-Glyph+", + "Afmparse", + "Afmparse+", + "Aladdin", + "Aladdin+", + "Apache-1.0", + "Apache-1.0+", + "Apache-1.1", + "Apache-1.1+", + "Apache-2.0", + "Apache-2.0+", + "App-s2p", + "App-s2p+", + "Arphic-1999", + "Arphic-1999+", + "Artistic-1.0", + "Artistic-1.0+", + "Artistic-1.0 OR GPL-1.0-or-later", + "Artistic-1.0-Perl", + "Artistic-1.0-Perl+", + "Artistic-1.0-cl8", + "Artistic-1.0-cl8+", + "Artistic-2.0", + "Artistic-2.0+", + "BSD-1-Clause", + "BSD-1-Clause+", + "BSD-2-Clause", + "BSD-2-Clause+", + "BSD-2-Clause-Patent", + "BSD-2-Clause-Patent+", + "BSD-2-Clause-Views", + "BSD-2-Clause-Views+", + "BSD-3-Clause", + "BSD-3-Clause+", + "BSD-3-Clause-Attribution", + "BSD-3-Clause-Attribution+", + "BSD-3-Clause-Clear", + "BSD-3-Clause-Clear+", + "BSD-3-Clause-LBNL", + "BSD-3-Clause-LBNL+", + "BSD-3-Clause-Modification", + "BSD-3-Clause-Modification+", + "BSD-3-Clause-No-Military-License", + "BSD-3-Clause-No-Military-License+", + "BSD-3-Clause-No-Nuclear-License", + "BSD-3-Clause-No-Nuclear-License+", + "BSD-3-Clause-No-Nuclear-License-2014", + "BSD-3-Clause-No-Nuclear-License-2014+", + "BSD-3-Clause-No-Nuclear-Warranty", + "BSD-3-Clause-No-Nuclear-Warranty+", + "BSD-3-Clause-Open-MPI", + "BSD-3-Clause-Open-MPI+", + "BSD-4-Clause", + "BSD-4-Clause+", + "BSD-4-Clause-Shortened", + "BSD-4-Clause-Shortened+", + "BSD-4-Clause-UC", + "BSD-4-Clause-UC+", + "BSD-Protection", + "BSD-Protection+", + "BSD-Source-Code", + "BSD-Source-Code+", + "BSL-1.0", + "BSL-1.0+", + "BUSL-1.1", + "BUSL-1.1+", + "Baekmuk", + "Baekmuk+", + "Bahyph", + "Bahyph+", + "Barr", + "Barr+", + "Beerware", + "Beerware+", + "BitTorrent-1.0", + "BitTorrent-1.0+", + "BitTorrent-1.1", + "BitTorrent-1.1+", + "Bitstream-Vera", + "Bitstream-Vera+", + "BlueOak-1.0.0", + "BlueOak-1.0.0+", + "Borceux", + "Borceux+", + "C-UDA-1.0", + "C-UDA-1.0+", + "CAL-1.0", + "CAL-1.0+", + "CAL-1.0-Combined-Work-Exception", + "CAL-1.0-Combined-Work-Exception+", + "CATOSL-1.1", + "CATOSL-1.1+", + "CC-BY-1.0", + "CC-BY-1.0+", + "CC-BY-2.0", + "CC-BY-2.0+", + "CC-BY-2.5", + "CC-BY-2.5+", + "CC-BY-2.5-AU", + "CC-BY-2.5-AU+", + "CC-BY-3.0", + "CC-BY-3.0+", + "CC-BY-3.0-AT", + "CC-BY-3.0-AT+", + "CC-BY-3.0-DE", + "CC-BY-3.0-DE+", + "CC-BY-3.0-IGO", + "CC-BY-3.0-IGO+", + "CC-BY-3.0-NL", + "CC-BY-3.0-NL+", + "CC-BY-3.0-US", + "CC-BY-3.0-US+", + "CC-BY-4.0", + "CC-BY-4.0+", + "CC-BY-NC-1.0", + "CC-BY-NC-1.0+", + "CC-BY-NC-2.0", + "CC-BY-NC-2.0+", + "CC-BY-NC-2.5", + "CC-BY-NC-2.5+", + "CC-BY-NC-3.0", + "CC-BY-NC-3.0+", + "CC-BY-NC-3.0-DE", + "CC-BY-NC-3.0-DE+", + "CC-BY-NC-4.0", + "CC-BY-NC-4.0+", + "CC-BY-NC-ND-1.0", + "CC-BY-NC-ND-1.0+", + "CC-BY-NC-ND-2.0", + "CC-BY-NC-ND-2.0+", + "CC-BY-NC-ND-2.5", + "CC-BY-NC-ND-2.5+", + "CC-BY-NC-ND-3.0", + "CC-BY-NC-ND-3.0+", + "CC-BY-NC-ND-3.0-DE", + "CC-BY-NC-ND-3.0-DE+", + "CC-BY-NC-ND-3.0-IGO", + "CC-BY-NC-ND-3.0-IGO+", + "CC-BY-NC-ND-4.0", + "CC-BY-NC-ND-4.0+", + "CC-BY-NC-SA-1.0", + "CC-BY-NC-SA-1.0+", + "CC-BY-NC-SA-2.0", + "CC-BY-NC-SA-2.0+", + "CC-BY-NC-SA-2.0-FR", + "CC-BY-NC-SA-2.0-FR+", + "CC-BY-NC-SA-2.0-UK", + "CC-BY-NC-SA-2.0-UK+", + "CC-BY-NC-SA-2.5", + "CC-BY-NC-SA-2.5+", + "CC-BY-NC-SA-3.0", + "CC-BY-NC-SA-3.0+", + "CC-BY-NC-SA-3.0-DE", + "CC-BY-NC-SA-3.0-DE+", + "CC-BY-NC-SA-3.0-IGO", + "CC-BY-NC-SA-3.0-IGO+", + "CC-BY-NC-SA-4.0", + "CC-BY-NC-SA-4.0+", + "CC-BY-ND-1.0", + "CC-BY-ND-1.0+", + "CC-BY-ND-2.0", + "CC-BY-ND-2.0+", + "CC-BY-ND-2.5", + "CC-BY-ND-2.5+", + "CC-BY-ND-3.0", + "CC-BY-ND-3.0+", + "CC-BY-ND-3.0-DE", + "CC-BY-ND-3.0-DE+", + "CC-BY-ND-4.0", + "CC-BY-ND-4.0+", + "CC-BY-SA-1.0", + "CC-BY-SA-1.0+", + "CC-BY-SA-2.0", + "CC-BY-SA-2.0+", + "CC-BY-SA-2.0-UK", + "CC-BY-SA-2.0-UK+", + "CC-BY-SA-2.1-JP", + "CC-BY-SA-2.1-JP+", + "CC-BY-SA-2.5", + "CC-BY-SA-2.5+", + "CC-BY-SA-3.0", + "CC-BY-SA-3.0+", + "CC-BY-SA-3.0-AT", + "CC-BY-SA-3.0-AT+", + "CC-BY-SA-3.0-DE", + "CC-BY-SA-3.0-DE+", + "CC-BY-SA-4.0", + "CC-BY-SA-4.0+", + "CC-PDDC", + "CC-PDDC+", + "CC0-1.0", + "CC0-1.0+", + "CDDL-1.0", + "CDDL-1.0+", + "CDDL-1.1", + "CDDL-1.1+", + "CDL-1.0", + "CDL-1.0+", + "CDLA-Permissive-1.0", + "CDLA-Permissive-1.0+", + "CDLA-Permissive-2.0", + "CDLA-Permissive-2.0+", + "CDLA-Sharing-1.0", + "CDLA-Sharing-1.0+", + "CECILL-1.0", + "CECILL-1.0+", + "CECILL-1.1", + "CECILL-1.1+", + "CECILL-2.0", + "CECILL-2.0+", + "CECILL-2.1", + "CECILL-2.1+", + "CECILL-B", + "CECILL-B+", + "CECILL-C", + "CECILL-C+", + "CERN-OHL-1.1", + "CERN-OHL-1.1+", + "CERN-OHL-1.2", + "CERN-OHL-1.2+", + "CERN-OHL-P-2.0", + "CERN-OHL-P-2.0+", + "CERN-OHL-S-2.0", + "CERN-OHL-S-2.0+", + "CERN-OHL-W-2.0", + "CERN-OHL-W-2.0+", + "CNRI-Jython", + "CNRI-Jython+", + "CNRI-Python", + "CNRI-Python+", + "CNRI-Python-GPL-Compatible", + "CNRI-Python-GPL-Compatible+", + "COIL-1.0", + "COIL-1.0+", + "CPAL-1.0", + "CPAL-1.0+", + "CPL-1.0", + "CPL-1.0+", + "CPOL-1.02", + "CPOL-1.02+", + "CUA-OPL-1.0", + "CUA-OPL-1.0+", + "Caldera", + "Caldera+", + "ClArtistic", + "ClArtistic+", + "Community-Spec-1.0", + "Community-Spec-1.0+", + "Condor-1.1", + "Condor-1.1+", + "Crossword", + "Crossword+", + "CrystalStacker", + "CrystalStacker+", + "Cube", + "Cube+", + "D-FSL-1.0", + "D-FSL-1.0+", + "DL-DE-BY-2.0", + "DL-DE-BY-2.0+", + "DOC", + "DOC+", + "DRL-1.0", + "DRL-1.0+", + "DSDP", + "DSDP+", + "Dotseqn", + "Dotseqn+", + "ECL-1.0", + "ECL-1.0+", + "ECL-2.0", + "ECL-2.0+", + "EFL-1.0", + "EFL-1.0+", + "EFL-2.0", + "EFL-2.0+", + "EPICS", + "EPICS+", + "EPL-1.0", + "EPL-1.0+", + "EPL-2.0", + "EPL-2.0+", + "EUDatagrid", + "EUDatagrid+", + "EUPL-1.0", + "EUPL-1.0+", + "EUPL-1.1", + "EUPL-1.1+", + "EUPL-1.2", + "EUPL-1.2+", + "Elastic-2.0", + "Elastic-2.0+", + "Entessa", + "Entessa+", + "ErlPL-1.1", + "ErlPL-1.1+", + "Eurosym", + "Eurosym+", + "FDK-AAC", + "FDK-AAC+", + "FSFAP", + "FSFAP+", + "FSFUL", + "FSFUL+", + "FSFULLR", + "FSFULLR+", + "FSFULLRWD", + "FSFULLRWD+", + "FTL", + "FTL+", + "Fair", + "Fair+", + "Frameworx-1.0", + "Frameworx-1.0+", + "FreeBSD-DOC", + "FreeBSD-DOC+", + "FreeImage", + "FreeImage+", + "GD", + "GD+", + "GFDL-1.1-invariants-only", + "GFDL-1.1-invariants-only+", + "GFDL-1.1-invariants-or-later", + "GFDL-1.1-invariants-or-later+", + "GFDL-1.1-no-invariants-only", + "GFDL-1.1-no-invariants-only+", + "GFDL-1.1-no-invariants-or-later", + "GFDL-1.1-no-invariants-or-later+", + "GFDL-1.1-only", + "GFDL-1.1-only+", + "GFDL-1.1-or-later", + "GFDL-1.1-or-later+", + "GFDL-1.2-invariants-only", + "GFDL-1.2-invariants-only+", + "GFDL-1.2-invariants-or-later", + "GFDL-1.2-invariants-or-later+", + "GFDL-1.2-no-invariants-only", + "GFDL-1.2-no-invariants-only+", + "GFDL-1.2-no-invariants-or-later", + "GFDL-1.2-no-invariants-or-later+", + "GFDL-1.2-only", + "GFDL-1.2-only+", + "GFDL-1.2-or-later", + "GFDL-1.2-or-later+", + "GFDL-1.3-invariants-only", + "GFDL-1.3-invariants-only+", + "GFDL-1.3-invariants-or-later", + "GFDL-1.3-invariants-or-later+", + "GFDL-1.3-no-invariants-only", + "GFDL-1.3-no-invariants-only+", + "GFDL-1.3-no-invariants-or-later", + "GFDL-1.3-no-invariants-or-later+", + "GFDL-1.3-only", + "GFDL-1.3-only+", + "GFDL-1.3-or-later", + "GFDL-1.3-or-later+", + "GL2PS", + "GL2PS+", + "GLWTPL", + "GLWTPL+", + "GPL-1.0-only", + "GPL-1.0-only+", + "GPL-1.0-or-later", + "GPL-1.0-or-later+", + "GPL-2.0-only", + "GPL-2.0-only+", + "GPL-2.0-or-later", + "GPL-2.0-or-later+", + "GPL-3.0-only", + "GPL-3.0-only+", + "GPL-3.0-only WITH GCC-exception-3.1", + "GPL-3.0-or-later", + "GPL-3.0-or-later+", + "GPL-3.0-or-later WITH Autoconf-exception-3.0", + "GPL-3.0-with-Qt-Company-Qt-exception-1.1", + "Giftware", + "Giftware+", + "Glide", + "Glide+", + "Glulxe", + "Glulxe+", + "HPND", + "HPND+", + "HPND-sell-variant", + "HPND-sell-variant+", + "HTMLTIDY", + "HTMLTIDY+", + "HaskellReport", + "HaskellReport+", + "Hippocratic-2.1", + "Hippocratic-2.1+", + "IBM-pibs", + "IBM-pibs+", + "ICU", + "ICU+", + "IJG", + "IJG+", + "IPA", + "IPA+", + "IPL-1.0", + "IPL-1.0+", + "ISC", + "ISC+", + "ImageMagick", + "ImageMagick+", + "Imlib2", + "Imlib2+", + "Info-ZIP", + "Info-ZIP+", + "Intel", + "Intel+", + "Intel-ACPI", + "Intel-ACPI+", + "Interbase-1.0", + "Interbase-1.0+", + "JPNIC", + "JPNIC+", + "JSON", + "JSON+", + "Jam", + "Jam+", + "JasPer-2.0", + "JasPer-2.0+", + "LAL-1.2", + "LAL-1.2+", + "LAL-1.3", + "LAL-1.3+", + "LGPL-2.0-only", + "LGPL-2.0-only+", + "LGPL-2.0-or-later", + "LGPL-2.0-or-later+", + "LGPL-2.1-only", + "LGPL-2.1-only+", + "LGPL-2.1-or-later", + "LGPL-2.1-or-later+", + "LGPL-2.1-or-later WITH GCC-exception-2.0", + "LGPL-2.1-with-Qt-Company-Qt-exception-1.1", + "LGPL-3.0-only", + "LGPL-3.0-only+", + "LGPL-3.0-only WITH openvpn-openssl-exception", + "LGPL-3.0-or-later", + "LGPL-3.0-or-later+", + "LGPL-3.0-with-Qt-Company-Qt-exception-1.1", + "LGPLLR", + "LGPLLR+", + "LPL-1.0", + "LPL-1.0+", + "LPL-1.02", + "LPL-1.02+", + "LPPL-1.0", + "LPPL-1.0+", + "LPPL-1.1", + "LPPL-1.1+", + "LPPL-1.2", + "LPPL-1.2+", + "LPPL-1.3a", + "LPPL-1.3a+", + "LPPL-1.3c", + "LPPL-1.3c+", + "LZMA-SDK-9.11-to-9.20", + "LZMA-SDK-9.11-to-9.20+", + "LZMA-SDK-9.22", + "LZMA-SDK-9.22+", + "Latex2e", + "Latex2e+", + "Leptonica", + "Leptonica+", + "LiLiQ-P-1.1", + "LiLiQ-P-1.1+", + "LiLiQ-R-1.1", + "LiLiQ-R-1.1+", + "LiLiQ-Rplus-1.1", + "LiLiQ-Rplus-1.1+", + "Libpng", + "Libpng+", + "Linux-OpenIB", + "Linux-OpenIB+", + "Linux-man-pages-copyleft", + "Linux-man-pages-copyleft+", + "MIT", + "MIT+", + "MIT-0", + "MIT-0+", + "MIT-CMU", + "MIT-CMU+", + "MIT-Modern-Variant", + "MIT-Modern-Variant+", + "MIT-advertising", + "MIT-advertising+", + "MIT-enna", + "MIT-enna+", + "MIT-feh", + "MIT-feh+", + "MIT-open-group", + "MIT-open-group+", + "MITNFA", + "MITNFA+", + "MPL-1.0", + "MPL-1.0+", + "MPL-1.1", + "MPL-1.1+", + "MPL-2.0", + "MPL-2.0+", + "MPL-2.0-no-copyleft-exception", + "MPL-2.0-no-copyleft-exception+", + "MS-LPL", + "MS-LPL+", + "MS-PL", + "MS-PL+", + "MS-RL", + "MS-RL+", + "MTLL", + "MTLL+", + "MakeIndex", + "MakeIndex+", + "Minpack", + "Minpack+", + "MirOS", + "MirOS+", + "Motosoto", + "Motosoto+", + "MulanPSL-1.0", + "MulanPSL-1.0+", + "MulanPSL-2.0", + "MulanPSL-2.0+", + "Multics", + "Multics+", + "Mup", + "Mup+", + "NAIST-2003", + "NAIST-2003+", + "NASA-1.3", + "NASA-1.3+", + "NBPL-1.0", + "NBPL-1.0+", + "NCGL-UK-2.0", + "NCGL-UK-2.0+", + "NCSA", + "NCSA+", + "NGPL", + "NGPL+", + "NICTA-1.0", + "NICTA-1.0+", + "NIST-PD", + "NIST-PD+", + "NIST-PD-fallback", + "NIST-PD-fallback+", + "NLOD-1.0", + "NLOD-1.0+", + "NLOD-2.0", + "NLOD-2.0+", + "NLPL", + "NLPL+", + "NOSL", + "NOSL+", + "NPL-1.0", + "NPL-1.0+", + "NPL-1.1", + "NPL-1.1+", + "NPOSL-3.0", + "NPOSL-3.0+", + "NRL", + "NRL+", + "NTP", + "NTP+", + "NTP-0", + "NTP-0+", + "Naumen", + "Naumen+", + "Net-SNMP", + "Net-SNMP+", + "NetCDF", + "NetCDF+", + "Newsletr", + "Newsletr+", + "Nokia", + "Nokia+", + "NonFree", + "NonFree+", + "Noweb", + "Noweb+", + "O-UDA-1.0", + "O-UDA-1.0+", + "OCCT-PL", + "OCCT-PL+", + "OCLC-2.0", + "OCLC-2.0+", + "ODC-By-1.0", + "ODC-By-1.0+", + "ODbL-1.0", + "ODbL-1.0+", + "OFL-1.0", + "OFL-1.0+", + "OFL-1.0-RFN", + "OFL-1.0-RFN+", + "OFL-1.0-no-RFN", + "OFL-1.0-no-RFN+", + "OFL-1.1", + "OFL-1.1+", + "OFL-1.1-RFN", + "OFL-1.1-RFN+", + "OFL-1.1-no-RFN", + "OFL-1.1-no-RFN+", + "OGC-1.0", + "OGC-1.0+", + "OGDL-Taiwan-1.0", + "OGDL-Taiwan-1.0+", + "OGL-Canada-2.0", + "OGL-Canada-2.0+", + "OGL-UK-1.0", + "OGL-UK-1.0+", + "OGL-UK-2.0", + "OGL-UK-2.0+", + "OGL-UK-3.0", + "OGL-UK-3.0+", + "OGTSL", + "OGTSL+", + "OLDAP-1.1", + "OLDAP-1.1+", + "OLDAP-1.2", + "OLDAP-1.2+", + "OLDAP-1.3", + "OLDAP-1.3+", + "OLDAP-1.4", + "OLDAP-1.4+", + "OLDAP-2.0", + "OLDAP-2.0+", + "OLDAP-2.0.1", + "OLDAP-2.0.1+", + "OLDAP-2.1", + "OLDAP-2.1+", + "OLDAP-2.2", + "OLDAP-2.2+", + "OLDAP-2.2.1", + "OLDAP-2.2.1+", + "OLDAP-2.2.2", + "OLDAP-2.2.2+", + "OLDAP-2.3", + "OLDAP-2.3+", + "OLDAP-2.4", + "OLDAP-2.4+", + "OLDAP-2.5", + "OLDAP-2.5+", + "OLDAP-2.6", + "OLDAP-2.6+", + "OLDAP-2.7", + "OLDAP-2.7+", + "OLDAP-2.8", + "OLDAP-2.8+", + "OML", + "OML+", + "OPL-1.0", + "OPL-1.0+", + "OPUBL-1.0", + "OPUBL-1.0+", + "OSET-PL-2.1", + "OSET-PL-2.1+", + "OSL-1.0", + "OSL-1.0+", + "OSL-1.1", + "OSL-1.1+", + "OSL-2.0", + "OSL-2.0+", + "OSL-2.1", + "OSL-2.1+", + "OSL-3.0", + "OSL-3.0+", + "OpenSSL", + "OpenSSL+", + "PDDL-1.0", + "PDDL-1.0+", + "PHP-3.0", + "PHP-3.0+", + "PHP-3.01", + "PHP-3.01+", + "PSF-2.0", + "PSF-2.0+", + "Parity-6.0.0", + "Parity-6.0.0+", + "Parity-7.0.0", + "Parity-7.0.0+", + "Plexus", + "Plexus+", + "PolyForm-Noncommercial-1.0.0", + "PolyForm-Noncommercial-1.0.0+", + "PolyForm-Small-Business-1.0.0", + "PolyForm-Small-Business-1.0.0+", + "PostgreSQL", + "PostgreSQL+", + "Python-2.0", + "Python-2.0+", + "Python-2.0.1", + "Python-2.0.1+", + "QPL-1.0", + "QPL-1.0+", + "Qhull", + "Qhull+", + "RHeCos-1.1", + "RHeCos-1.1+", + "RPL-1.1", + "RPL-1.1+", + "RPL-1.5", + "RPL-1.5+", + "RPSL-1.0", + "RPSL-1.0+", + "RSA-MD", + "RSA-MD+", + "RSCPL", + "RSCPL+", + "Rdisc", + "Rdisc+", + "Ruby", + "Ruby+", + "SAX-PD", + "SAX-PD+", + "SCEA", + "SCEA+", + "SGI-B-1.0", + "SGI-B-1.0+", + "SGI-B-1.1", + "SGI-B-1.1+", + "SGI-B-2.0", + "SGI-B-2.0+", + "SHL-0.5", + "SHL-0.5+", + "SHL-0.51", + "SHL-0.51+", + "SISSL", + "SISSL+", + "SISSL-1.2", + "SISSL-1.2+", + "SMLNJ", + "SMLNJ+", + "SMPPL", + "SMPPL+", + "SNIA", + "SNIA+", + "SPL-1.0", + "SPL-1.0+", + "SSH-OpenSSH", + "SSH-OpenSSH+", + "SSH-short", + "SSH-short+", + "SSPL-1.0", + "SSPL-1.0+", + "SUSE-BSD-3-Clause-with-non-nuclear-addition", + "SUSE-BSD-3-Clause-with-non-nuclear-addition+", + "SUSE-BSD-Mark-Modifications", + "SUSE-BSD-Mark-Modifications+", + "SUSE-CC-Sampling-Plus-1.0", + "SUSE-CC-Sampling-Plus-1.0+", + "SUSE-CPL-0.5", + "SUSE-CPL-0.5+", + "SUSE-CacertRoot", + "SUSE-CacertRoot+", + "SUSE-Copyleft-Next-0.3.0", + "SUSE-Copyleft-Next-0.3.0+", + "SUSE-Curb", + "SUSE-Curb+", + "SUSE-DMTF", + "SUSE-DMTF+", + "SUSE-Docbook-XSL", + "SUSE-Docbook-XSL+", + "SUSE-EULA", + "SUSE-EULA+", + "SUSE-Egenix-1.1.0", + "SUSE-Egenix-1.1.0+", + "SUSE-FHS", + "SUSE-FHS+", + "SUSE-FLTK", + "SUSE-FLTK+", + "SUSE-Firmware", + "SUSE-Firmware+", + "SUSE-Free-Art-1.3", + "SUSE-Free-Art-1.3+", + "SUSE-Freetype", + "SUSE-Freetype+", + "SUSE-Freeware", + "SUSE-Freeware+", + "SUSE-GL2PS-2.0", + "SUSE-GL2PS-2.0+", + "SUSE-GPL-2.0+-with-openssl-exception", + "SUSE-GPL-2.0+-with-openssl-exception+", + "SUSE-GPL-2.0+-with-sane-exception", + "SUSE-GPL-2.0+-with-sane-exception+", + "SUSE-GPL-2.0-with-FLOSS-exception", + "SUSE-GPL-2.0-with-FLOSS-exception+", + "SUSE-GPL-2.0-with-OSI-exception", + "SUSE-GPL-2.0-with-OSI-exception+", + "SUSE-GPL-2.0-with-linking-exception", + "SUSE-GPL-2.0-with-linking-exception+", + "SUSE-GPL-2.0-with-openssl-exception", + "SUSE-GPL-2.0-with-openssl-exception+", + "SUSE-GPL-2.0-with-plugin-exception", + "SUSE-GPL-2.0-with-plugin-exception+", + "SUSE-GPL-3.0+-with-font-exception", + "SUSE-GPL-3.0+-with-font-exception+", + "SUSE-GPL-3.0+-with-openssl-exception", + "SUSE-GPL-3.0+-with-openssl-exception+", + "SUSE-GPL-3.0-with-FLOSS-exception", + "SUSE-GPL-3.0-with-FLOSS-exception+", + "SUSE-GPL-3.0-with-font-exception", + "SUSE-GPL-3.0-with-font-exception+", + "SUSE-GPL-3.0-with-openssl-exception", + "SUSE-GPL-3.0-with-openssl-exception+", + "SUSE-GPL-3.0-with-template-exception", + "SUSE-GPL-3.0-with-template-exception+", + "SUSE-Gitslave", + "SUSE-Gitslave+", + "SUSE-Gnuplot", + "SUSE-Gnuplot+", + "SUSE-Hack-Open-Font-2.0", + "SUSE-Hack-Open-Font-2.0+", + "SUSE-IBPL-1.0", + "SUSE-IBPL-1.0+", + "SUSE-IDPL-1.0", + "SUSE-IDPL-1.0+", + "SUSE-IEEE", + "SUSE-IEEE+", + "SUSE-Innernet-2.0", + "SUSE-Innernet-2.0+", + "SUSE-Innernet-2.00", + "SUSE-Innernet-2.00+", + "SUSE-LDPL-2.0", + "SUSE-LDPL-2.0+", + "SUSE-LGPL-2.0-with-linking-exception", + "SUSE-LGPL-2.0-with-linking-exception+", + "SUSE-LGPL-2.1-with-digia-exception-1.1", + "SUSE-LGPL-2.1-with-digia-exception-1.1+", + "SUSE-LGPL-2.1-with-nokia-exception-1.1", + "SUSE-LGPL-2.1-with-nokia-exception-1.1+", + "SUSE-Liberation", + "SUSE-Liberation+", + "SUSE-MIT-Khronos", + "SUSE-MIT-Khronos+", + "SUSE-Manpages", + "SUSE-Manpages+", + "SUSE-Matplotlib", + "SUSE-Matplotlib+", + "SUSE-MgOpen", + "SUSE-MgOpen+", + "SUSE-Oasis-Specification-Notice", + "SUSE-Oasis-Specification-Notice+", + "SUSE-OldFSFDocLicense", + "SUSE-OldFSFDocLicense+", + "SUSE-OpenPublication-1.0", + "SUSE-OpenPublication-1.0+", + "SUSE-PHP-2.02", + "SUSE-PHP-2.02+", + "SUSE-Permissive", + "SUSE-Permissive+", + "SUSE-Permissive-Modify-By-Patch", + "SUSE-Permissive-Modify-By-Patch+", + "SUSE-Public-Domain", + "SUSE-Public-Domain+", + "SUSE-Python-1.6", + "SUSE-Python-1.6+", + "SUSE-QWT-1.0", + "SUSE-QWT-1.0+", + "SUSE-Redistributable-Content", + "SUSE-Redistributable-Content+", + "SUSE-Repoze", + "SUSE-Repoze+", + "SUSE-SIP", + "SUSE-SIP+", + "SUSE-SLIB", + "SUSE-SLIB+", + "SUSE-SNIA-1.0", + "SUSE-SNIA-1.0+", + "SUSE-SNIA-1.1", + "SUSE-SNIA-1.1+", + "SUSE-Scrot", + "SUSE-Scrot+", + "SUSE-Sun-Laboratories", + "SUSE-Sun-Laboratories+", + "SUSE-TGPPL-1.0", + "SUSE-TGPPL-1.0+", + "SUSE-TeX", + "SUSE-TeX+", + "SUSE-Ubuntu-Font-License-1.0", + "SUSE-Ubuntu-Font-License-1.0+", + "SUSE-XDebug", + "SUSE-XDebug+", + "SUSE-XFree86-with-font-exception", + "SUSE-XFree86-with-font-exception+", + "SUSE-XSL-Lint", + "SUSE-XSL-Lint+", + "SUSE-Xano", + "SUSE-Xano+", + "SUSE-Xenonsoft-1.00", + "SUSE-Xenonsoft-1.00+", + "SUSE-mirror", + "SUSE-mirror+", + "SUSE-wxWidgets-3.1", + "SUSE-wxWidgets-3.1+", + "SWL", + "SWL+", + "Saxpath", + "Saxpath+", + "SchemeReport", + "SchemeReport+", + "Sendmail", + "Sendmail+", + "Sendmail-8.23", + "Sendmail-8.23+", + "SimPL-2.0", + "SimPL-2.0+", + "Sleepycat", + "Sleepycat+", + "Spencer-86", + "Spencer-86+", + "Spencer-94", + "Spencer-94+", + "Spencer-99", + "Spencer-99+", + "SugarCRM-1.1.3", + "SugarCRM-1.1.3+", + "TAPR-OHL-1.0", + "TAPR-OHL-1.0+", + "TCL", + "TCL+", + "TCP-wrappers", + "TCP-wrappers+", + "TMate", + "TMate+", + "TORQUE-1.1", + "TORQUE-1.1+", + "TOSL", + "TOSL+", + "TU-Berlin-1.0", + "TU-Berlin-1.0+", + "TU-Berlin-2.0", + "TU-Berlin-2.0+", + "UCL-1.0", + "UCL-1.0+", + "UPL-1.0", + "UPL-1.0+", + "Unicode", + "Unicode+", + "Unicode-DFS-2015", + "Unicode-DFS-2015+", + "Unicode-DFS-2016", + "Unicode-DFS-2016+", + "Unicode-TOU", + "Unicode-TOU+", + "Unlicense", + "Unlicense+", + "VOSTROM", + "VOSTROM+", + "VSL-1.0", + "VSL-1.0+", + "Vim", + "Vim+", + "W3C", + "W3C+", + "W3C-19980720", + "W3C-19980720+", + "W3C-20150513", + "W3C-20150513+", + "WTFPL", + "WTFPL+", + "Watcom-1.0", + "Watcom-1.0+", + "Wsuipa", + "Wsuipa+", + "X11", + "X11+", + "X11-distribute-modifications-variant", + "X11-distribute-modifications-variant+", + "XFree86-1.1", + "XFree86-1.1+", + "XSkat", + "XSkat+", + "Xerox", + "Xerox+", + "Xnet", + "Xnet+", + "YPL-1.0", + "YPL-1.0+", + "YPL-1.1", + "YPL-1.1+", + "ZPL-1.1", + "ZPL-1.1+", + "ZPL-2.0", + "ZPL-2.0+", + "ZPL-2.1", + "ZPL-2.1+", + "Zed", + "Zed+", + "Zend-2.0", + "Zend-2.0+", + "Zimbra-1.3", + "Zimbra-1.3+", + "Zimbra-1.4", + "Zimbra-1.4+", + "Zlib", + "Zlib+", + "blessing", + "blessing+", + "bzip2-1.0.6", + "bzip2-1.0.6+", + "checkmk", + "checkmk+", + "copyleft-next-0.3.0", + "copyleft-next-0.3.0+", + "copyleft-next-0.3.1", + "copyleft-next-0.3.1+", + "curl", + "curl+", + "diffmark", + "diffmark+", + "dvipdfm", + "dvipdfm+", + "eGenix", + "eGenix+", + "etalab-2.0", + "etalab-2.0+", + "gSOAP-1.3b", + "gSOAP-1.3b+", + "gnuplot", + "gnuplot+", + "iMatix", + "iMatix+", + "libpng-2.0", + "libpng-2.0+", + "libselinux-1.0", + "libselinux-1.0+", + "libtiff", + "libtiff+", + "mpi-permissive", + "mpi-permissive+", + "mpich2", + "mpich2+", + "mplus", + "mplus+", + "psfrag", + "psfrag+", + "psutils", + "psutils+", + "xinetd", + "xinetd+", + "xpp", + "xpp+", + "zlib-acknowledgement", + "zlib-acknowledgement+", + # SUSE EXCEPTIONS + "AGPL-3.0", + "AGPL-3.0+", + "GFDL-1.1", + "GFDL-1.1+", + "GFDL-1.2", + "GFDL-1.2+", + "GFDL-1.3", + "GFDL-1.3+", + "GPL-3.0-with-GCC-exception", + "GPL-2.0-with-classpath-exception", + "GPL-2.0-with-font-exception", + "SUSE-LGPL-2.1+-with-GCC-exception", + "SUSE-NonFree", + "GPL-1.0+", + "GPL-1.0", + "GPL-2.0+", + "GPL-2.0", + "GPL-3.0+", + "GPL-3.0", + "LGPL-2.0", + "LGPL-2.0+", + "LGPL-2.1+", + "LGPL-2.1", + "LGPL-3.0+", + "LGPL-3.0", +] + +ValidLicenseExceptions = [ + "389-exception", + "Autoconf-exception-2.0", + "Autoconf-exception-3.0", + "Bison-exception-2.2", + "Bootloader-exception", + "CLISP-exception-2.0", + "Classpath-exception-2.0", + "DigiRule-FOSS-exception", + "FLTK-exception", + "Fawkes-Runtime-exception", + "Font-exception-2.0", + "GCC-exception-2.0", + "GCC-exception-3.1", + "GPL-3.0-linking-exception", + "GPL-3.0-linking-source-exception", + "GPL-CC-1.0", + "GStreamer-exception-2005", + "GStreamer-exception-2008", + "KiCad-libraries-exception", + "LGPL-3.0-linking-exception", + "LLVM-exception", + "LZMA-exception", + "Libtool-exception", + "Linux-syscall-note", + "Nokia-Qt-exception-1.1", + "OCCT-exception-1.0", + "OCaml-LGPL-linking-exception", + "OpenJDK-assembly-exception-1.0", + "PS-or-PDF-font-exception-20170817", + "Qt-GPL-exception-1.0", + "Qt-LGPL-exception-1.1", + "Qwt-exception-1.0", + "SHL-2.0", + "SHL-2.1", + "Swift-exception", + "Universal-FOSS-exception-1.0", + "WxWindows-exception-3.1", + "eCos-exception-2.0", + "freertos-exception-2.0", + "gnu-javamail-exception", + "i2p-gpl-java-exception", + "mif-exception", + "openvpn-openssl-exception", + "u-boot-exception-2.0", + "x11vnc-openssl-exception", +] diff --git a/configs/openSUSE/opensuse.toml b/configs/openSUSE/opensuse.toml new file mode 100644 index 0000000..af6a8bf --- /dev/null +++ b/configs/openSUSE/opensuse.toml @@ -0,0 +1,243 @@ +# Configuration for the rpmlint utility. + +# Configuration options used by the checks +CompressExtension = "gz" +UseVarLockSubsys = false +UseVersionInChangelog = false +BadnessThreshold = 999 + +# Enabled checks for the rpmlint to be run (besides the default set) +Checks = [ + "BashismsCheck", + "PAMModulesCheck", + "TmpFilesCheck", + "SysVInitOnSystemdCheck", + "SharedLibraryPolicyCheck", +] + +# List of directory prefixes that are not allowed in packages +DisallowedDirs = [ + "/etc/NetworkManager/dispatcher.d", +] + +FilterErrorTitles = [ + 'cross-directory-hard-link', +] + +Filters = [ +# Stuff autobuild takes care about + '.*invalid-version.*', + '.*invalid-packager.*', + '.*not-standard-release-extension.*', + '.*invalid-buildhost.*', + '.*executable-in-library-package.*', + '.*non-versioned-file-in-library-package.*', + '.*shlib-policy-name-error.*', + '.*hardcoded-path-in-buildroot-tag.*', + '.*no-buildroot-tag.*', + +# Do not validate package rpm groups + '.*devel-package-with-non-devel-group.*', + '.*no-group-tag.*', + '.*non-standard-group.*', + +# Output filters + '.*spurious-bracket-in-.*', + '.*one-line-command-in-.*', + ' dir-or-file-in-opt ', # handled by CheckFilelist.py + ' dir-or-file-in-usr-local ', # handled by CheckFilelist.py + ' non-standard-dir-in-usr ', # handled by CheckFilelist.py + 'incoherent-version-in-changelog', + ' no-signature', + ' symlink-crontab-file', #bnc591431 + ' without-chkconfig', + 'unstripped-binary-or-object.*\.ko', + ' no-chkconfig', + ' subsys-not-used', + ' dangerous-command.*', + ' setuid-binary.*', + 'subdir-in-bin /sbin/conf.d/', + '.* nss_db non-standard-dir-in-var db', + 'non-standard-dir-in-usr openwin', + 'ibcs2 non-standard-dir-in-usr i486-sysv4', + 'shlibs5 non-standard-dir-in-usr i486-linux-libc5', + 'explicit-lib-dependency libtool', + +# Filesystem package needs special exceptions + '^filesystem\..*: dir-or-file-in-var-run', + '^filesystem\..*: dir-or-file-in-var-lock', + '^filesystem\..*: dir-or-file-in-var-tmp', + '^filesystem\..*: dir-or-file-in-var-run', + '^filesystem\..*: dir-or-file-in-var-lock', + '^filesystem\..*: dir-or-file-in-usr-tmp', + '^filesystem\..*: dir-or-file-in-tmp', + '^filesystem\..*: dir-or-file-in-mnt', + '^filesystem\..*: dir-or-file-in-home', + '^filesystem\..*: hidden-file-or-dir /root/.gnupg', + '^filesystem\..*: hidden-file-or-dir /root/.gnupg', + '^filesystem\..*: hidden-file-or-dir /etc/skel/.config', + '^filesystem\..*: hidden-file-or-dir /etc/skel/.local', + '^filesystem\..*: hidden-file-or-dir /tmp/.X11-unix', + '^filesystem\..*: hidden-file-or-dir /tmp/.ICE-unix', + '^filesystem\..*: hidden-file-or-dir /etc/skel/.fonts', + '^filesystem\..*: filelist-forbidden-fhs23', + '^filesystem\..*: filelist-forbidden-opt', + '^filesystem\..*: non-standard-uid /var/lib/nobody nobody', + '^filesystem\..*: missing-dependency-to-cron', +# has arch specific dirs in /usr + '^filesystem\..*: no-binary', + +# Suppress any errors about internal packages + '^qa\S+: [EWI]:', + '^\S*(?:INTERNAL|internal)\.\S+: [EWI]:', + +# Exceptions for devel-files + 'devel-file-in-non-devel-package.*/boot/vmlinuz-.*autoconf.h', + 'devel-file-in-non-devel-package.*/usr/src/linux-', + 'devel-file-in-non-devel-package.*/usr/share/systemtap', + '-(?:examples|doc)\.\S+: \w: devel-file-in-non-devel-package', + 'java\S+-demo\.\S+: \w: devel-file-in-non-devel-package', + 'avr-libc\.\S+: \w: devel-file-in-non-devel-package', + 'cross-.*devel-file-in-non-devel-package', + 'cmake.*devel-file-in-non-devel-package', + 'gcc\d\d.*devel-file-in-non-devel-package', + 'OpenOffice_org-sdk\.\S+: \w: devel-file-in-non-devel-package', + 'wnn-sdk\.\S+: \w: devel-file-in-non-devel-package', + 'ocaml\.\S+: \w: devel-file-in-non-devel-package', + 'xorg-x11-server-sdk\.\S+: \w: devel-file-in-non-devel-package', + 'linux-kernel-headers\.\S+: \w: devel-file-in-non-devel-package', + ' devel-file-in-non-devel-package.*-config', + 'libtool\.\S+: \w: devel-file-in-non-devel-package', + 'sdb.* dangling-relative-symlink /usr/share/doc/sdb/.*/gifs ../gifs', + 'kernel-modules-not-in-kernel-packages', + +# SUSE kmp's don't need manual depmod (bnc#456048) + 'module-without-depmod-postin', + 'postin-with-wrong-depmod', + 'module-without-depmod-postun', + 'postun-with-wrong-depmod', + 'configure-without-libdir-spec', + 'conffile-without-noreplace-flag /etc/init.d', + 'use-of-RPM_SOURCE_DIR', + 'use-tmp-in-', + 'symlink-contains-up-and-down-segments /var/lib/named', + 'no-ldconfig-symlink', + 'aaa_base\.\S+: \w: use-of-home-in-%post', + 'description-line-too-long', + 'hardcoded-library-path', + +# Doesn't seem to make sense + 'invalid-ldconfig-symlink', + 'invalid-soname', + 'only-non-binary-in-usr-lib', + 'outside-libdir-files', + +# We want these files + ' perl-temp-file ', + ' hidden-file-or-dir .*/\.packlist', + ' hidden-file-or-dir .*/\.directory', + 'perl-.*no-binary', + ' no-major-in-name ', + +# We check for that already + 'dangling-relative-symlink', + ' lib-package-without-%mklibname', + ' requires-on-release', + ' non-executable-script /etc/profile.d/', + ' non-executable-script /var/adm/fillup-templates/', + ' init-script-name-with-dot ', + '.* statically-linked-binary /sbin/ldconfig', + '.* statically-linked-binary /sbin/init', + 'valgrind.* statically-linked-binary', + 'ldconfig-post.*/ddiwrapper/wine/', + 'glibc\.\S+: \w: statically-linked-binary /usr/sbin/glibc_post_upgrade', + ' symlink-should-be-relative ', + 'libzypp.*shlib-policy-name-error.*libzypp', + 'libtool.*shlib-policy.*', + +# Stuff that is currently too noisy, but might become relevant in the future + ' prereq-use', + ' file-not-utf8', + ' tag-not-utf8', + ' setup-not-quiet', + ' mixed-use-of-spaces-and-tabs ', + ' prereq-use ', + +# An issue with OBS, works with autobuild + ' no-packager-tag', + ' unversioned-explicit-provides ', + ' unversioned-explicit-obsoletes ', + ' service-default-enabled ', + ' non-standard-dir-perm ', + ' conffile-without-noreplace-flag ', + ' non-standard-executable-perm ', + ' jar-not-indexed ', + ' uncompressed-zip ', + ' %ifarch-applied-patch ', + ' read-error ', + ' init-script-without-chkconfig-postin ', + ' init-script-without-chkconfig-preun ', + ' postin-without-chkconfig ', + ' preun-without-chkconfig ', + ' no-dependency-on locales', + ' shlib-policy-name-error', + ' executable-marked-as-config-file', + ' log-files-without-logrotate', + ' hardcoded-prefix-tag', + ' no-documentation', + ' multiple-specfiles', + ' no-default-runlevel ', + ' setgid-binary ', + ' non-readable ', + ' postin-without-ghost-file-creation ', + +# Exceptions for filelist checks + 'nfs-client\.\S+: \w: filelist-forbidden-backup-file /var/lib/nfs/sm.bak', + 'perl\.\S+: \w: filelist-forbidden-perl-dir ', + 'info\.\S+: \w: info-dir-file .*/usr/share/info/dir', + +# These packages are used for CD creation and are not supposed to be +# installed. It's still a dirty hack to make an exception. The +# packages should either be built in a separate project with +# different config or file be put somewhere below /opt/suse/* + '(?:dosutils|skelcd|installation-images|yast2-slide-show|instlux|skelcd-.*|patterns-.*)\.\S+: \w: filelist-forbidden-fhs23 /CD1', + +# Too noisy, and usually not something downstream packagers can fix + ' incorrect-fsf-address ', + ' no-manual-page-for-binary ', + ' static-library-without-debuginfo /usr/lib(?:64)?/ghc-[\d\.]+/', + +# Many places have shorter paths + ' non-coherent-filename ', + +# Mandriva specific stuff that we don't want + ' invalid-build-requires ', + ' no-provides ', + +# Bash completion files are not scripts, do not require them marked as %config + 'W: non-conffile-in-etc /etc/bash_completion.d/', + +# Info uses file triggers now (boo#1152169) + ' info-files-without-install-info-postin' , + ' postin-without-install-info ', + ' info-files-without-install-info-postun ', +] + +[DanglingSymlinkExceptions."/usr/share/doc/licenses/"] +path = "/usr/share/doc/licenses/" +name = "licenses" +[DanglingSymlinkExceptions."consolehelper$"] +path = "consolehelper$" +name = "usermode-consoleonly" + +[Descriptions] +non-standard-uid = '''A file in this package is owned by an unregistered user id. +To register the user, please make a pull request to the rpmlint config file +configs/openSUSE/users-groups.toml in the rpmlint repository. +''' +non-standard-gid = '''A file in this package is owned by an unregistered group id. +To register the group, please make a pull request to the rpmlint config file +configs/openSUSE/users-groups.toml in the rpmlint repository. +''' +no-changelogname-tag = '''There is no changelog. Please insert a '%changelog' section heading in your +spec file and prepare your changes file using e.g. the 'osc vc' command.''' diff --git a/configs/openSUSE/pam-modules.toml b/configs/openSUSE/pam-modules.toml new file mode 100644 index 0000000..c491755 --- /dev/null +++ b/configs/openSUSE/pam-modules.toml @@ -0,0 +1,143 @@ +PAMAuthorizedModules = [ + # pam_krb5 + "pam_krb5.so", + "pam_krb5afs.so", + # ecryptfs-utils + "pam_ecryptfs.so", + # gnome-keyring-pam + "pam_gnome_keyring.so", + # samba-winbind + "pam_winbind.so", + # pam_ssh + "pam_ssh.so", + # pam_mount + "pam_mount.so", + # pam_ccreds + "pam_ccreds.so", + # pam_radius + "pam_radius_auth.so", + # pam_pkcs11 + "pam_pkcs11.so", + # nss-pam-ldapd + "pam_ldap.so", + # pam_passwdqc + "pam_passwdqc.so", + # pam_userpass + "pam_userpass.so", + # pam_apparmor + "pam_apparmor.so", + # pam_ldap + "pam_ldap.so", + # opie + "pam_opie.so", + # pam + "pam_access.so", + "pam_cracklib.so", + "pam_debug.so", + "pam_deny.so", + "pam_echo.so", + "pam_env.so", + "pam_exec.so", + "pam_faildelay.so", + "pam_filter.so", + "pam_ftp.so", + "pam_group.so", + "pam_issue.so", + "pam_keyinit.so", + "pam_lastlog.so", + "pam_limits.so", + "pam_listfile.so", + "pam_localuser.so", + "pam_loginuid.so", + "pam_mail.so", + "pam_mkhomedir.so", + "pam_motd.so", + "pam_namespace.so", + "pam_nologin.so", + "pam_permit.so", + "pam_pwhistory.so", + "pam_rhosts.so", + "pam_rootok.so", + "pam_securetty.so", + "pam_selinux.so", + "pam_sepermit.so", + "pam_shells.so", + "pam_stress.so", + "pam_succeed_if.so", + "pam_tally2.so", + "pam_time.so", + "pam_timestamp.so", + "pam_tty_audit.so", + "pam_umask.so", + "pam_unix.so", + "pam_unix_acct.so", + "pam_unix_auth.so", + "pam_unix_passwd.so", + "pam_unix_session.so", + "pam_userdb.so", + "pam_warn.so", + "pam_wheel.so", + "pam_xauth.so", + # systemd + "pam_systemd.so", + # sssd + "pam_sss.so", + # pam_mktemp + "pam_mktemp.so", + # pam_csync + "pam_csync.so", + # pam_chroot + "pam_chroot.so", + # pam_snapper (bnc#815383) + "pam_snapper.so", + # pam_gdm (bsc#1004346) + "pam_gdm.so", + # pam_slurm (bsc#1007053) + "pam_slurm.so", + # pam_slurm_adopt (bsc#1116758) + "pam_slurm_adopt.so", + # pam_script (bsc#1039848) + "pam_script.so", + # pam_yubico (bsc#1087060) + "pam_yubico.so", + # pam_oath (bsc#1089114) + "pam_oath.so", + # pam_p11 (bsc#1123916) + "pam_p11.so", + # pam_cifscreds (bsc#1150527) + "pam_cifscreds.so", + # libpwquality (bsc#1150520) + "pam_pwquality.so", + # lxc (bsc#1150519) + "pam_cgfs.so", + # google-authenticator-libpam (bsc#1150524) - potential future removal candidate + "pam_google_authenticator.so", + # pam_u2f (bsc#1087061) + "pam_u2f.so", + # pam_kwallet (bsc#993806) + "pam_kwallet5.so", + # pam_dbus (bsc#1039709) + "pam_dbus.so", + # google-compute-engine (bsc#1146353) + "pam_oslogin_admin.so", + "pam_oslogin_login.so", + # fprintd (bsc#792095) + "pam_fprintd.so", + # mariadb (bsc#1163362) + "pam_user_map.so", + # oddjob (bsc#1169494) + "pam_oddjob_mkhomedir.so", + # cockpit (bsc#1169614) + "pam_cockpit_cert.so", + "pam_ssh_add.so", + # pam (bsc#1171564) + "pam_usertype.so", + # pam (bsc#1171563) + "pam_setquota.so", + # kanidm (bsc#1173387) + "pam_kanidm.so", + # GNOME malcontent parental control (bsc#1177974) + "pam_malcontent.so", + # pam (bsc#1171562) + "pam_faillock.so" +] diff --git a/configs/openSUSE/pie-executables.toml b/configs/openSUSE/pie-executables.toml new file mode 100644 index 0000000..0b9725f --- /dev/null +++ b/configs/openSUSE/pie-executables.toml @@ -0,0 +1,227 @@ +PieExecutables = [ + "/bin/ping", + "/usr/bin/ping", + "/bin/ping6", + "/usr/bin/ping6", + "/bin/su", + "/usr/bin/su", + "/usr/bin/pidgin", + "/sbin/arping", + "/sbin/clockdiff", + "/sbin/dhclient", + "/sbin/dhcpcd", + "/sbin/klogd", + "/sbin/rpcbind", + "/sbin/syslogd", + "/sbin/tracepath", + "/sbin/tracepath6", + "/usr/bin/uniconv", + "/usr/bin/achfile", + "/usr/bin/adv1tov2", + "/usr/bin/aecho", + "/usr/bin/afile", + "/usr/bin/afppasswd", + "/usr/bin/at", + "/usr/bin/cadaver", + "/usr/bin/chage", + "/usr/bin/chfn", + "/usr/bin/chsh", + "/usr/bin/ciptool", + "/usr/bin/cnid_index", + "/usr/bin/dig", + "/usr/bin/dund", + "/usr/bin/expiry", + "/usr/bin/finger", + "/usr/bin/getzones", + "/usr/bin/gpasswd", + "/usr/bin/gpg", + "/usr/bin/gpgsplit", + "/usr/bin/gpgv", + "/usr/bin/hcitool", + "/usr/bin/hidd", + "/usr/bin/host", + "/usr/bin/htpasswd", + "/usr/bin/l2ping", + "/usr/bin/lppasswd", + "/usr/bin/megatron", + "/usr/bin/nbplkup", + "/usr/bin/nbprgstr", + "/usr/bin/nbpunrgstr", + "/usr/bin/ncplogin", + "/usr/bin/ncpmap", + "/usr/bin/net", + "/usr/bin/newgrp", + "/usr/bin/nmblookup", + "/usr/bin/nslookup", + "/usr/bin/nsupdate", + "/usr/bin/nwsfind", + "/usr/bin/omshell", + "/usr/bin/pand", + "/usr/bin/pap", + "/usr/bin/papstatus", + "/usr/bin/passwd", + "/usr/bin/pdbedit", + "/usr/bin/profiles", + "/usr/bin/psorder", + "/usr/bin/rcp", + "/usr/bin/rexec", + "/usr/bin/rfcomm", + "/usr/bin/rlogin", + "/usr/bin/rpcclient", + "/usr/bin/rsh", + "/usr/bin/scp", + "/usr/bin/sdptool", + "/usr/bin/sftp", + "/usr/bin/showppd", + "/usr/bin/smbcacls", + "/usr/bin/smbclient", + "/usr/bin/smbcontrol", + "/usr/bin/smbcquotas", + "/sbin/mount.cifs", + "/usr/bin/smbpasswd", + "/usr/bin/smbspool", + "/usr/bin/smbstatus", + "/usr/bin/smbtree", + "/usr/bin/ssh", + "/usr/bin/ssh-add", + "/usr/bin/ssh-agent", + "/usr/bin/ssh-keygen", + "/usr/bin/ssh-keyscan", + "/usr/bin/svn", + "/usr/bin/svnadmin", + "/usr/bin/svndumpfilter", + "/usr/bin/svnlook", + "/usr/bin/svnserve", + "/usr/bin/svnversion", + "/usr/bin/talk", + "/usr/bin/telnet", + "/usr/bin/testparm", + "/usr/bin/testprns", + "/usr/bin/timeout", + "/usr/bin/wbinfo", + "/usr/lib/mit/bin/ftp", + "/usr/lib/mit/bin/gss-client", + "/usr/lib/mit/bin/kdestroy", + "/usr/lib/mit/bin/kinit", + "/usr/lib/mit/bin/klist", + "/usr/lib/mit/bin/kpasswd", + "/usr/lib/mit/bin/krb524init", + "/usr/lib/mit/bin/ksu", + "/usr/lib/mit/bin/kvno", + "/usr/lib/mit/bin/rcp", + "/usr/lib/mit/bin/rlogin", + "/usr/lib/mit/bin/rsh", + "/usr/lib/mit/bin/sclient", + "/usr/lib/mit/bin/sim_client", + "/usr/lib/mit/bin/telnet", + "/usr/lib/mit/bin/uuclient", + "/usr/lib/mit/bin/v4rcp", + "/usr/lib/mit/sbin/ftpd", + "/usr/lib/mit/sbin/gss-server", + "/usr/lib/mit/sbin/kadmin", + "/usr/lib/mit/sbin/kadmin.local", + "/usr/lib/mit/sbin/kadmind", + "/usr/lib/mit/sbin/kdb5_util", + "/usr/lib/mit/sbin/klogind", + "/usr/lib/mit/sbin/kprop", + "/usr/lib/mit/sbin/kpropd", + "/usr/lib/mit/sbin/krb524d", + "/usr/lib/mit/sbin/krb5kdc", + "/usr/lib/mit/sbin/kshd", + "/usr/lib/mit/sbin/ktutil", + "/usr/lib/mit/sbin/login.krb5", + "/usr/lib/mit/sbin/sim_server", + "/usr/lib/mit/sbin/sserver", + "/usr/lib/mit/sbin/telnetd", + "/usr/lib/mit/sbin/uuserver", + "/usr/lib/news/bin/innd", + "/usr/lib/news/bin/innbind", + "/usr/lib/news/bin/rnews", + "/usr/sbin/afpd", + "/usr/sbin/amcheck", + "/usr/sbin/amdd", + "/usr/sbin/atalkd", + "/usr/sbin/atd", + "/usr/sbin/automount", + "/usr/sbin/chat", + "/usr/sbin/cnid_dbd", + "/usr/sbin/cnid_metad", + "/usr/sbin/cron", + "/usr/sbin/cupsd", + "/usr/sbin/dhcpd", + "/usr/sbin/dhcrelay", + "/usr/sbin/dnssec-keygen", + "/usr/sbin/dnssec-signzone", + "/usr/sbin/exim", + "/usr/sbin/hciattach", + "/usr/sbin/bluetoothd", + "/usr/sbin/hciconfig", + "/usr/sbin/hid2hci", + "/usr/sbin/httpd2", + "/usr/sbin/httpd2-prefork", + "/usr/sbin/httpd2-worker", + "/usr/sbin/in.fingerd", + "/usr/sbin/in.ntalkd", + "/usr/sbin/in.rexecd", + "/usr/sbin/in.rlogind", + "/usr/sbin/in.rshd", + "/usr/sbin/in.telnetd", + "/usr/sbin/irqbalance", + "/usr/sbin/lwresd", + "/usr/sbin/mailstats", + "/usr/sbin/makemap", + "/usr/sbin/named", + "/usr/sbin/named-checkconf", + "/usr/sbin/named-checkzone", + "/usr/sbin/nmbd", + "/usr/sbin/nscd", + "/usr/sbin/ntlm_auth", + "/usr/sbin/ntp-keygen", + "/usr/sbin/ntpd", + "/usr/sbin/ntpdc", + "/usr/sbin/ntpq", + "/usr/sbin/ntptime", + "/usr/sbin/openvpn", + "/usr/sbin/papd", + "/usr/sbin/postfix", + "/usr/sbin/pppd", + "/usr/sbin/praliases", + "/usr/sbin/radiusd", + "/usr/sbin/rarpd", + "/usr/sbin/rndc", + "/usr/sbin/rndc-confgen", + "/usr/sbin/rotatelogs2", + "/usr/sbin/rpc.mountd", + "/usr/sbin/rpc.nfsd", + "/usr/sbin/rpc.rquotad", + "/usr/sbin/rpc.rwalld", + "/usr/sbin/rpc.yppasswdd", + "/usr/sbin/rpc.ypxfrd", + "/usr/sbin/safe_finger", + "/usr/sbin/sendmail", + "/usr/lib/sudo/sesh", + "/usr/lib/openldap/slapd", + "/usr/sbin/smartctl", + "/usr/sbin/smartd", + "/usr/sbin/smbd", + "/usr/sbin/snmpd", + "/usr/sbin/snmptrapd", + "/usr/sbin/squid", + "/usr/sbin/squidclient", + "/usr/sbin/sshd", + "/usr/sbin/stunnel", + "/usr/sbin/suexec2", + "/usr/sbin/tcpd", + "/usr/sbin/tickadj", + "/usr/sbin/traceroute", + "/usr/sbin/traceroute6", + "/usr/sbin/try-from", + "/usr/sbin/utempter", + "/usr/sbin/visudo", + "/usr/sbin/vsftpd", + "/usr/sbin/winbindd", + "/usr/sbin/xinetd", + "/usr/sbin/yppush", + "/usr/sbin/ypserv", + "/usr/bin/zone2ldap", +] diff --git a/configs/openSUSE/scoring.toml b/configs/openSUSE/scoring.toml new file mode 100644 index 0000000..1236276 --- /dev/null +++ b/configs/openSUSE/scoring.toml @@ -0,0 +1,40 @@ +[Scoring] +arch-dependent-file-in-usr-share = 590 +arch-independent-package-contains-binary-or-object = 499 +binary-in-etc = 900 +devel-file-in-non-devel-package = 50 +dir-or-file-in-var-lock = 10000 +dir-or-file-in-var-run = 10000 +env-script-interpreter = 9 +executable-docs = 900 +file-contains-buildroot = 10000 +files-duplicated-waste = 100 +hardlink-across-config-files = 10000 +hardlink-across-partition = 10000 +info-dir-file = 10000 +invalid-license = 100000 +invalid-pkgconfig-file = 10000 +libtool-wrapper-in-package = 10000 +lto-bytecode = 10000 +lto-no-text-in-archive = 10000 +makefile-junk = 109 +no-pkg-config-provides = 300 +non-ghost-in-run = 10000 +non-position-independent-executable = 10000 +percent-in-conflicts = 10000 +percent-in-dependency = 10000 +percent-in-obsoletes = 10000 +percent-in-provides = 10000 +shlib-policy-name-error = 10000 +spurious-executable-perm = 50 +summary-ended-with-dot = 20 +summary-not-capitalized = 20 +summary-too-long = 200 +pam-unauthorized-module = 10000 +wrong-script-interpreter = 490 +obsolete-insserv-requirement = 10000 +deprecated-init-script = 10000 +deprecated-boot-script = 10000 +executable-stack = 10000 +binary-or-shlib-defines-rpath = 10000 +patchable-function-entry-in-archive = 10000 diff --git a/configs/openSUSE/users-groups.toml b/configs/openSUSE/users-groups.toml new file mode 100644 index 0000000..0a40e9f --- /dev/null +++ b/configs/openSUSE/users-groups.toml @@ -0,0 +1,443 @@ +StandardGroups = [ + 'aegis', + 'antivir', + 'arangodb', + 'at', + 'audio', + 'avahi', + 'bacula', + 'beagleindex', + 'bigsister', + 'bin', + 'bird', + 'bitcoin', + 'bitlbee', + 'boinc', + 'casaauth', + 'cdrom', + 'ceilometer', + 'ceph', + 'cephadm', + 'chef', + 'chrony', + 'cinder', + 'citadel', + 'colord', + 'conman', + 'console', + 'coroqnetd', + 'coturn', + 'crowbar', + 'cwbconv', + '_cscreen', + 'daapd', + 'daemon', + 'davfs2', + 'dba', + 'ddclient', + 'debuginfod', + 'dehydrated', + 'dialout', + 'disk', + 'distcc', + 'dnscrypt', + 'dosemu', + 'dovecot', + 'elasticsearch', + 'epmd', + 'ergo', + 'festival', + 'ffums', + 'firebird', + 'firejail', + 'floppy', + 'fonehome', + 'ftp', + 'games', + 'gdm', + 'gerbera', + 'geronimo', + 'glance', + '_gns3', + 'grafana', + 'guixbuild', + 'haclient', + 'haldaemon', + 'heat', + 'hsqldb', + 'icecast', + 'icecream', + 'icinga', + 'icingacmd', + 'icingaweb2', + 'ifdrwww', + 'intermezzo', + 'iouyap', + 'jboss', + 'jenkins', + 'jetty5', + 'jitsi', + 'jonas', + 'keyserver', + 'keystone', + 'kmem', + 'kolab', + 'kolab-n', + 'kolab-r', + 'kvm', + 'ldap', + 'libstoragemgmt', + 'libvirt', + 'lightdm', + 'lighttpd', + 'localham', + 'locate', + 'logstash', + 'lp', + 'lxdm', + 'mail', + 'maildrop', + 'mailman', + 'man', + 'mdom', + 'memcached', + 'messagebus', + 'minetest', + 'mktex', + 'modem', + 'mumble-server', + 'munge', + 'mysql', + 'nagcmd', + 'nagios', + 'named', + 'nats', + 'neutron', + 'news', + 'nginx', + 'nobody', + 'nogroup', + 'nova', + 'novell_nogroup', + 'novlxtier', + '_nsd', + 'ntadmin', + 'ntop', + 'ntp', + 'oinstall', + 'opensearch', + 'openvswitch', + 'orthanc', + 'otobo', + 'otrs', + 'ovirtagent', + 'pcp', + 'pcpqa', + 'pdns', + 'pegasus', + 'pkcs11', + 'polipo', + 'polkituser', + 'postfix', + 'postgres', + 'pound', + 'powersave', + 'privoxy', + 'prometheus', + 'prosody', + 'ptokax', + 'public', + 'pulse', + 'pulse-access', + 'pulse-rt', + 'puppet', + 'qemu', + 'quagga', + 'quasselcore', + 'rabbitmq', + 'radicale', + 'radiusd', + 'root', + 'sabayon-admin', + 'salt', + 'sanlock', + 'sapdb', + 'sddm', + 'sensu', + 'shadow', + 'shibd', + 'signaling', + 'singularity', + 'siproxd', + '_sks', + 'slurm', + 'snort', + 'sogo', + 'squid', + 'sshd', + 'suse-ncc', + 'svn', + 'swift', + 'synapse', + 'sys', + 'systemd-journal', + 'systemd-journal-gateway', + 'tape', + 'tftp', + 'tomcat', + 'tomcat4', + 'tor', + 'tox', + 'transmission', + 'trove', + 'trusted', + 'tryton', + 'tss', + 'ts-shell', + 'tty', + 'unbound', + 'users', + 'utmp', + 'uucp', + 'uuidd', + 'vacation', + 'varnish', + 'video', + 'vnc', + 'vscan', + 'warewulf', + 'wheel', + 'wireshark', + 'www', + 'xok', + 'xrootd', + 'xymon', + 'zabbix', + 'zabbixs', + 'zeroinst', + 'zkeyadm', + 'znc', + 'zope', +] + +StandardUsers = [ + 'aegis', + 'amanda', + 'aodh', + 'arangodb', + 'asterisk', + 'at', + 'avahi', + 'bacula', + 'barbican', + 'beagleindex', + 'bigsister', + 'bin', + 'bird', + 'bitcoin', + 'bitlbee', + 'boinc', + 'casaatsd', + 'casaatvd', + 'casaauth', + 'ceilometer', + 'ceph', + 'cephadm', + 'chef', + 'chrony', + 'cinder', + 'citadel', + 'cntlm', + 'colord', + 'conman', + 'cop', + 'coroqnetd', + 'coturn', + 'crowbar', + 'cyrus', + '_cscreen', + 'daapd', + 'daemon', + 'davfs2', + 'ddclient', + 'dehydrated', + 'designate', + 'dhcpd', + 'debuginfod', + 'distcc', + 'dnscrypt', + 'dovecot', + 'dpbox', + 'dvbdaemon', + 'ec2-api', + 'elasticsearch', + 'epmd', + 'ergo', + 'fax', + 'festival', + 'fetchmail', + 'ffums', + 'firebird', + 'fonehome', + 'ftp', + 'games', + 'gdm', + 'gerbera', + 'geronimo', + 'glance', + 'gnats', + 'gnocchi', + '_gns3', + 'gnump3d', + 'grafana', + 'hacluster', + 'haldaemon', + 'heat', + 'hsqldb', + 'icecast', + 'icecream', + 'icinga', + 'intermezzo', + 'iodined', + 'irc', + 'ironic', + 'jabber', + 'jboss', + 'jenkins', + 'jetty5', + 'jibri', + 'jicofo', + 'jonas', + 'jvb', + 'keyserver', + 'keystone', + 'kolab', + 'kolab-n', + 'kolab-r', + 'ldap', + 'libstoragemgmt', + 'lightdm', + 'lighttpd', + 'logstash', + 'lp', + 'lxdm', + 'magnum', + 'mail', + 'mailman', + 'man', + 'manila', + 'mdnsd', + 'mdom', + 'mednafen', + 'memcached', + 'messagebus', + 'minetest', + 'mktex', + 'mpd', + 'mumble-server', + 'munge', + 'murano', + 'mysql', + 'nagios', + 'named', + 'nats', + 'neutron', + 'news', + 'nginx', + 'nova', + 'novell_nobody', + 'novlifdr', + 'novlxregd', + 'novlxsrvd', + '_nsd', + 'ntop', + 'ntp', + 'octavia', + 'opensearch', + 'openvswitch', + 'oracle', + 'orthanc', + 'otobo', + 'otrs', + 'ovirtagent', + 'partimag', + 'pcp', + 'pcpqa', + 'pdns', + 'pegasus', + 'polipo', + 'polkitd', + 'polkituser', + 'pop', + 'postfix', + 'postgres', + 'postgrey', + 'pound', + 'privoxy', + 'prometheus', + 'prosody', + 'ptokax', + 'pulse', + 'puppet', + 'qemu', + 'quagga', + 'quasselcore', + 'rabbitmq', + 'radicale', + 'radiusd', + 'radvd', + '_rmt', + 'root', + 'sabayon-admin', + 'sahara', + 'salt', + 'sanlock', + 'sapdb', + 'sddm', + 'sensu', + 'shibd', + 'signaling', + 'siproxd', + '_sks', + 'slurm', + 'snort', + 'sogo', + 'squid', + 'sshd', + 'statd', + 'suse-ncc', + 'svn', + 'swift', + 'synapse', + 'systemd-journal-gateway', + 'tftp', + 'tomcat', + 'tomcat4', + 'tor', + 'toxcmd', + 'transmission', + 'trove', + 'tryton', + 'tss', + 'ulogd', + 'unbound', + 'upsd', + 'uucp', + 'uuidd', + 'vacation', + 'varnish', + 'vdr', + 'vnc', + 'vscan', + 'wnn', + 'wwwrun', + 'xrootd', + 'xymon', + 'yastws', + 'zabbix', + 'zabbixs', + 'zaqar', + 'zeroinst', + 'znc', + 'zope', +] @@ -0,0 +1,5 @@ +#!/usr/bin/python3 + +from rpmlint.cli import diff + +diff() @@ -0,0 +1,5 @@ +#!/usr/bin/python3 + +from rpmlint.cli import lint + +lint() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8fbd3b0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,82 @@ +[build-system] +requires = [ + "setuptools>=61.0" +] +build-backend = "setuptools.build_meta" + +[project] +name = "rpmlint" +version = "2.5.0" +description = "Check for common errors in RPM packages" +license = {text = "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)"} +authors = [ + {name = "Frédéric Lepied", email = "flepied@mandriva.com"} +] +maintainers = [ + {name = "RPMLint maintainers", email = "rpm-ecosystem@lists.rpm.org"} +] +readme = "README.md" +classifiers = [ + # complete classifier list: + # http://pypi.python.org/pypi?%3Aaction=list_classifiers + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", + "Operating System :: Unix", + "Operating System :: POSIX", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Utilities", + "Topic :: Software Development :: Quality Assurance", + "Topic :: System :: Archiving :: Packaging", +] +keywords = ["RPM", ".spec", "validator"] +dependencies = [ + "pybeam", + "pyxdg", + "rpm", + "tomli;python_version<'3.11'", + "tomli-w", + "zstandard", + "importlib-metadata;python_version<'3.8'", + "pyenchant", + "python-magic", + "packaging" +] + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-cov", + "pytest-xdist" +] + +[project.scripts] +rpmdiff = "rpmlint.cli:diff" +rpmlint = "rpmlint.cli:lint" + +[project.urls] +homepage = "https://github.com/rpm-software-management/rpmlint" +download-url = "https://github.com/rpm-software-management/rpmlint" + +[tool.setuptools] +license-files = ['COPYING'] +platforms = ["Linux"] +packages = [ + "rpmlint", + "rpmlint.checks", + "rpmlint.descriptions" +] +include-package-data = true + + +[tool.setuptools.package-data] +rpmlint = ["configdefaults.toml"] +"rpmlint.descriptions" = ["*.toml"] + +[tool.ruff] + +ignore = ["E501"] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..819ea30 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,8 @@ +[pytest] +addopts = + -vv + --cov=rpmlint + -n auto + -p no:legacypath +pythonpath= . +testpaths= test diff --git a/rpmdiff.1 b/rpmdiff.1 deleted file mode 100644 index 5f1fff7..0000000 --- a/rpmdiff.1 +++ /dev/null @@ -1,70 +0,0 @@ -.\" -.\" (C) Copyright 2014, Arturo Borrero Gonzalez <arturo@debian.org>, -.\" -.\" %%%LICENSE_START(GPLv2+_DOC_FULL) -.\" This is free documentation; you can redistribute it and/or -.\" modify it under the terms of the GNU General Public License as -.\" published by the Free Software Foundation; either version 2 of -.\" the License, or (at your option) any later version. -.\" -.\" The GNU General Public License's references to "object code" -.\" and "executables" are to be interpreted as the output of any -.\" document formatting or typesetting system, including -.\" intermediate and printed output. -.\" -.\" This manual is distributed in the hope that it will be useful, -.\" but WITHOUT ANY WARRANTY; without even the implied warranty of -.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -.\" GNU General Public License for more details. -.\" -.\" You should have received a copy of the GNU General Public -.\" License along with this manual; if not, see -.\" <http://www.gnu.org/licenses/>. -.\" %%%LICENSE_END -.\" -.TH RPMDIFF 1 "February 6, 2014" - -.SH NAME -rpmdiff \- search for differences between two RPM packages -.SH SYNOPSIS -.B rpmdiff -.RI [<options>] " <old_RPM> <new_RPM>" -.SH DESCRIPTION -The \fBrpmdiff\fP tool shows differences between two RPM packages. - -.SH OPTIONS -This program follows the usual GNU command line syntax, with long -options starting with two dashes (`-'). -.TP -.B \-h, \-\-help -Show summary of options. -.TP -.B \-i, \-\-ignore\fR=\fIproperty\fR -File property to ignore when calculating differences (may be used -multiple times). -.br -Valid values are: -.nf - S (size) - M (mode) - 5 (checksum) - D (device) - N (inode) - L (number of links) - V (vflags) - U (user) - G (group) - F (digest) - T (time) -.fi -.SH SEE ALSO -.BR rpmlint (1), -.BR rpm (1) - -.SH AUTHOR -Originally written by Frédéric Lepied, modified and maintained by -numerous contributors since. -.br -This manual page was written by Arturo Borrero González -<arturo@debian.org>, and is free/libre documentation -(GPL-2+). diff --git a/rpmlint b/rpmlint deleted file mode 100755 index 8b1b528..0000000 --- a/rpmlint +++ /dev/null @@ -1,378 +0,0 @@ -#!/usr/bin/python -ttOu -# -*- coding: utf-8 -*- -############################################################################# -# File : rpmlint -# Package : rpmlint -# Author : Frederic Lepied -# Created on : Mon Sep 27 19:20:18 1999 -# Purpose : main entry point: process options, load the checks and run -# the checks. -############################################################################# - -import getopt -import glob -import locale -import os -import re -import stat -import sys -import tempfile - -try: - import importlib -except ImportError: # Python < 2.7 - importlib = None - import imp - -# 1 instead of 0 here because we want the script dir to be looked up first, -# e.g. for in-place from tarball or SCM checkout -sys.path.insert(1, '/usr/share/rpmlint') - -# Do not import anything that initializes its global variables from -# Config at load time here (or anything that imports such a thing), -# that results in those variables initialized before config files are -# loaded which is too early - settings from config files won't take -# place for those variables. - -import AbstractCheck # noqa: E402 -import Config # noqa: E402 -from Filter import badnessScore, badnessThreshold, printAllReasons, \ - printDescriptions, printed_messages, printInfo, setRawOut # noqa: E402 -import Pkg # noqa: E402 - - -_default_user_conf = '%s/rpmlint' % \ - (os.environ.get('XDG_CONFIG_HOME') or '~/.config') - - -# Print usage information -def usage(name): - print('''usage: %s [<options>] <rpm files|installed packages|specfiles|dirs> - options: -\t[-i|--info] -\t[-I|--explain <messageid>] -\t[-c|--check <check>] -\t[-a|--all] -\t[-C|--checkdir <checkdir>] -\t[-h|--help] -\t[-v|--verbose] -\t[-E|--extractdir <dir>] -\t[-V|--version] -\t[-n|--noexception] -\t[ --rawout <file>] -\t[-f|--file <user config file to use instead of %s] -\t[-o|--option <key value>]''' - % (name, _default_user_conf)) - - -# Print version information -def printVersion(): - print('rpmlint version %s' % Config.__version__) - - -def loadCheck(name): - '''Load a (check) module by its name, unless it is already loaded.''' - # Avoid loading more than once (initialization costs) - loaded = sys.modules.get(name) - if loaded: - return loaded - if importlib: - importlib.import_module(name) - else: - (fobj, pathname, description) = imp.find_module(name) - with fobj: - imp.load_module(name, fobj, pathname, description) - - -############################################################################# -# main program -############################################################################# -def main(): - - locale.setlocale(locale.LC_COLLATE, '') - - # Add check dirs to the front of load path - sys.path[0:0] = Config.checkDirs() - - # Load all checks - for c in Config.allChecks(): - loadCheck(c) - - packages_checked = 0 - specfiles_checked = 0 - - try: - # Loop over all file names given in arguments - dirs = [] - for arg in args: - pkgs = [] - isfile = False - try: - if arg == "-": - arg = "(standard input)" - # Short-circuit stdin spec file check - stdin = sys.stdin.readlines() - if not stdin: - continue - with Pkg.FakePkg(arg) as pkg: - runSpecChecks(pkg, None, spec_lines=stdin) - specfiles_checked += 1 - continue - - try: - st = os.stat(arg) - isfile = True - if stat.S_ISREG(st[stat.ST_MODE]): - if arg.endswith(".spec"): - # Short-circuit spec file checks - with Pkg.FakePkg(arg) as pkg: - runSpecChecks(pkg, arg) - specfiles_checked += 1 - elif "/" in arg or arg.endswith(".rpm") or \ - arg.endswith(".spm"): - pkgs.append(Pkg.Pkg(arg, extract_dir)) - else: - raise OSError - - elif stat.S_ISDIR(st[stat.ST_MODE]): - dirs.append(arg) - continue - else: - raise OSError - except OSError: - ipkgs = Pkg.getInstalledPkgs(arg) - if not ipkgs: - Pkg.warn( - '(none): E: no installed packages by name %s' % arg) - else: - ipkgs.sort(key=lambda x: locale.strxfrm( - x.header.sprintf("%{NAME}.%{ARCH}"))) - pkgs.extend(ipkgs) - except KeyboardInterrupt: - if isfile: - arg = os.path.abspath(arg) - Pkg.warn( - '(none): E: interrupted, exiting while reading %s' % arg) - sys.exit(2) - except Exception as e: - if isfile: - arg = os.path.abspath(arg) - Pkg.warn('(none): E: error while reading %s: %s' % (arg, e)) - pkgs = [] - continue - - for pkg in pkgs: - with pkg: - runChecks(pkg) - packages_checked += 1 - - for dname in dirs: - try: - for path, _, files in os.walk(dname): - for fname in files: - fname = os.path.abspath(os.path.join(path, fname)) - try: - if fname.endswith('.rpm') or \ - fname.endswith('.spm'): - with Pkg.Pkg(fname, extract_dir) as pkg: - runChecks(pkg) - packages_checked += 1 - - elif fname.endswith('.spec'): - with Pkg.FakePkg(fname) as pkg: - runSpecChecks(pkg, fname) - specfiles_checked += 1 - - except KeyboardInterrupt: - Pkg.warn( - '(none): E: interrupted while reading %s' % - fname) - sys.exit(2) - except Exception as e: - Pkg.warn( - '(none): E: while reading %s: %s' % (fname, e)) - continue - except Exception as e: - Pkg.warn( - '(none): E: error while reading dir %s: %s' % (dname, e)) - continue - - if printAllReasons(): - Pkg.warn('(none): E: badness %d exceeds threshold %d, aborting.' % - (badnessScore(), badnessThreshold())) - sys.exit(66) - - finally: - print("%d packages and %d specfiles checked; %d errors, %d warnings." - % (packages_checked, specfiles_checked, - printed_messages["E"], printed_messages["W"])) - - if printed_messages["E"] > 0: - sys.exit(64) - sys.exit(0) - - -def runChecks(pkg): - - 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, spec_lines=None): - - 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, spec_lines) - else: - Pkg.warn('(none): W: unknown check %s, skipping' % name) - - -############################################################################# -# -############################################################################# - -argv0 = os.path.basename(sys.argv[0]) - -# parse options -try: - (opt, args) = getopt.getopt( - sys.argv[1:], 'iI:c:C:hVvanE:f:o:', - ['info', 'explain=', 'check=', 'checkdir=', 'help', 'version', - 'verbose', 'all', 'noexception', 'extractdir=', 'file=', 'option=', - 'rawout=']) -except getopt.GetoptError as e: - Pkg.warn("%s: %s" % (argv0, e)) - usage(argv0) - sys.exit(1) - -# process options -checkdir = '/usr/share/rpmlint' -checks = [] -verbose = False -extract_dir = None -conf_file = _default_user_conf -info_error = set() - -# load global config files -configs = glob.glob('/etc/rpmlint/*config') -configs.sort() - -# Was rpmlint invoked as a prefixed variant? -m = re.match(r"(?P<prefix>[\w-]+)-rpmlint(\.py)?", argv0) -if m: - # Okay, we're a prefixed variant. Look for the variant config. - # If we find it, use it. If not, fallback to the default. - prefix = m.group('prefix') - if os.path.isfile('/usr/share/rpmlint/config.%s' % prefix): - configs.insert(0, '/usr/share/rpmlint/config.%s' % prefix) - else: - configs.insert(0, '/usr/share/rpmlint/config') -else: - configs.insert(0, '/usr/share/rpmlint/config') - -for f in configs: - try: - with open(f) as fobj: - exec(compile(fobj.read(), f, 'exec')) - except IOError: - pass - except Exception as e: - Pkg.warn('(none): W: error loading %s, skipping: %s' % (f, e)) -# pychecker fix -del f - -config_overrides = {} - -# process command line options -for o in opt: - if o[0] in ('-c', '--check'): - checks.append(o[1]) - elif o[0] in ('-i', '--info'): - Config.info = True - elif o[0] in ('-I', '--explain'): - # split by comma for deprecated backwards compatibility with < 1.2 - info_error.update(o[1].split(',')) - elif o[0] in ('-h', '--help'): - usage(argv0) - sys.exit(0) - elif o[0] in ('-C', '--checkdir'): - Config.addCheckDir(o[1]) - elif o[0] in ('-v', '--verbose'): - verbose = True - elif o[0] in ('-V', '--version'): - printVersion() - sys.exit(0) - elif o[0] in ('-E', '--extractdir'): - extract_dir = o[1] - Config.setOption('ExtractDir', extract_dir) - elif o[0] in ('-n', '--noexception'): - Config.no_exception = True - elif o[0] in ('-a', '--all'): - if '*' not in args: - args.append('*') - elif o[0] in ('-f', '--file'): - conf_file = o[1] - elif o[0] in ('-o', '--option'): - kv = o[1].split(None, 1) - if len(kv) == 1: - config_overrides[kv[0]] = None - else: - config_overrides[kv[0]] = eval(kv[1]) - elif o[0] in ('--rawout',): - setRawOut(o[1]) - -# load user config file -try: - expconf = os.path.expanduser(conf_file) - with open(expconf) as fobj: - exec(compile(fobj.read(), expconf, 'exec')) -except IOError: - pass -except Exception as e: - Pkg.warn('(none): W: error loading %s, skipping: %s' % (conf_file, e)) - -# apply config overrides -for key, value in config_overrides.items(): - Config.setOption(key, value) - -if not extract_dir: - extract_dir = Config.getOption('ExtractDir', tempfile.gettempdir()) - -if info_error: - Config.info = True - sys.path[0:0] = Config.checkDirs() - for c in checks: - Config.addCheck(c) - for c in Config.allChecks(): - loadCheck(c) - for e in sorted(info_error): - print("%s:" % e) - printDescriptions(e) - sys.exit(0) - -# if no argument print usage -if not args: - usage(argv0) - sys.exit(1) - -if __name__ == '__main__': - if checks: - Config.resetChecks() - for check in checks: - Config.addCheck(check) - main() - -# rpmlint ends here diff --git a/rpmlint.1 b/rpmlint.1 deleted file mode 100644 index adfd40c..0000000 --- a/rpmlint.1 +++ /dev/null @@ -1,115 +0,0 @@ -.TH RPMLINT "1" "April 2011" "rpmlint" "User Commands" -.SH NAME -rpmlint \- check common problems in rpm packages -.SH SYNOPSIS -\fBrpmlint\fR [\fIOPTION\fR]... [\fIFILE\fR|\fIPACKAGE\fR]... -.SH DESCRIPTION -\fBrpmlint\fR is a tool for checking common errors in rpm packages. -It can be used to test individual packages and spec files before -uploading or to check an entire distribution. By default all -applicable checks are processed but specific checks can be performed -by using command line parameters. - -\fIFILE\fR can be a rpm package file, a spec file, or a directory. In -case of a directory, it is recursively searched for rpm and spec files -to check. The special value \fB\-\fR results in standard input being -read and treated as (single) spec file content. -\fIPACKAGE\fR is the name of an installed package or a -.BR glob (7) -pattern to match installed packages, unless a file by that name exists. -.TP -\fB\-i\fR, \fB\-\-info\fR -Display explanations for reported messages. -.TP -\fB-I\fR, \fB\-\-explain\fR=\fImessageid\fR -Display explanations for the specified message identifiers and exit. -This option may be given multiple times. -.TP -\fB\-c\fR, \fB\-\-check\fR=\fIcheck\fR -Run only the specified check. This option may be given multiple times -to specify multiple checks to run. \fIcheck\fR is the name of the Python -module (as it would be given to Python's import statement) containing the -check. -.TP -\fB\-a\fR, \fB\-\-all\fR -Check all installed packages. -.TP -\fB\-C\fR, \fB\-\-checkdir\fR=\fIdir\fR -Insert \fIdir\fR to the front of the list of paths to load checks -from, unless it is already in the list. The default list of check -dirs typically contains only /usr/share/rpmlint. Directories in the -check dirs list are also inserted to the front of the list of paths to -load Python modules from when the check process begins. -.TP -\fB\-h\fR, \fB\-\-help\fR -Display summary of command line options and exit. -.TP -\fB\-v\fR, \fB\-\-verbose\fR -Operate in verbose mode. -.TP -\fB\-E\fR, \fB\-\-extractdir\fR=\fIdir\fR -Base directory for extracted temporary files, default is what Python's -tempfile.gettempdir() returns. -.TP -\fB\-V\fR, \fB\-\-version\fR -Display version information and exit. -.TP -\fB\-n\fR, \fB\-\-noexception\fR -Ignore output filters. -.TP -\fB\-\-rawout\fR=\fIfile\fR -Write unfiltered output to \fIfile\fR. -.TP -\fB\-f\fR, \fB\-\-file\fR=\fIconffile\fR -Load user configuration from the specified file, default is -$XDG_CONFIG_HOME/rpmlint (~/.config/rpmlint if $XDG_CONFIG_HOME is -empty or not set). -.TP -\fB\-o\fR, \fB\-\-option\fR=\fIvalue\fR -Override a configuration option. \fIvalue\fR is a whitespace separated string, -first word of which is the option name to set, and the Python eval() return -value for the rest is set as the value for the option. Passing only an option -name is treated as if None was passed as its value. See the file "config" -shipped with rpmlint for the list of configuration options and their types. -For example: - \-o "NetworkEnabled True" - \-o "Distribution \(aqMy favorite distro\(aq" - \-o "MaxLineLength 80" - \-o "ValidShells (\(aq/bin/sh\(aq, \(aq/bin/bash\(aq)" -.SH CAVEATS -All checks do not apply to all argument types. For best check -coverage, run rpmlint on all source and binary packages your build -produces. The set of checks rpmlint runs on source packages is a -superset of the one for plain specfiles, the set of checks run for -installed binary packages is a superset of the one for uninstalled -binary package files, and the source and binary package check sets are -quite different. -.SH FILES -.TP -\fB/usr/share/rpmlint/config\fR, \fB/usr/share/rpmlint/config.*\fR -Built-in configuration. When invoked as \fIsomeprefix\fR-rpmlint, -/usr/share/rpmlint/config.\fIsomeprefix\fR is used if it exists, -otherwise /usr/share/rpmlint/config. -.TP -\fB/etc/rpmlint/*config\fR -System wide configuration. -.TP -\fB$XDG_CONFIG_HOME/rpmlint\fR or \fB~/.config/rpmlint\fR -User configuration. -.SH EXIT CODES -.IP 0 -No errors. -.IP 1 -Unspecified error. -.IP 2 -Interrupted. -.IP 64 -One or more error message printed. -.IP 66 -Badness threshold exceeded. -.SH AUTHOR -Originally written by Frédéric Lepied, modified and maintained by -numerous contributors since. -.SH COPYRIGHT -This program is licensed under the GNU General Public License, see the -file COPYING included in the distribution archive. diff --git a/rpmlint.bash-completion b/rpmlint.bash-completion deleted file mode 100644 index db1bad6..0000000 --- a/rpmlint.bash-completion +++ /dev/null @@ -1,91 +0,0 @@ -# bash-completion add-on for rpmlint -# http://bash-completion.alioth.debian.org/ - -_rpmlint_installed_packages() -{ - if declare -F _rpm_installed_packages &>/dev/null ; then - _rpm_installed_packages - elif declare -F _xfunc &>/dev/null ; then - # bash-completion 1.90+ dynamic loading - _xfunc rpm _rpm_installed_packages - fi -} - -_rpmlint() -{ - COMPREPLY=() - local cur=$2 # for _rpm_installed_packages, _filedir - - case $3 in - -C|--checkdir|-E|--extractdir) - _filedir -d - return 0 - ;; - -f|--file|--rawout) - _filedir - return 0 - ;; - -c|--check) - # should take --checkdir, python path, inheritance into account... - COMPREPLY=( $( compgen -W \ - "$( command ls /usr/share/rpmlint/*Check.py* 2>/dev/null | - sed -e '/^AbstractCheck/d' \ - -e 's|.*/\([^/.]*\)\.py.*|\1|' )" -- "$cur" ) ) - return 0 - ;; - -I|--explain) - # should take --checkdir into account... - COMPREPLY=( $( compgen -W "$( sed -e '1,/^addDetails/d' \ - -ne "s/^'\([^'[:space:]]\{1,\}\)',\$/\1/p" \ - /usr/share/rpmlint/*Check.py 2>/dev/null )" -- "$cur" ) ) #' - return 0 - ;; - -o|--option) - # argument required but no completions available - return 0 - ;; - esac - - if [[ "$cur" == -* ]]; then - COMPREPLY=( $( compgen -W '--info --explain --check --all --checkdir - --help --verbose --extractdir --version --noexception - --file --option' -- "$cur" ) ) - else - # Installed packages completion is potentially slow, do it only if $cur - # does not look like a path. - [[ $cur != */* && $cur != [.~]* ]] && _rpmlint_installed_packages - _filedir @([rs]pm|spec) - fi -} -complete -F _rpmlint -o filenames rpmlint - -_rpmdiff() -{ - COMPREPLY=() - local cur=$2 # for _rpm_installed_packages, _filedir - - case $3 in - -i|--ignore) - COMPREPLY=( $( compgen -W 'S M 5 D N L V U G F T' -- "$cur" ) ) - return 0 - ;; - -h|--help) - return 0 - ;; - esac - - if [[ "$cur" == -* ]]; then - COMPREPLY=( $( compgen -W '--help --ignore' -- "$cur" ) ) - else - # Installed packages completion is potentially slow, do it only if $cur - # does not look like a path. - [[ $cur != */* && $cur != [.~]* ]] && _rpmlint_installed_packages - _filedir [rs]pm - fi -} -complete -F _rpmdiff -o filenames rpmdiff - -# Local variables: -# mode: shell-script -# End: -# ex: filetype=sh diff --git a/rpmlint.svg b/rpmlint.svg new file mode 100644 index 0000000..e0890f8 --- /dev/null +++ b/rpmlint.svg @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="678.422" + height="478.58984" + viewBox="0 0 179.49896 126.62697" + version="1.1" + id="svg5" + xml:space="preserve" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + sodipodi:docname="rpmlint.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview + id="namedview7" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="mm" + showgrid="false" + inkscape:zoom="0.65427211" + inkscape:cx="571.62761" + inkscape:cy="257.53811" + inkscape:window-width="2560" + inkscape:window-height="1371" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /><defs + id="defs2" /><g + inkscape:label="Capa 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-28.57345,-63.985242)"><path + id="path579" + style="fill:#62a0ea;fill-opacity:1;stroke-width:2.08748;stroke-linejoin:bevel" + d="m 118.49097,207.50144 a 82.11496,82.11496 0 0 0 -82.112817,82.07044 l 60.270739,-0.37362 a 21.875221,21.875221 0 0 1 3.552238,-11.51971 L 75.689965,243.07084 c -0.125014,-0.1511 -0.363294,-0.45394 -0.641304,-0.9803 -0.183371,-0.34718 -0.415493,-0.77079 -0.507463,-1.56838 -0.09197,-0.79759 0.24374,-2.34892 1.304313,-3.17965 0.511494,-0.40023 1.216011,-0.67691 1.875338,-0.72657 2.146436,-0.16056 2.631075,0.78188 3.077848,1.18752 0.29726,0.2699 0.485345,0.48346 0.606165,0.62787 l 29.084528,30.84722 a 21.875221,21.875221 0 0 1 8.00158,-1.53737 21.875221,21.875221 0 0 1 13.44827,4.6824 l 16.58969,-9.88157 c -13.29293,-14.90435 -35.28289,-12.41339 -39.77794,-10.55026 3.12522,-3.45527 23.53983,-10.20097 45.89901,6.90449 l 13.38006,-7.97006 c -23.20664,-26.0737 -54.47812,-30.81804 -81.116491,-25.17314 24.430811,-9.56851 59.240711,-10.55215 87.237571,21.52737 l 10.47016,-6.23683 a 82.11496,82.11496 0 0 0 -66.13033,-33.54214 z m -14.01827,24.98193 c 0,0 0.187,3.44848 1.31052,4.56303 1.12351,1.11456 4.57336,1.27434 4.57336,1.27434 0,0 -3.44848,0.18701 -4.56303,1.31052 -1.11455,1.12353 -1.27434,4.57337 -1.27434,4.57337 0,0 -0.18699,-3.44848 -1.31051,-4.56304 -1.12352,-1.11455 -4.57337,-1.27434 -4.57337,-1.27434 0,0 3.44848,-0.187 4.56303,-1.31051 1.11456,-1.12352 1.27434,-4.57337 1.27434,-4.57337 z m -44.469633,27.15856 c 0,0 0.145017,2.67066 1.014925,3.53363 0.869909,0.86297 3.541386,0.98599 3.541386,0.98599 0,0 -2.670669,0.14502 -3.533634,1.01492 -0.862972,0.86992 -0.985986,3.54139 -0.985986,3.54139 0,0 -0.145018,-2.67066 -1.014926,-3.53363 -0.869908,-0.86297 -3.541386,-0.98599 -3.541386,-0.98599 0,0 2.670669,-0.14502 3.533635,-1.01493 0.862971,-0.86991 0.985986,-3.54138 0.985986,-3.54138 z m 23.623365,14.29628 c 0,0 0.09433,1.736 0.659908,2.29702 0.56558,0.56114 2.302185,0.64131 2.302185,0.64131 0,0 -1.735952,0.0942 -2.297018,0.6599 -0.561065,0.56553 -0.641304,2.30219 -0.641304,2.30219 0,0 -0.09436,-1.736 -0.659908,-2.29702 -0.565574,-0.56114 -2.302185,-0.6413 -2.302185,-0.6413 0,0 1.735958,-0.0946 2.297017,-0.65991 0.561076,-0.56553 0.641305,-2.30219 0.641305,-2.30219 z" /><path + id="path1237" + style="fill:#62a0ea;fill-opacity:1;stroke-width:2.11465;stroke-linejoin:bevel" + d="m 77.953393,239.71239 c -0.06905,0.005 -0.133821,0.0282 -0.192753,0.0744 -0.471456,0.36929 0.421679,1.42989 0.421679,1.42989 l 31.258551,44.1353 a 9.7585783,9.7585783 0 0 0 -0.86661,4.00492 9.7585783,9.7585783 0 0 0 9.75858,9.75857 9.7585783,9.7585783 0 0 0 9.75857,-9.75857 9.7585783,9.7585783 0 0 0 -9.75857,-9.75858 9.7585783,9.7585783 0 0 0 -2.15439,0.24133 L 79.106811,240.52113 c 0,0 -0.670099,-0.84489 -1.153418,-0.80874 z m 40.379447,46.50053 a 3.1439967,3.1439967 0 0 1 3.14399,3.14399 3.1439967,3.1439967 0 0 1 -3.14399,3.14399 3.1439967,3.1439967 0 0 1 -3.144,-3.14399 3.1439967,3.1439967 0 0 1 3.144,-3.14399 z" /><rect + style="fill:#62a0ea;fill-opacity:1;stroke-width:1.99999;stroke-linejoin:bevel" + id="rect1332" + width="74.242485" + height="4.2533646" + x="-29.557165" + y="305.51419" + transform="rotate(-30.379227)" /><path + id="rect1334" + style="fill:#62a0ea;fill-opacity:1;stroke-width:1.99999;stroke-linejoin:bevel" + d="m 196.33893,246.31613 -3.14038,1.84122 c -8e-4,4.8e-4 -0.002,6.5e-4 -0.003,0.001 l -57.76236,33.86098 -3.14296,1.84227 1.84072,3.1409 c 5e-4,8.6e-4 10e-4,0.002 0.002,0.003 5.1e-4,8.6e-4 0.001,0.002 0.002,0.003 l 0.30696,0.52348 0.047,-0.0274 c 1.12202,1.34202 3.06882,1.71731 4.63021,0.80202 l 8.50645,-4.98626 23.22908,4.12791 0.73339,-0.43804 19.76985,33.78141 c 1.31226,2.2423 4.17384,2.9912 6.41615,1.67896 l 0.16847,-0.0987 c 2.24235,-1.31217 2.99121,-4.17383 1.67896,-6.41615 l -19.81274,-33.85581 0.89321,-0.53288 7.04246,-21.76457 9.13743,-5.35626 c 1.56147,-0.91535 2.18475,-2.79709 1.56167,-4.43177 l 0.0475,-0.0279 -0.3085,-0.52606 z" /><g + aria-label="rpmlint" + id="text5107" + style="font-weight:bold;font-size:28.1241px;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro Bold';letter-spacing:0px;stroke-width:17.7161;stroke-linejoin:bevel" + transform="matrix(1.079111,0,0,1.079111,-1.0028127,131.35755)"><path + d="m 46.533799,155.87729 q -0.645426,-0.30212 -1.290852,-0.43944 -0.631694,-0.15106 -1.27712,-0.15106 -1.895081,0 -2.925016,1.22219 -1.016203,1.20846 -1.016203,3.47431 v 7.08596 h -4.916224 v -15.38037 h 4.916224 v 2.52678 q 0.947541,-1.51058 2.169731,-2.1972 1.235922,-0.70035 2.952481,-0.70035 0.247184,0 0.535566,0.0275 0.288382,0.0137 0.837681,0.0824 z" + style="font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#62a0ea;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5885" /><path + d="m 53.894403,164.84459 v 8.07469 h -4.916224 v -21.2304 h 4.916224 v 2.25213 q 1.016203,-1.34578 2.252125,-1.97748 1.235923,-0.64542 2.842622,-0.64542 2.842621,0 4.66904,2.26585 1.826418,2.25213 1.826418,5.80884 0,3.55671 -1.826418,5.82257 -1.826419,2.25212 -4.66904,2.25212 -1.606699,0 -2.842622,-0.63169 -1.235922,-0.64543 -2.252125,-1.99121 z m 3.268328,-9.95604 q -1.579234,0 -2.430647,1.16726 -0.837681,1.15353 -0.837681,3.33699 0,2.18346 0.837681,3.35072 0.851413,1.15353 2.430647,1.15353 1.579234,0 2.403183,-1.15353 0.83768,-1.15353 0.83768,-3.35072 0,-2.1972 -0.83768,-3.35072 -0.823949,-1.15353 -2.403183,-1.15353 z" + style="font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#62a0ea;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5887" /><path + d="m 83.364283,154.24312 q 0.933808,-1.42818 2.210928,-2.16973 1.290852,-0.75528 2.828889,-0.75528 2.650366,0 4.037346,1.63416 1.386979,1.63416 1.386979,4.75143 v 9.36555 h -4.943689 v -8.01976 q 0.01373,-0.17853 0.01373,-0.37078 0.01373,-0.19225 0.01373,-0.5493 0,-1.63416 -0.480636,-2.36198 -0.480637,-0.74156 -1.55177,-0.74156 -1.400712,0 -2.16973,1.15353 -0.755286,1.15353 -0.782751,3.33699 v 7.55286 H 78.98362 v -8.01976 q 0,-2.55424 -0.439439,-3.28206 -0.439439,-0.74156 -1.565502,-0.74156 -1.414444,0 -2.183463,1.16726 -0.769018,1.15353 -0.769018,3.30953 v 7.56659 h -4.94369 v -15.38037 h 4.94369 v 2.25213 q 0.906343,-1.30459 2.073603,-1.96375 1.180992,-0.65915 2.595437,-0.65915 1.592966,0 2.815156,0.76901 1.22219,0.76902 1.853884,2.156 z" + style="font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#62a0ea;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5889" /><path + d="m 98.41507,145.70152 h 4.91622 v 21.36773 h -4.91622 z" + style="font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#62a0ea;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5891" /><path + id="path5893" + style="font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#62a0ea;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + d="m 108.05542,151.68874 v 15.38045 h 4.91598 v -15.38045 z" /><path + d="m 133.15822,157.7037 v 9.36555 h -4.94369 v -1.52431 -5.64404 q 0,-1.99121 -0.0961,-2.74649 -0.0824,-0.75529 -0.30211,-1.11233 -0.28838,-0.48064 -0.78275,-0.74156 -0.49437,-0.27465 -1.12606,-0.27465 -1.53804,0 -2.41692,1.19473 -0.87888,1.18099 -0.87888,3.28206 v 7.56659 h -4.91622 v -15.38037 h 4.91622 v 2.25213 q 1.11233,-1.34578 2.36199,-1.97748 1.24965,-0.64542 2.76022,-0.64542 2.6641,0 4.03735,1.63416 1.38698,1.63416 1.38698,4.75143 z" + style="font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#62a0ea;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5895" /><path + d="m 143.0868,147.32196 v 4.36692 h 5.06728 v 3.51551 h -5.06728 v 6.52293 q 0,1.07113 0.4257,1.45564 0.42571,0.37078 1.6891,0.37078 h 2.52677 v 3.51551 h -4.21587 q -2.91128,0 -4.13347,-1.20846 -1.20846,-1.22219 -1.20846,-4.13347 v -6.52293 h -2.44438 v -3.51551 h 2.44438 v -4.36692 z" + style="font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#62a0ea;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5897" /></g><g + id="g1538" + transform="translate(-204.08817,5.9843623)"><path + id="path579-3" + style="fill:#62a0ea;fill-opacity:1;stroke-width:2.08748;stroke-linejoin:bevel" + d="m 322.57914,63.506678 a 82.11496,82.11496 0 0 0 -82.11282,82.070452 l 60.27074,-0.37362 A 21.875221,21.875221 0 0 1 304.2893,133.6838 L 279.77813,99.076082 c -0.12501,-0.1511 -0.36329,-0.45394 -0.6413,-0.9803 -0.18337,-0.34718 -0.41549,-0.77079 -0.50746,-1.56838 -0.092,-0.79759 0.24374,-2.34892 1.30431,-3.17965 0.51149,-0.40023 1.21601,-0.67691 1.87534,-0.72657 2.14643,-0.16056 2.63107,0.78188 3.07785,1.18752 0.29726,0.2699 0.48534,0.48346 0.60616,0.62787 l 29.08453,30.847228 a 21.875221,21.875221 0 0 1 8.00158,-1.53737 21.875221,21.875221 0 0 1 13.44827,4.6824 l 16.58969,-9.88157 c -13.29293,-14.90435 -35.28289,-12.41339 -39.77794,-10.55026 3.12522,-3.45527 23.53983,-10.200978 45.89901,6.90449 l 13.38006,-7.97006 C 348.91159,80.857722 317.64011,76.113382 291.00174,81.758282 c 24.43081,-9.56851 59.24071,-10.55215 87.23757,21.527378 l 10.47016,-6.236838 A 82.11496,82.11496 0 0 0 322.57914,63.506678 Z m -14.01827,24.981934 c 0,0 0.187,3.44848 1.31052,4.56303 1.12351,1.11456 4.57336,1.27434 4.57336,1.27434 0,0 -3.44848,0.18701 -4.56303,1.31052 -1.11455,1.12353 -1.27434,4.573368 -1.27434,4.573368 0,0 -0.18699,-3.448478 -1.31051,-4.563038 -1.12352,-1.11455 -4.57337,-1.27434 -4.57337,-1.27434 0,0 3.44848,-0.187 4.56303,-1.31051 1.11456,-1.12353 1.27434,-4.57337 1.27434,-4.57337 z m -44.46963,27.158568 c 0,0 0.14501,2.67066 1.01492,3.53363 0.86991,0.86297 3.54139,0.98599 3.54139,0.98599 0,0 -2.67067,0.14502 -3.53364,1.01492 -0.86297,0.86992 -0.98598,3.54139 -0.98598,3.54139 0,0 -0.14502,-2.67066 -1.01493,-3.53363 -0.86991,-0.86297 -3.54138,-0.98599 -3.54138,-0.98599 0,0 2.67066,-0.14502 3.53363,-1.01493 0.86297,-0.86991 0.98599,-3.54138 0.98599,-3.54138 z m 23.62336,14.29628 c 0,0 0.0943,1.736 0.65991,2.29702 0.56558,0.56114 2.30218,0.64131 2.30218,0.64131 0,0 -1.73595,0.0942 -2.29701,0.6599 -0.56107,0.56553 -0.64131,2.30219 -0.64131,2.30219 0,0 -0.0944,-1.736 -0.65991,-2.29702 -0.56557,-0.56114 -2.30218,-0.6413 -2.30218,-0.6413 0,0 1.73596,-0.0946 2.29702,-0.65991 0.56107,-0.56553 0.6413,-2.30219 0.6413,-2.30219 z" /><path + id="path1237-6" + style="fill:#62a0ea;fill-opacity:1;stroke-width:2.11465;stroke-linejoin:bevel" + d="m 282.08742,95.716942 c -0.0154,-5.4e-4 -0.0309,-6.1e-4 -0.046,5.1e-4 -0.0691,0.005 -0.13382,0.0282 -0.19275,0.0744 -0.47146,0.36929 0.42168,1.42989 0.42168,1.42989 l 33.49086,46.990058 c 1.31075,-2.75983 3.8269,-4.75608 6.81251,-5.40484 L 283.19484,96.526192 c 0,0 -0.62871,-0.79256 -1.10743,-0.80925 z m 40.25442,42.532318 c -3.98428,-2e-4 -7.21423,3.22975 -7.21404,7.21403 9e-5,3.98408 3.22996,7.21372 7.21404,7.21352 3.98388,-10e-5 7.21342,-3.22964 7.21351,-7.21352 1.9e-4,-3.98407 -3.22944,-7.21393 -7.21351,-7.21403 z m 0.0791,3.96875 c 1.73638,0 3.144,1.40762 3.144,3.144 0,1.73638 -1.40762,3.14399 -3.144,3.14399 -1.73638,0 -3.14399,-1.40761 -3.14399,-3.14399 0,-1.73638 1.40761,-3.144 3.14399,-3.144 z" + sodipodi:nodetypes="sccccccscccccsssss" /><rect + style="fill:#62a0ea;fill-opacity:1;stroke-width:1.99999;stroke-linejoin:bevel" + id="rect1332-7" + width="74.242485" + height="4.2533646" + x="219.33028" + y="284.50201" + transform="rotate(-30.379227)" /><path + id="rect1334-5" + style="fill:#62a0ea;fill-opacity:1;stroke-width:1.99999;stroke-linejoin:bevel" + d="m 400.4271,102.32137 -3.14038,1.84123 c -8e-4,4.8e-4 -0.002,6.5e-4 -0.003,0.001 l -57.76236,33.86098 -3.14296,1.84227 1.84072,3.1409 c 5e-4,8.6e-4 0.001,0.002 0.002,0.003 5.1e-4,8.6e-4 0.001,0.002 0.002,0.003 l 0.30696,0.52348 0.047,-0.0274 c 1.12202,1.34202 3.06882,1.71731 4.63021,0.80202 l 8.50645,-4.98626 23.22908,4.12791 0.73339,-0.43804 19.76985,33.78141 c 1.31226,2.2423 4.17384,2.9912 6.41615,1.67896 l 0.16847,-0.0987 c 2.24235,-1.31217 2.99121,-4.17383 1.67896,-6.41615 l -19.81274,-33.85581 0.89321,-0.53288 7.04246,-21.76457 9.13743,-5.35626 c 1.56147,-0.91535 2.18475,-2.79709 1.56167,-4.43177 l 0.0475,-0.0279 -0.3085,-0.52606 z" /><path + d="m 253.30049,155.57169 q -0.69648,-0.32602 -1.39297,-0.4742 -0.68167,-0.16301 -1.37815,-0.16301 -2.04501,0 -3.15642,1.31888 -1.0966,1.30406 -1.0966,3.74916 v 7.64654 h -5.30515 v -16.59713 h 5.30515 v 2.72668 q 1.02251,-1.63008 2.34139,-2.37102 1.33369,-0.75576 3.18605,-0.75576 0.26674,0 0.57794,0.0297 0.31119,0.0148 0.90395,0.0889 z" + style="font-weight:bold;font-size:28.1241px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;fill:#62a0ea;fill-opacity:1;stroke:none;stroke-width:0.856544;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5885-5" /><path + d="m 261.2434,165.2484 v 8.71349 h -5.30515 v -22.90996 h 5.30515 v 2.4303 q 1.0966,-1.45224 2.4303,-2.13392 1.33369,-0.69648 3.0675,-0.69648 3.0675,0 5.03841,2.4451 1.97091,2.4303 1.97091,6.26839 0,3.83808 -1.97091,6.2832 -1.97091,2.43028 -5.03841,2.43028 -1.73381,0 -3.0675,-0.68166 -1.3337,-0.69649 -2.4303,-2.14874 z m 3.52689,-10.74367 q -1.70417,0 -2.62294,1.25961 -0.90395,1.24478 -0.90395,3.60098 0,2.35619 0.90395,3.6158 0.91877,1.24478 2.62294,1.24478 1.70417,0 2.5933,-1.24478 0.90395,-1.24479 0.90395,-3.6158 0,-2.37102 -0.90395,-3.6158 -0.88913,-1.24479 -2.5933,-1.24479 z" + style="font-weight:bold;font-size:28.1241px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;fill:#62a0ea;fill-opacity:1;stroke:none;stroke-width:0.856544;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5887-6" /><path + d="m 293.04467,153.80824 q 1.00769,-1.54116 2.38584,-2.34138 1.39297,-0.81503 3.05269,-0.81503 2.86004,0 4.35674,1.76344 1.49671,1.76344 1.49671,5.12732 v 10.10647 h -5.33479 v -8.65421 q 0.0148,-0.19266 0.0148,-0.40011 0.0148,-0.20746 0.0148,-0.59276 0,-1.76344 -0.51866,-2.54884 -0.51866,-0.80022 -1.67453,-0.80022 -1.51153,0 -2.34138,1.24478 -0.81504,1.24479 -0.84468,3.60099 v 8.15037 h -5.33479 v -8.65421 q 0,-2.75631 -0.4742,-3.54171 -0.4742,-0.80022 -1.68935,-0.80022 -1.52634,0 -2.3562,1.2596 -0.82986,1.24479 -0.82986,3.57135 v 8.16519 h -5.33479 v -16.59713 h 5.33479 v 2.4303 q 0.97805,-1.4078 2.23765,-2.1191 1.27442,-0.7113 2.80077,-0.7113 1.71898,0 3.03786,0.82985 1.31888,0.82986 2.00055,2.32656 z" + style="font-weight:bold;font-size:28.1241px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;fill:#62a0ea;fill-opacity:1;stroke:none;stroke-width:0.856544;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5889-2" /><path + d="m 309.28614,144.59091 h 5.30515 v 23.05815 h -5.30515 z" + style="font-weight:bold;font-size:28.1241px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;fill:#62a0ea;fill-opacity:1;stroke:none;stroke-width:0.856544;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5891-9" /><path + id="path5893-1" + style="font-weight:bold;font-size:28.1241px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;fill:#62a0ea;fill-opacity:1;stroke:none;stroke-width:0.856544;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + d="m 319.68915,151.05178 v 16.59721 h 5.30489 v -16.59721 z" /><path + d="m 346.77786,157.54259 v 10.10647 h -5.33479 v -1.6449 -6.09055 q 0,-2.14873 -0.1037,-2.96376 -0.0889,-0.81505 -0.32601,-1.20033 -0.3112,-0.51866 -0.84468,-0.80023 -0.53348,-0.29637 -1.21514,-0.29637 -1.65972,0 -2.60813,1.28924 -0.94841,1.27442 -0.94841,3.54171 v 8.16519 h -5.30514 v -16.59713 h 5.30514 v 2.4303 q 1.20033,-1.45224 2.54885,-2.13392 1.34852,-0.69648 2.97859,-0.69648 2.87486,0 4.35675,1.76344 1.4967,1.76344 1.4967,5.12732 z" + style="font-weight:bold;font-size:28.1241px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;fill:#62a0ea;fill-opacity:1;stroke:none;stroke-width:0.856544;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5895-2" /><path + d="m 357.4919,146.33954 v 4.71239 h 5.46816 v 3.79363 h -5.46816 v 7.03896 q 0,1.15587 0.45938,1.5708 0.45939,0.40011 1.82272,0.40011 h 2.72667 v 3.79363 h -4.54939 q -3.1416,0 -4.46048,-1.30406 -1.30406,-1.31888 -1.30406,-4.46048 v -7.03896 h -2.63776 v -3.79363 h 2.63776 v -4.71239 z" + style="font-weight:bold;font-size:28.1241px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;fill:#62a0ea;fill-opacity:1;stroke:none;stroke-width:0.856544;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + id="path5897-7" /></g></g></svg> diff --git a/rpmlint/__init__.py b/rpmlint/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/rpmlint/__init__.py diff --git a/__isocodes__.py b/rpmlint/__isocodes__.py index 18e9f07..111bd5c 100644 --- a/__isocodes__.py +++ b/rpmlint/__isocodes__.py @@ -1,7 +1,7 @@ -# Generated with tools/generate-isocodes.py +# Generated with ./tools/generate-isocodes.py -LANGUAGES = set( -['aa', +LANGUAGES = \ +{'aa', 'aaa', 'aab', 'aac', @@ -110,6 +110,7 @@ LANGUAGES = set( 'aey', 'aez', 'af', + 'afa', 'afb', 'afd', 'afe', @@ -181,7 +182,6 @@ LANGUAGES = set( 'aip', 'aiq', 'air', - 'ais', 'ait', 'aiw', 'aix', @@ -191,7 +191,7 @@ LANGUAGES = set( 'aji', 'ajn', 'ajp', - 'ajt', + 'ajs', 'aju', 'ajw', 'ajz', @@ -225,6 +225,7 @@ LANGUAGES = set( 'ald', 'ale', 'alf', + 'alg', 'alh', 'ali', 'alj', @@ -302,7 +303,6 @@ LANGUAGES = set( 'aoe', 'aof', 'aog', - 'aoh', 'aoi', 'aoj', 'aok', @@ -315,6 +315,7 @@ LANGUAGES = set( 'aou', 'aox', 'aoz', + 'apa', 'apb', 'apc', 'apd', @@ -343,6 +344,7 @@ LANGUAGES = set( 'aqc', 'aqd', 'aqg', + 'aqk', 'aqm', 'aqn', 'aqp', @@ -365,6 +367,7 @@ LANGUAGES = set( 'arq', 'arr', 'ars', + 'art', 'aru', 'arv', 'arw', @@ -375,7 +378,6 @@ LANGUAGES = set( 'asa', 'asb', 'asc', - 'asd', 'ase', 'asf', 'asg', @@ -403,6 +405,7 @@ LANGUAGES = set( 'atd', 'ate', 'atg', + 'ath', 'ati', 'atj', 'atk', @@ -437,6 +440,7 @@ LANGUAGES = set( 'aup', 'auq', 'aur', + 'aus', 'aut', 'auu', 'auw', @@ -501,7 +505,6 @@ LANGUAGES = set( 'ays', 'ayt', 'ayu', - 'ayy', 'ayz', 'az', 'aza', @@ -518,10 +521,12 @@ LANGUAGES = set( 'baa', 'bab', 'bac', + 'bad', 'bae', 'baf', 'bag', 'bah', + 'bai', 'baj', 'bal', 'ban', @@ -529,6 +534,7 @@ LANGUAGES = set( 'bap', 'bar', 'bas', + 'bat', 'bau', 'bav', 'baw', @@ -559,7 +565,6 @@ LANGUAGES = set( 'bbw', 'bbx', 'bby', - 'bbz', 'bca', 'bcb', 'bcc', @@ -627,6 +632,7 @@ LANGUAGES = set( 'beo', 'bep', 'beq', + 'ber', 'bes', 'bet', 'beu', @@ -685,6 +691,7 @@ LANGUAGES = set( 'bgx', 'bgy', 'bgz', + 'bh', 'bha', 'bhb', 'bhc', @@ -713,12 +720,10 @@ LANGUAGES = set( 'bi', 'bia', 'bib', - 'bic', 'bid', 'bie', 'bif', 'big', - 'bij', 'bik', 'bil', 'bim', @@ -788,7 +793,6 @@ LANGUAGES = set( 'bld', 'ble', 'blf', - 'blg', 'blh', 'bli', 'blj', @@ -852,6 +856,7 @@ LANGUAGES = set( 'bnq', 'bnr', 'bns', + 'bnt', 'bnu', 'bnv', 'bnw', @@ -883,8 +888,9 @@ LANGUAGES = set( 'boy', 'boz', 'bpa', - 'bpb', + 'bpc', 'bpd', + 'bpe', 'bpg', 'bph', 'bpi', @@ -990,6 +996,7 @@ LANGUAGES = set( 'bth', 'bti', 'btj', + 'btk', 'btm', 'btn', 'bto', @@ -1160,6 +1167,7 @@ LANGUAGES = set( 'caf', 'cag', 'cah', + 'cai', 'caj', 'cak', 'cal', @@ -1170,6 +1178,7 @@ LANGUAGES = set( 'caq', 'car', 'cas', + 'cau', 'cav', 'caw', 'cax', @@ -1193,7 +1202,6 @@ LANGUAGES = set( 'cbv', 'cbw', 'cby', - 'cca', 'ccc', 'ccd', 'cce', @@ -1208,7 +1216,6 @@ LANGUAGES = set( 'cda', 'cde', 'cdf', - 'cdg', 'cdh', 'cdi', 'cdj', @@ -1224,8 +1231,10 @@ LANGUAGES = set( 'ceb', 'ceg', 'cek', + 'cel', 'cen', 'cet', + 'cey', 'cfa', 'cfd', 'cfg', @@ -1283,6 +1292,7 @@ LANGUAGES = set( 'ckb', 'ckh', 'ckl', + 'ckm', 'ckn', 'cko', 'ckq', @@ -1310,6 +1320,7 @@ LANGUAGES = set( 'clw', 'cly', 'cma', + 'cmc', 'cme', 'cmg', 'cmi', @@ -1329,6 +1340,9 @@ LANGUAGES = set( 'cnk', 'cnl', 'cno', + 'cnp', + 'cnq', + 'cnr', 'cns', 'cnt', 'cnu', @@ -1360,10 +1374,13 @@ LANGUAGES = set( 'cpa', 'cpb', 'cpc', + 'cpe', + 'cpf', 'cpg', 'cpi', 'cpn', 'cpo', + 'cpp', 'cps', 'cpu', 'cpx', @@ -1384,6 +1401,7 @@ LANGUAGES = set( 'crm', 'crn', 'cro', + 'crp', 'crq', 'crr', 'crs', @@ -1409,12 +1427,14 @@ LANGUAGES = set( 'csm', 'csn', 'cso', + 'csp', 'csq', 'csr', 'css', 'cst', 'csv', 'csw', + 'csx', 'csy', 'csz', 'cta', @@ -1431,12 +1451,12 @@ LANGUAGES = set( 'cts', 'ctt', 'ctu', + 'cty', 'ctz', 'cu', 'cua', 'cub', 'cuc', - 'cug', 'cuh', 'cui', 'cuj', @@ -1446,11 +1466,13 @@ LANGUAGES = set( 'cup', 'cuq', 'cur', + 'cus', 'cut', 'cuu', 'cuv', 'cuw', 'cux', + 'cuy', 'cv', 'cvg', 'cvn', @@ -1489,6 +1511,7 @@ LANGUAGES = set( 'dav', 'daw', 'dax', + 'day', 'daz', 'dba', 'dbb', @@ -1556,7 +1579,6 @@ LANGUAGES = set( 'dgr', 'dgs', 'dgt', - 'dgu', 'dgw', 'dgx', 'dgz', @@ -1591,7 +1613,6 @@ LANGUAGES = set( 'diq', 'dir', 'dis', - 'dit', 'diu', 'diw', 'dix', @@ -1613,6 +1634,7 @@ LANGUAGES = set( 'dju', 'djw', 'dka', + 'dkg', 'dkk', 'dkr', 'dks', @@ -1626,6 +1648,7 @@ LANGUAGES = set( 'dmc', 'dmd', 'dme', + 'dmf', 'dmg', 'dmk', 'dml', @@ -1646,6 +1669,7 @@ LANGUAGES = set( 'dnj', 'dnk', 'dnn', + 'dno', 'dnr', 'dnt', 'dnu', @@ -1674,6 +1698,7 @@ LANGUAGES = set( 'doy', 'doz', 'dpp', + 'dra', 'drb', 'drc', 'drd', @@ -1684,7 +1709,6 @@ LANGUAGES = set( 'drn', 'dro', 'drq', - 'drr', 'drs', 'drt', 'dru', @@ -1697,6 +1721,7 @@ LANGUAGES = set( 'dsn', 'dso', 'dsq', + 'dsz', 'dta', 'dtb', 'dtd', @@ -1715,7 +1740,6 @@ LANGUAGES = set( 'dua', 'dub', 'duc', - 'dud', 'due', 'duf', 'dug', @@ -1739,11 +1763,13 @@ LANGUAGES = set( 'dv', 'dva', 'dwa', + 'dwk', 'dwr', 'dws', 'dwu', 'dww', 'dwy', + 'dwz', 'dya', 'dyb', 'dyd', @@ -1761,6 +1787,7 @@ LANGUAGES = set( 'dzl', 'dzn', 'eaa', + 'ebc', 'ebg', 'ebk', 'ebo', @@ -1776,15 +1803,16 @@ LANGUAGES = set( 'efi', 'ega', 'egl', + 'egm', 'ego', 'egy', + 'ehs', 'ehu', 'eip', 'eit', 'eiv', 'eja', 'eka', - 'ekc', 'eke', 'ekg', 'eki', @@ -1813,11 +1841,13 @@ LANGUAGES = set( 'emm', 'emn', 'emp', + 'emq', 'ems', 'emu', 'emw', 'emx', 'emy', + 'emz', 'en', 'ena', 'enb', @@ -1914,10 +1944,12 @@ LANGUAGES = set( 'fi', 'fia', 'fie', + 'fif', 'fil', 'fip', 'fir', 'fit', + 'fiu', 'fiw', 'fj', 'fkk', @@ -2061,12 +2093,14 @@ LANGUAGES = set( 'geb', 'gec', 'ged', + 'gef', 'geg', 'geh', 'gei', 'gej', 'gek', 'gel', + 'gem', 'geq', 'ges', 'gev', @@ -2101,8 +2135,10 @@ LANGUAGES = set( 'gib', 'gic', 'gid', + 'gie', 'gig', 'gih', + 'gii', 'gil', 'gim', 'gin', @@ -2116,23 +2152,23 @@ LANGUAGES = set( 'gix', 'giy', 'giz', - 'gji', 'gjk', 'gjm', 'gjn', 'gjr', 'gju', 'gka', + 'gkd', 'gke', 'gkn', 'gko', 'gkp', 'gku', 'gl', + 'glb', 'glc', 'gld', 'glh', - 'gli', 'glj', 'glk', 'gll', @@ -2149,6 +2185,7 @@ LANGUAGES = set( 'gml', 'gmm', 'gmn', + 'gmr', 'gmu', 'gmv', 'gmx', @@ -2163,6 +2200,7 @@ LANGUAGES = set( 'gng', 'gnh', 'gni', + 'gnj', 'gnk', 'gnl', 'gnm', @@ -2195,6 +2233,7 @@ LANGUAGES = set( 'gos', 'got', 'gou', + 'gov', 'gow', 'gox', 'goy', @@ -2303,8 +2342,10 @@ LANGUAGES = set( 'gyl', 'gym', 'gyn', + 'gyo', 'gyr', 'gyy', + 'gyz', 'gza', 'gzi', 'gzn', @@ -2366,6 +2407,7 @@ LANGUAGES = set( 'hij', 'hik', 'hil', + 'him', 'hio', 'hir', 'hit', @@ -2374,7 +2416,9 @@ LANGUAGES = set( 'hji', 'hka', 'hke', + 'hkh', 'hkk', + 'hkn', 'hks', 'hla', 'hlb', @@ -2409,6 +2453,7 @@ LANGUAGES = set( 'hna', 'hnd', 'hne', + 'hng', 'hnh', 'hni', 'hnj', @@ -2497,6 +2542,7 @@ LANGUAGES = set( 'hwo', 'hy', 'hya', + 'hyw', 'hz', 'ia', 'iai', @@ -2507,6 +2553,7 @@ LANGUAGES = set( 'ibd', 'ibe', 'ibg', + 'ibh', 'ibl', 'ibm', 'ibn', @@ -2557,6 +2604,7 @@ LANGUAGES = set( 'ije', 'ijj', 'ijn', + 'ijo', 'ijs', 'ik', 'ike', @@ -2590,8 +2638,11 @@ LANGUAGES = set( 'imo', 'imr', 'ims', + 'imt', 'imy', 'inb', + 'inc', + 'ine', 'ing', 'inh', 'inj', @@ -2611,11 +2662,13 @@ LANGUAGES = set( 'ipo', 'iqu', 'iqw', + 'ira', 'ire', 'irh', 'iri', 'irk', 'irn', + 'iro', 'irr', 'iru', 'irx', @@ -2693,6 +2746,7 @@ LANGUAGES = set( 'jbi', 'jbj', 'jbk', + 'jbm', 'jbn', 'jbo', 'jbr', @@ -2706,7 +2760,6 @@ LANGUAGES = set( 'jdt', 'jeb', 'jee', - 'jeg', 'jeh', 'jei', 'jek', @@ -2744,6 +2797,7 @@ LANGUAGES = set( 'jko', 'jkp', 'jkr', + 'jks', 'jku', 'jle', 'jls', @@ -2821,6 +2875,7 @@ LANGUAGES = set( 'kao', 'kap', 'kaq', + 'kar', 'kav', 'kaw', 'kax', @@ -2953,7 +3008,6 @@ LANGUAGES = set( 'kg', 'kga', 'kgb', - 'kgd', 'kge', 'kgf', 'kgg', @@ -2982,6 +3036,7 @@ LANGUAGES = set( 'khf', 'khg', 'khh', + 'khi', 'khj', 'khk', 'khl', @@ -3028,7 +3083,6 @@ LANGUAGES = set( 'kjc', 'kjd', 'kje', - 'kjf', 'kjg', 'kjh', 'kji', @@ -3239,8 +3293,8 @@ LANGUAGES = set( 'krj', 'krk', 'krl', - 'krm', 'krn', + 'kro', 'krp', 'krr', 'krs', @@ -3389,7 +3443,6 @@ LANGUAGES = set( 'kxi', 'kxj', 'kxk', - 'kxl', 'kxm', 'kxn', 'kxo', @@ -3398,7 +3451,6 @@ LANGUAGES = set( 'kxr', 'kxs', 'kxt', - 'kxu', 'kxv', 'kxw', 'kxx', @@ -3465,7 +3517,6 @@ LANGUAGES = set( 'lah', 'lai', 'laj', - 'lak', 'lal', 'lam', 'lan', @@ -3479,7 +3530,6 @@ LANGUAGES = set( 'lay', 'laz', 'lb', - 'lba', 'lbb', 'lbc', 'lbe', @@ -3563,6 +3613,7 @@ LANGUAGES = set( 'lgl', 'lgm', 'lgn', + 'lgo', 'lgq', 'lgr', 'lgt', @@ -3639,7 +3690,6 @@ LANGUAGES = set( 'lll', 'llm', 'lln', - 'llo', 'llp', 'llq', 'lls', @@ -3667,7 +3717,6 @@ LANGUAGES = set( 'lmw', 'lmx', 'lmy', - 'lmz', 'ln', 'lna', 'lnb', @@ -3679,7 +3728,6 @@ LANGUAGES = set( 'lnl', 'lnm', 'lnn', - 'lno', 'lns', 'lnu', 'lnw', @@ -3715,6 +3763,7 @@ LANGUAGES = set( 'lpn', 'lpo', 'lpx', + 'lqr', 'lra', 'lrc', 'lre', @@ -3730,22 +3779,27 @@ LANGUAGES = set( 'lrv', 'lrz', 'lsa', + 'lsb', + 'lsc', 'lsd', 'lse', - 'lsg', 'lsh', 'lsi', 'lsl', 'lsm', + 'lsn', 'lso', 'lsp', 'lsr', 'lss', 'lst', + 'lsv', + 'lsw', 'lsy', 'lt', 'ltc', 'ltg', + 'lth', 'lti', 'ltn', 'lto', @@ -3776,6 +3830,7 @@ LANGUAGES = set( 'luz', 'lv', 'lva', + 'lvi', 'lvk', 'lvs', 'lvu', @@ -3786,9 +3841,11 @@ LANGUAGES = set( 'lwl', 'lwm', 'lwo', + 'lws', 'lwt', 'lwu', 'lww', + 'lxm', 'lya', 'lyg', 'lyn', @@ -3807,6 +3864,7 @@ LANGUAGES = set( 'mak', 'mam', 'man', + 'map', 'maq', 'mas', 'mat', @@ -4049,6 +4107,7 @@ LANGUAGES = set( 'mke', 'mkf', 'mkg', + 'mkh', 'mki', 'mkj', 'mkk', @@ -4130,6 +4189,7 @@ LANGUAGES = set( 'mnl', 'mnm', 'mnn', + 'mno', 'mnp', 'mnq', 'mnr', @@ -4301,6 +4361,7 @@ LANGUAGES = set( 'muk', 'mul', 'mum', + 'mun', 'muo', 'mup', 'muq', @@ -4322,7 +4383,6 @@ LANGUAGES = set( 'mvi', 'mvk', 'mvl', - 'mvm', 'mvn', 'mvo', 'mvp', @@ -4357,8 +4417,6 @@ LANGUAGES = set( 'mwu', 'mwv', 'mww', - 'mwx', - 'mwy', 'mwz', 'mxa', 'mxb', @@ -4389,16 +4447,15 @@ LANGUAGES = set( 'my', 'myb', 'myc', - 'myd', 'mye', 'myf', 'myg', 'myh', - 'myi', 'myj', 'myk', 'myl', 'mym', + 'myn', 'myo', 'myp', 'myr', @@ -4441,6 +4498,8 @@ LANGUAGES = set( 'nae', 'naf', 'nag', + 'nah', + 'nai', 'naj', 'nak', 'nal', @@ -4494,7 +4553,7 @@ LANGUAGES = set( 'ncm', 'ncn', 'nco', - 'ncp', + 'ncq', 'ncr', 'ncs', 'nct', @@ -4570,7 +4629,6 @@ LANGUAGES = set( 'ngl', 'ngm', 'ngn', - 'ngo', 'ngp', 'ngq', 'ngr', @@ -4607,6 +4665,7 @@ LANGUAGES = set( 'nhz', 'nia', 'nib', + 'nic', 'nid', 'nie', 'nif', @@ -4679,6 +4738,7 @@ LANGUAGES = set( 'nlj', 'nlk', 'nll', + 'nlm', 'nlo', 'nlq', 'nlu', @@ -4731,7 +4791,6 @@ LANGUAGES = set( 'nnp', 'nnq', 'nnr', - 'nns', 'nnt', 'nnu', 'nnv', @@ -4771,13 +4830,16 @@ LANGUAGES = set( 'npo', 'nps', 'npu', + 'npx', 'npy', 'nqg', 'nqk', + 'nql', 'nqm', 'nqn', 'nqo', 'nqq', + 'nqt', 'nqy', 'nr', 'nra', @@ -4798,6 +4860,7 @@ LANGUAGES = set( 'nrx', 'nrz', 'nsa', + 'nsb', 'nsc', 'nsd', 'nse', @@ -4837,6 +4900,7 @@ LANGUAGES = set( 'nty', 'ntz', 'nua', + 'nub', 'nuc', 'nud', 'nue', @@ -4874,6 +4938,7 @@ LANGUAGES = set( 'nwm', 'nwo', 'nwr', + 'nww', 'nwx', 'nwy', 'nxa', @@ -4888,7 +4953,6 @@ LANGUAGES = set( 'nxo', 'nxq', 'nxr', - 'nxu', 'nxx', 'ny', 'nyb', @@ -4917,6 +4981,7 @@ LANGUAGES = set( 'nyy', 'nza', 'nzb', + 'nzd', 'nzi', 'nzk', 'nzm', @@ -4939,6 +5004,7 @@ LANGUAGES = set( 'oc', 'oca', 'och', + 'ocm', 'oco', 'ocu', 'oda', @@ -4957,6 +5023,7 @@ LANGUAGES = set( 'oht', 'ohu', 'oia', + 'oie', 'oin', 'oj', 'ojb', @@ -4968,6 +5035,7 @@ LANGUAGES = set( 'ojw', 'oka', 'okb', + 'okc', 'okd', 'oke', 'okg', @@ -4984,6 +5052,7 @@ LANGUAGES = set( 'oku', 'okv', 'okx', + 'okz', 'ola', 'old', 'ole', @@ -5009,6 +5078,7 @@ LANGUAGES = set( 'omu', 'omw', 'omx', + 'omy', 'ona', 'onb', 'one', @@ -5057,6 +5127,7 @@ LANGUAGES = set( 'osa', 'osc', 'osi', + 'osn', 'oso', 'osp', 'ost', @@ -5071,6 +5142,7 @@ LANGUAGES = set( 'otl', 'otm', 'otn', + 'oto', 'otq', 'otr', 'ots', @@ -5085,6 +5157,7 @@ LANGUAGES = set( 'oue', 'oui', 'oum', + 'ovd', 'owi', 'owl', 'oyb', @@ -5093,6 +5166,7 @@ LANGUAGES = set( 'oyy', 'ozm', 'pa', + 'paa', 'pab', 'pac', 'pad', @@ -5109,7 +5183,6 @@ LANGUAGES = set( 'paq', 'par', 'pas', - 'pat', 'pau', 'pav', 'paw', @@ -5124,6 +5197,7 @@ LANGUAGES = set( 'pbh', 'pbi', 'pbl', + 'pbm', 'pbn', 'pbo', 'pbp', @@ -5193,6 +5267,8 @@ LANGUAGES = set( 'phd', 'phg', 'phh', + 'phi', + 'phj', 'phk', 'phl', 'phm', @@ -5213,7 +5289,6 @@ LANGUAGES = set( 'pif', 'pig', 'pih', - 'pii', 'pij', 'pil', 'pim', @@ -5255,7 +5330,6 @@ LANGUAGES = set( 'pll', 'pln', 'plo', - 'plp', 'plq', 'plr', 'pls', @@ -5289,6 +5363,7 @@ LANGUAGES = set( 'pna', 'pnb', 'pnc', + 'pnd', 'pne', 'png', 'pnh', @@ -5342,7 +5417,7 @@ LANGUAGES = set( 'ppu', 'pqa', 'pqm', - 'prb', + 'pra', 'prc', 'prd', 'pre', @@ -5407,7 +5482,6 @@ LANGUAGES = set( 'pug', 'pui', 'puj', - 'puk', 'pum', 'puo', 'pup', @@ -5435,7 +5509,9 @@ LANGUAGES = set( 'pyu', 'pyx', 'pyy', + 'pzh', 'pzn', + 'qaa-qtz', 'qu', 'qua', 'qub', @@ -5548,7 +5624,7 @@ LANGUAGES = set( 'rhg', 'rhp', 'ria', - 'rie', + 'rib', 'rif', 'ril', 'rim', @@ -5592,6 +5668,7 @@ LANGUAGES = set( 'rmy', 'rmz', 'rn', + 'rnb', 'rnd', 'rng', 'rnl', @@ -5600,6 +5677,7 @@ LANGUAGES = set( 'rnr', 'rnw', 'ro', + 'roa', 'rob', 'roc', 'rod', @@ -5619,9 +5697,10 @@ LANGUAGES = set( 'rro', 'rrt', 'rsb', - 'rsi', + 'rsk', 'rsl', 'rsm', + 'rsn', 'rtc', 'rth', 'rtm', @@ -5646,6 +5725,7 @@ LANGUAGES = set( 'rw', 'rwa', 'rwk', + 'rwl', 'rwm', 'rwo', 'rwr', @@ -5663,8 +5743,10 @@ LANGUAGES = set( 'sae', 'saf', 'sah', + 'sai', 'saj', 'sak', + 'sal', 'sam', 'sao', 'saq', @@ -5717,6 +5799,7 @@ LANGUAGES = set( 'scp', 'scq', 'scs', + 'sct', 'scu', 'scv', 'scw', @@ -5732,10 +5815,10 @@ LANGUAGES = set( 'sdj', 'sdk', 'sdl', - 'sdm', 'sdn', 'sdo', 'sdp', + 'sdq', 'sdr', 'sds', 'sdt', @@ -5755,6 +5838,7 @@ LANGUAGES = set( 'sej', 'sek', 'sel', + 'sem', 'sen', 'seo', 'sep', @@ -5784,6 +5868,7 @@ LANGUAGES = set( 'sgj', 'sgk', 'sgm', + 'sgn', 'sgp', 'sgr', 'sgs', @@ -5832,10 +5917,12 @@ LANGUAGES = set( 'sik', 'sil', 'sim', + 'sio', 'sip', 'siq', 'sir', 'sis', + 'sit', 'siu', 'siv', 'siw', @@ -5869,7 +5956,6 @@ LANGUAGES = set( 'skh', 'ski', 'skj', - 'skk', 'skm', 'skn', 'sko', @@ -5885,6 +5971,7 @@ LANGUAGES = set( 'sky', 'skz', 'sl', + 'sla', 'slc', 'sld', 'sle', @@ -5910,10 +5997,10 @@ LANGUAGES = set( 'sma', 'smb', 'smc', - 'smd', 'smf', 'smg', 'smh', + 'smi', 'smj', 'smk', 'sml', @@ -5931,12 +6018,10 @@ LANGUAGES = set( 'smy', 'smz', 'sn', - 'snb', 'snc', 'sne', 'snf', 'sng', - 'snh', 'sni', 'snj', 'snk', @@ -5966,6 +6051,7 @@ LANGUAGES = set( 'soj', 'sok', 'sol', + 'son', 'soo', 'sop', 'soq', @@ -6009,6 +6095,7 @@ LANGUAGES = set( 'sqs', 'sqt', 'squ', + 'sqx', 'sr', 'sra', 'srb', @@ -6034,6 +6121,7 @@ LANGUAGES = set( 'sry', 'srz', 'ss', + 'ssa', 'ssb', 'ssc', 'ssd', @@ -6091,6 +6179,7 @@ LANGUAGES = set( 'sui', 'suj', 'suk', + 'suo', 'suq', 'sur', 'sus', @@ -6168,8 +6257,10 @@ LANGUAGES = set( 'szl', 'szn', 'szp', + 'szs', 'szv', 'szw', + 'szy', 'ta', 'taa', 'tab', @@ -6178,6 +6269,7 @@ LANGUAGES = set( 'tae', 'taf', 'tag', + 'tai', 'taj', 'tak', 'tal', @@ -6194,7 +6286,6 @@ LANGUAGES = set( 'tay', 'taz', 'tba', - 'tbb', 'tbc', 'tbd', 'tbe', @@ -6287,6 +6378,7 @@ LANGUAGES = set( 'tew', 'tex', 'tey', + 'tez', 'tfi', 'tfn', 'tfo', @@ -6332,7 +6424,6 @@ LANGUAGES = set( 'tht', 'thu', 'thv', - 'thw', 'thy', 'thz', 'ti', @@ -6361,10 +6452,12 @@ LANGUAGES = set( 'tja', 'tjg', 'tji', + 'tjj', 'tjl', 'tjm', 'tjn', 'tjo', + 'tjp', 'tjs', 'tju', 'tjw', @@ -6441,7 +6534,6 @@ LANGUAGES = set( 'tnb', 'tnc', 'tnd', - 'tne', 'tng', 'tnh', 'tni', @@ -6470,6 +6562,7 @@ LANGUAGES = set( 'toh', 'toi', 'toj', + 'tok', 'tol', 'tom', 'too', @@ -6606,8 +6699,10 @@ LANGUAGES = set( 'tum', 'tun', 'tuo', + 'tup', 'tuq', 'tus', + 'tut', 'tuu', 'tuv', 'tux', @@ -6625,6 +6720,7 @@ LANGUAGES = set( 'tvt', 'tvu', 'tvw', + 'tvx', 'tvy', 'tw', 'twa', @@ -6680,6 +6776,7 @@ LANGUAGES = set( 'tyu', 'tyv', 'tyx', + 'tyy', 'tyz', 'tza', 'tzh', @@ -6712,6 +6809,7 @@ LANGUAGES = set( 'uga', 'ugb', 'uge', + 'ugh', 'ugn', 'ugo', 'ugy', @@ -6724,11 +6822,14 @@ LANGUAGES = set( 'uka', 'ukg', 'ukh', + 'uki', + 'ukk', 'ukl', 'ukp', 'ukq', 'uks', 'uku', + 'ukv', 'ukw', 'uky', 'ula', @@ -6760,6 +6861,7 @@ LANGUAGES = set( 'und', 'une', 'ung', + 'uni', 'unk', 'unm', 'unn', @@ -6767,6 +6869,7 @@ LANGUAGES = set( 'unu', 'unx', 'unz', + 'uon', 'upi', 'upv', 'ur', @@ -6797,14 +6900,15 @@ LANGUAGES = set( 'usi', 'usk', 'usp', + 'uss', 'usu', 'uta', 'ute', + 'uth', 'utp', 'utr', 'utu', 'uum', - 'uun', 'uur', 'uuu', 'uve', @@ -6855,15 +6959,16 @@ LANGUAGES = set( 'vit', 'viv', 'vka', - 'vki', 'vkj', 'vkk', 'vkl', 'vkm', + 'vkn', 'vko', 'vkp', 'vkt', 'vku', + 'vkz', 'vlp', 'vls', 'vma', @@ -6918,6 +7023,7 @@ LANGUAGES = set( 'wah', 'wai', 'waj', + 'wak', 'wal', 'wam', 'wan', @@ -6946,6 +7052,7 @@ LANGUAGES = set( 'wbp', 'wbq', 'wbr', + 'wbs', 'wbt', 'wbv', 'wbw', @@ -6955,6 +7062,7 @@ LANGUAGES = set( 'wdg', 'wdj', 'wdk', + 'wdt', 'wdu', 'wdy', 'wea', @@ -6964,6 +7072,7 @@ LANGUAGES = set( 'weh', 'wei', 'wem', + 'wen', 'weo', 'wep', 'wer', @@ -7005,6 +7114,7 @@ LANGUAGES = set( 'wkb', 'wkd', 'wkl', + 'wkr', 'wku', 'wkw', 'wky', @@ -7012,6 +7122,7 @@ LANGUAGES = set( 'wlc', 'wle', 'wlg', + 'wlh', 'wli', 'wlk', 'wll', @@ -7029,6 +7140,7 @@ LANGUAGES = set( 'wmc', 'wmd', 'wme', + 'wmg', 'wmh', 'wmi', 'wmm', @@ -7070,9 +7182,7 @@ LANGUAGES = set( 'wow', 'woy', 'wpc', - 'wra', 'wrb', - 'wrd', 'wrg', 'wrh', 'wri', @@ -7124,10 +7234,10 @@ LANGUAGES = set( 'www', 'wxa', 'wxw', - 'wya', 'wyb', 'wyi', 'wym', + 'wyn', 'wyr', 'wyy', 'xaa', @@ -7185,6 +7295,8 @@ LANGUAGES = set( 'xdc', 'xdk', 'xdm', + 'xdo', + 'xdq', 'xdy', 'xeb', 'xed', @@ -7213,6 +7325,7 @@ LANGUAGES = set( 'xhc', 'xhd', 'xhe', + 'xhm', 'xhr', 'xht', 'xhu', @@ -7294,9 +7407,12 @@ LANGUAGES = set( 'xng', 'xnh', 'xni', + 'xnj', 'xnk', + 'xnm', 'xnn', 'xno', + 'xnq', 'xnr', 'xns', 'xnt', @@ -7315,12 +7431,17 @@ LANGUAGES = set( 'xor', 'xow', 'xpa', + 'xpb', 'xpc', + 'xpd', 'xpe', + 'xpf', 'xpg', + 'xph', 'xpi', 'xpj', 'xpk', + 'xpl', 'xpm', 'xpn', 'xpo', @@ -7330,7 +7451,11 @@ LANGUAGES = set( 'xps', 'xpt', 'xpu', + 'xpv', + 'xpw', + 'xpx', 'xpy', + 'xpz', 'xqa', 'xqt', 'xra', @@ -7341,7 +7466,6 @@ LANGUAGES = set( 'xri', 'xrm', 'xrn', - 'xrq', 'xrr', 'xrt', 'xru', @@ -7353,6 +7477,7 @@ LANGUAGES = set( 'xse', 'xsh', 'xsi', + 'xsj', 'xsl', 'xsm', 'xsn', @@ -7386,7 +7511,6 @@ LANGUAGES = set( 'xtv', 'xtw', 'xty', - 'xtz', 'xua', 'xub', 'xud', @@ -7593,6 +7717,7 @@ LANGUAGES = set( 'ypb', 'ypg', 'yph', + 'ypk', 'ypm', 'ypn', 'ypo', @@ -7613,6 +7738,7 @@ LANGUAGES = set( 'ysd', 'ysg', 'ysl', + 'ysm', 'ysn', 'yso', 'ysp', @@ -7693,12 +7819,15 @@ LANGUAGES = set( 'zax', 'zay', 'zaz', + 'zba', 'zbc', 'zbe', 'zbl', 'zbt', + 'zbu', 'zbw', 'zca', + 'zcd', 'zch', 'zdj', 'zea', @@ -7723,7 +7852,6 @@ LANGUAGES = set( 'zil', 'zim', 'zin', - 'zir', 'ziw', 'ziz', 'zka', @@ -7740,6 +7868,7 @@ LANGUAGES = set( 'zku', 'zkv', 'zkz', + 'zla', 'zlj', 'zlm', 'zln', @@ -7771,6 +7900,7 @@ LANGUAGES = set( 'zmy', 'zmz', 'zna', + 'znd', 'zne', 'zng', 'znk', @@ -7847,11 +7977,10 @@ LANGUAGES = set( 'zyn', 'zyp', 'zza', - 'zzj'] -) + 'zzj'} -COUNTRIES = set( -['AD', +COUNTRIES = \ +{'AD', 'AE', 'AF', 'AG', @@ -7867,6 +7996,15 @@ COUNTRIES = set( 'AW', 'AX', 'AZ', + 'Adlm', + 'Afak', + 'Aghb', + 'Ahom', + 'Arab', + 'Aran', + 'Armi', + 'Armn', + 'Avst', 'BA', 'BB', 'BD', @@ -7888,6 +8026,18 @@ COUNTRIES = set( 'BW', 'BY', 'BZ', + 'Bali', + 'Bamu', + 'Bass', + 'Batk', + 'Beng', + 'Bhks', + 'Blis', + 'Bopo', + 'Brah', + 'Brai', + 'Bugi', + 'Buhd', 'CA', 'CC', 'CD', @@ -7907,12 +8057,25 @@ COUNTRIES = set( 'CX', 'CY', 'CZ', + 'Cakm', + 'Cans', + 'Cari', + 'Cham', + 'Cher', + 'Cirt', + 'Copt', + 'Cprt', + 'Cyrl', + 'Cyrs', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', + 'Deva', + 'Dsrt', + 'Dupl', 'EC', 'EE', 'EG', @@ -7920,6 +8083,11 @@ COUNTRIES = set( 'ER', 'ES', 'ET', + 'Egyd', + 'Egyh', + 'Egyp', + 'Elba', + 'Ethi', 'FI', 'FJ', 'FK', @@ -7945,12 +8113,33 @@ COUNTRIES = set( 'GU', 'GW', 'GY', + 'Geok', + 'Geor', + 'Glag', + 'Goth', + 'Gran', + 'Grek', + 'Gujr', + 'Guru', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', + 'Hanb', + 'Hang', + 'Hani', + 'Hano', + 'Hans', + 'Hant', + 'Hatr', + 'Hebr', + 'Hira', + 'Hluw', + 'Hmng', + 'Hrkt', + 'Hung', 'ID', 'IE', 'IL', @@ -7961,10 +8150,16 @@ COUNTRIES = set( 'IR', 'IS', 'IT', + 'Inds', + 'Ital', 'JE', 'JM', 'JO', 'JP', + 'Jamo', + 'Java', + 'Jpan', + 'Jurc', 'KE', 'KG', 'KH', @@ -7976,6 +8171,17 @@ COUNTRIES = set( 'KW', 'KY', 'KZ', + 'Kali', + 'Kana', + 'Khar', + 'Khmr', + 'Khoj', + 'Kitl', + 'Kits', + 'Knda', + 'Kore', + 'Kpel', + 'Kthi', 'LA', 'LB', 'LC', @@ -7987,6 +8193,20 @@ COUNTRIES = set( 'LU', 'LV', 'LY', + 'Lana', + 'Laoo', + 'Latf', + 'Latg', + 'Latn', + 'Leke', + 'Lepc', + 'Limb', + 'Lina', + 'Linb', + 'Lisu', + 'Loma', + 'Lyci', + 'Lydi', 'MA', 'MC', 'MD', @@ -8010,6 +8230,22 @@ COUNTRIES = set( 'MX', 'MY', 'MZ', + 'Mahj', + 'Mand', + 'Mani', + 'Marc', + 'Maya', + 'Mend', + 'Merc', + 'Mero', + 'Mlym', + 'Modi', + 'Mong', + 'Moon', + 'Mroo', + 'Mtei', + 'Mult', + 'Mymr', 'NA', 'NC', 'NE', @@ -8022,7 +8258,19 @@ COUNTRIES = set( 'NR', 'NU', 'NZ', + 'Narb', + 'Nbat', + 'Newa', + 'Nkgb', + 'Nkoo', + 'Nshu', 'OM', + 'Ogam', + 'Olck', + 'Orkh', + 'Orya', + 'Osge', + 'Osma', 'PA', 'PE', 'PF', @@ -8037,12 +8285,28 @@ COUNTRIES = set( 'PT', 'PW', 'PY', + 'Palm', + 'Pauc', + 'Perm', + 'Phag', + 'Phli', + 'Phlp', + 'Phlv', + 'Phnx', + 'Piqd', + 'Plrd', + 'Prti', 'QA', + 'Qaaa', + 'Qabx', 'RE', 'RO', 'RS', 'RU', 'RW', + 'Rjng', + 'Roro', + 'Runr', 'SA', 'SB', 'SC', @@ -8064,6 +8328,23 @@ COUNTRIES = set( 'SX', 'SY', 'SZ', + 'Samr', + 'Sara', + 'Sarb', + 'Saur', + 'Sgnw', + 'Shaw', + 'Shrd', + 'Sidd', + 'Sind', + 'Sinh', + 'Sora', + 'Sund', + 'Sylo', + 'Syrc', + 'Syre', + 'Syrj', + 'Syrn', 'TC', 'TD', 'TF', @@ -8080,12 +8361,28 @@ COUNTRIES = set( 'TV', 'TW', 'TZ', + 'Tagb', + 'Takr', + 'Tale', + 'Talu', + 'Taml', + 'Tang', + 'Tavt', + 'Telu', + 'Teng', + 'Tfng', + 'Tglg', + 'Thaa', + 'Thai', + 'Tibt', + 'Tirh', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', + 'Ugar', 'VA', 'VC', 'VE', @@ -8093,11 +8390,24 @@ COUNTRIES = set( 'VI', 'VN', 'VU', + 'Vaii', + 'Visp', 'WF', 'WS', + 'Wara', + 'Wole', + 'Xpeo', + 'Xsux', 'YE', 'YT', + 'Yiii', 'ZA', 'ZM', - 'ZW'] -) + 'ZW', + 'Zinh', + 'Zmth', + 'Zsye', + 'Zsym', + 'Zxxx', + 'Zyyy', + 'Zzzz'} diff --git a/rpmlint/arparser.py b/rpmlint/arparser.py new file mode 100644 index 0000000..c1bcbb4 --- /dev/null +++ b/rpmlint/arparser.py @@ -0,0 +1,24 @@ +import subprocess + +from rpmlint.helpers import ENGLISH_ENVIROMENT + + +class ArParser: + """ + Class contains all information obtained by ar command. + """ + + def __init__(self, pkgfile_path): + self.pkgfile_path = pkgfile_path + self.objects = [] + self.parsing_failed_reason = None + self.parse() + + def parse(self): + r = subprocess.run(['ar', 't', self.pkgfile_path], encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + return + + self.objects = r.stdout.splitlines() diff --git a/rpmlint/checks/AbstractCheck.py b/rpmlint/checks/AbstractCheck.py new file mode 100644 index 0000000..72db300 --- /dev/null +++ b/rpmlint/checks/AbstractCheck.py @@ -0,0 +1,67 @@ +import concurrent.futures +import re + + +class AbstractCheck: + def __init__(self, config, output): + # Note: do not add any capturing parentheses here + self.macro_regex = re.compile(r'%+[{(]?[a-zA-Z_]\w{2,}[)}]?') + self.config = config + self.output = output + # by default do not track checked files + self.checked_files = None + + def check(self, pkg): + if pkg.is_source: + return self.check_source(pkg) + return self.check_binary(pkg) + + def check_source(self, pkg): + return + + def check_binary(self, pkg): + return + + def check_spec(self, pkg): + return + + def after_checks(self): + return + + +class AbstractFilesCheck(AbstractCheck): + def __init__(self, config, output, file_regexp): + self.__files_re = re.compile(file_regexp) + self.use_threads = False + super().__init__(config, output) + + def check_binary(self, pkg): + if self.checked_files is None: + self.checked_files = 0 + + filenames = [x for x in pkg.files if x not in pkg.ghost_files and self.__files_re.match(x)] + if self.use_threads: + # NOTE: the speed benefit of the ThreadPoolExecutor is limited due to + # Global Interpreter Lock (GIL). + + # start with the biggest files first + filenames = sorted(filenames, key=lambda x: pkg.files[x].size, reverse=True) + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = [] + for filename in filenames: + futures.append(executor.submit(self.check_file, pkg, filename)) + concurrent.futures.wait(futures) + for future in futures: + err = future.exception() + if err: + raise err + else: + for filename in filenames: + self.check_file(pkg, filename) + self.checked_files += len(filenames) + + def check_file(self, pkg, filename): + """Virtual method called for each file that match the regexp passed + to the constructor. + """ + raise NotImplementedError('check must be implemented in subclass') diff --git a/rpmlint/checks/AlternativesCheck.py b/rpmlint/checks/AlternativesCheck.py new file mode 100644 index 0000000..08f2d6c --- /dev/null +++ b/rpmlint/checks/AlternativesCheck.py @@ -0,0 +1,271 @@ +from os.path import basename +from pathlib import Path +import re +import stat + +import rpm +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.helpers import byte_to_string + + +class AlternativesCheck(AbstractCheck): + """ + Check for compliance with update-alternatives usage guidelines: + http://en.opensuse.org/openSUSE:Packaging_Multiple_Version_guidelines + https://docs.fedoraproject.org/en-US/packaging-guidelines/Alternatives/ + In short the rules are: + /etc/alternative/basename must be in %files and must be %ghost file + The alternative master must be a symlink to /etc and in filelist. I.e.: + /usr/bin/basename -> /etc/alternative/basename + In %post the update-alternatives with install must be called + In %postun the update-alternatives with remove must be called + Requires(post) and Requires(postun) must depend on update-alternatives + """ + # Regex to match anything that can be in requires for update-alternatives + re_requirement = re.compile(r'^(/usr/sbin/|%{?_sbindir}?/)?update-alternatives$') + re_install = re.compile(r'--install\s+(?P<link>\S+)\s+(?P<name>\S+)\s+(\S+)\s+(\S+)') + re_slave = re.compile(r'--slave\s+(?P<link>\S+)\s+(\S+)\s+(\S+)') + command = 'update-alternatives' + alts_requirement = 'alts' + + def __init__(self, config, output): + super().__init__(config, output) + + def check(self, pkg): + if pkg.is_source: + return + + if self._check_libalternatives_presence(pkg): + self.output.add_info('I', pkg, 'package-supports-libalternatives') + self._check_libalternatives_requirements(pkg) + self._check_libalternatives_filelist(pkg) + + self.install_binaries = {} + self.slave_binaries = [] + # populate scriptlets + self.post = byte_to_string(pkg.header[rpm.RPMTAG_POSTIN]) + self.postun = byte_to_string(pkg.header[rpm.RPMTAG_POSTUN]) + + if not self._check_ua_presence(pkg): + return + self.output.add_info('I', pkg, 'package-supports-update-alternatives') + + self._check_requirements(pkg) + self._check_post_phase(pkg, self.post) + self._check_postun_phase(pkg, self.postun) + self._check_filelist(pkg) + + def _find_u_a_binarires(self, line): + """ + Find all binaries that have install or slave that are needed + to be validated. + update-alternatives --install link name path priority [--slave link name path]+ + """ + match = self.re_install.search(line) + if match: + self.install_binaries[match.group('link')] = match.group('name') + # --slave can be repeated multiple times + matches = self.re_slave.finditer(line) + for match in matches: + self.slave_binaries.append(match.group('link')) + + def _check_post_phase(self, pkg, script): + """ + Validate that post phase contains the update-alternatives --install call + Collect all binaries that are to be validated for the usage + """ + script = self._normalize_script(script) + # If there is no u-a call then give up right away + if not script: + self.output.add_info('E', pkg, 'update-alternatives-post-call-missing') + return + # collect all the known binaries + for line in script: + self._find_u_a_binarires(line) + # if there is u-a call, but no --install command it is still an issue + if not self.install_binaries: + self.output.add_info('E', pkg, 'update-alternatives-post-call-missing') + + def _check_postun_phase(self, pkg, script): + """ + Validate that post phase contains the update-alternatives --remove call + Make sure there is --remove line for all installed binaries + update-alternatives --remove name path + """ + script = self._normalize_script(script) + # If there is no u-a call then give up right away + if not script: + self.output.add_info('E', pkg, 'update-alternatives-postun-call-missing') + return + # validate each binary actually is properly removed + binaries = list(self.install_binaries.values()) + # we remove from the binaries list in the loop, copy it + for binary in binaries.copy(): + re_remove = re.compile(fr'--remove\s+{re.escape(binary)}\b') + for line in script: + if re_remove.search(line) and binary in binaries: + binaries.remove(binary) + for binary in binaries: + self.output.add_info('E', pkg, 'update-alternatives-postun-call-missing', binary) + + def _check_filelist(self, pkg): + """ + Validate all filelists for required content to make u-a work: + * For each install/slave binary I need /etc/alternatives/X + + This file must be in filelist marked as ghost + * The install/slave binary must be present in filelist + + The item must be a a link to /etc/alternatives + """ + files = pkg.files + ghost_files = pkg.ghost_files + for binary in self.slave_binaries + list(self.install_binaries.keys()): + etc_alt_file = '/etc/alternatives/%s' % basename(binary) + if etc_alt_file not in files: + # The alternative is missing completely + self.output.add_info('E', pkg, 'alternative-link-missing', etc_alt_file) + elif etc_alt_file not in ghost_files: + # The alternative is present, but not as ghost + self.output.add_info('E', pkg, 'alternative-link-not-ghost', etc_alt_file) + + # generic-name should be a symlink to /etc/alternatives/$(basename) + if binary not in files: + self.output.add_info('E', pkg, 'alternative-generic-name-missing', binary) + elif not stat.S_ISLNK(files[binary].mode): + self.output.add_info('E', pkg, 'alternative-generic-name-not-symlink', binary) + + def _check_ua_presence(self, pkg): + """ + Check if there is update-alternatives scriptlet present and if we should do validation + """ + # first check just if we have anything in /etc/alternatives + for path in pkg.files: + if path.startswith('/etc/alternatives'): + return True + # then check the scriptlets if they run update-alternatives + if self._check_scriptlet_for_alternatives(self.post): + return True + if self._check_scriptlet_for_alternatives(self.postun): + return True + return False + + def _check_libalternatives_presence(self, pkg): + """ + Check if there is libalternatives scriptlet present + """ + # first check just if we have anything in /usr/share/libalternatives/ + for path in pkg.files: + if path.startswith('/usr/share/libalternatives/'): + return True + # then check if package with the name "alts" is required + return any(req[0] == self.alts_requirement for req in pkg.requires + pkg.prereq) + + def _check_scriptlet_for_alternatives(self, scriptlet): + """ + Check if scriptlet actually contains the update-alternatives call + """ + if scriptlet is not None and self.command in scriptlet: + return True + return False + + def _normalize_script(self, script): + """ + Remove "backslash+newline" to keep all commands as oneliners. + Remove single and double quotes everywhere. + Keep only the line that contains the update-alternatives call. + Return the list of lines that contain update-alternatives calls + """ + # with old rpm we get wrong type + script = byte_to_string(script) + if script is None: + return None + script = script.replace('\\\n', '') + script = script.replace('"', '') + script = script.replace("'", '') + script = script.strip() + return [i for i in script.splitlines() if self.command in i] + + def _check_requirements(self, pkg): + """ + Check that Requires(post/postun) contain the update-alternatives dependency + """ + for require in pkg.prereq: + if self.re_requirement.match(require[0]): + return + self.output.add_info('E', pkg, 'update-alternatives-requirement-missing') + + def _check_libalternatives_requirements(self, pkg): + """ + Check the requirement of package "alts" + """ + for req in pkg.requires + pkg.prereq: + if req[0] == self.alts_requirement: + return + self.output.add_info('E', pkg, 'alts-requirement-missed') + + def _check_libalternatives_filelist(self, pkg): + """ + Checking if all links to "alts" have corresponding entries in + /usr/share/libalternatives. + """ + for f, pkgfile in pkg.files.items(): + if pkgfile.linkto == Path(self.alts_requirement).name: + dir_name = '/usr/share/libalternatives/' + Path(f).name + if dir_name not in pkg.files: + self.output.add_info('E', pkg, 'libalternatives-directory-not-exists', dir_name) + else: + r = re.compile('^' + dir_name + '/.*.conf$') + if not list(filter(r.match, pkg.files)): + self.output.add_info('E', pkg, 'empty-libalternatives-directory', dir_name) + """ + Checking content of all /usr/share/libalternatives/*/*.conf files + """ + for f, pkgfile in pkg.files.items(): + if re.search('^/usr/share/libalternatives/.*conf$', f): + filename = Path(pkg.dirname + f) + if not filename.exists(): + if pkgfile.is_ghost: + self.output.add_info('I', pkg, 'libalternatives-conf-not-found', f) + else: + self.output.add_info('E', pkg, 'libalternatives-conf-not-found', f) + continue + bin_found = False + man_found = False + with open(filename) as read_obj: + # Read all lines in the file one by one. E.g: + # + # binary=/usr/bin/jupyter-3.8 + # man=jupyter-3.8.1 + # group=jupyter, jupyter-migrate, jupyter-troubleshoot + # + for line_nr, line in enumerate(read_obj): + line_array = [x.strip() for x in line.split('=')] + line_nr_str = f'Line: {line_nr}' + if len(line_array) != 2: # empty values are valid + self.output.add_info('E', pkg, 'wrong-entry-format', f, line_nr_str) + + key, value = line_array + if key == 'binary': + if bin_found: + self.output.add_info('E', pkg, 'multiple-entries', f, line_nr_str) + continue + for path in pkg.files: + if 'bin/' in path and path.endswith(value): + bin_found = True + if not bin_found: + self.output.add_info('W', pkg, 'binary-entry-value-not-found', f, line_nr_str) + elif key == 'man': + if man_found: + self.output.add_info('E', pkg, 'double-entries', f, line_nr_str) + continue + mans = value.split(',') + for man in mans: + man_found = False + for path in pkg.files: + if path.startswith('/usr/share/man/') and man.strip() in path: + man_found = True + if not man_found: + self.output.add_info('W', pkg, 'man-entry-value-not-found', f, line_nr_str) + elif key != 'group' and key != 'options': + self.output.add_info('W', pkg, 'wrong-tag-found', f, line_nr_str) + if not bin_found: + self.output.add_info('W', pkg, 'wrong-or-missed-binary-entry', f) diff --git a/rpmlint/checks/AppDataCheck.py b/rpmlint/checks/AppDataCheck.py new file mode 100644 index 0000000..26a65d2 --- /dev/null +++ b/rpmlint/checks/AppDataCheck.py @@ -0,0 +1,38 @@ +import subprocess +from xml.etree import ElementTree + +from rpmlint.checks.AbstractCheck import AbstractFilesCheck +from rpmlint.helpers import ENGLISH_ENVIROMENT + + +class AppDataCheck(AbstractFilesCheck): + """ + check appdata files for format violations + https://www.freedesktop.org/software/appstream/docs/ + """ + # default command, split here so we can mock it later + cmd = 'appstream-util validate-relax --nonet ' + + def __init__(self, config, output): + super().__init__(config, output, r'/usr/share/appdata/.*\.(appdata|metainfo).xml$') + + def check_file(self, pkg, filename): + root = pkg.dir_name() + f = root + filename + cmd = self.cmd + f + + validation_failed = False + try: + r = subprocess.run(cmd.split(), env=ENGLISH_ENVIROMENT, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + if r.returncode != 0: + validation_failed = True + except FileNotFoundError: + # appstream-util is not installed + # validate the xml format only + try: + ElementTree.parse(f) + except ElementTree.ParseError: + validation_failed = True + if validation_failed: + self.output.add_info('E', pkg, 'invalid-appdata-file', filename) diff --git a/rpmlint/checks/BashismsCheck.py b/rpmlint/checks/BashismsCheck.py new file mode 100644 index 0000000..2cc088a --- /dev/null +++ b/rpmlint/checks/BashismsCheck.py @@ -0,0 +1,72 @@ +import stat +import subprocess + +from rpmlint.checks.AbstractCheck import AbstractFilesCheck +from rpmlint.helpers import ENGLISH_ENVIROMENT + + +class BashismsCheck(AbstractFilesCheck): + def __init__(self, config, output): + super().__init__(config, output, r'.*') + self.use_threads = True + self._detect_early_fail_option() + self.file_cache = {} + + def _detect_early_fail_option(self): + output = subprocess.check_output('checkbashisms --help', + shell=True, encoding='utf8') + # FIXME: remove in the future + self.use_early_fail = '[-e]' in output + + def check_file(self, pkg, filename): + root = pkg.dir_name() + pkgfile = pkg.files[filename] + filepath = root + filename + + # We only care about the real files that state they are shell scripts + if not (stat.S_ISREG(pkgfile.mode) and + pkgfile.magic.startswith('POSIX shell script')): + return + + # There are package likes Linux kernel where there are common + # shell scripts present in multiple packages + # (kernel-source, kernel-source-vanilla). + if pkgfile.md5 not in self.file_cache: + self.file_cache[pkgfile.md5] = list(self.check_bashisms(pkg, filepath, filename)) + + for warning in self.file_cache[pkgfile.md5]: + self.output.add_info('W', pkg, warning, filename) + + def check_bashisms(self, pkg, filepath, filename): + """ + Run dash and then checkbashism on file + + We need to see if it is valid syntax of bash and if there are no + potential bash issues. + Return a warning message or None if there is no problem. + """ + try: + r = subprocess.run(['dash', '-n', filepath], + stderr=subprocess.DEVNULL, + env=ENGLISH_ENVIROMENT) + if r.returncode == 2: + yield 'bin-sh-syntax-error' + elif r.returncode == 127: + raise FileNotFoundError(filename) + except UnicodeDecodeError: + pass + + try: + cmd = ['checkbashisms', filepath] + # --early-fail option can rapidly speed up the check + if self.use_early_fail: + cmd.append('-e') + r = subprocess.run(cmd, + stderr=subprocess.DEVNULL, + env=ENGLISH_ENVIROMENT) + if r.returncode == 1: + yield 'potential-bashisms' + elif r.returncode == 2: + raise FileNotFoundError(filename) + except UnicodeDecodeError: + pass diff --git a/rpmlint/checks/BinariesCheck.py b/rpmlint/checks/BinariesCheck.py new file mode 100644 index 0000000..8579256 --- /dev/null +++ b/rpmlint/checks/BinariesCheck.py @@ -0,0 +1,733 @@ +import concurrent.futures +import contextlib +from pathlib import Path +import re +import stat + +from rpmlint.arparser import ArParser +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.lddparser import LddParser +from rpmlint.objdumpparser import ObjdumpParser +from rpmlint.pkg import FakePkg, InstalledPkg +from rpmlint.readelfparser import ReadelfParser +from rpmlint.stringsparser import StringsParser + +KERNEL_MODULES_PATHS = ('/lib/modules/', '/usr/lib/modules/') +GLIBC_EMPTY_ARCHIVES = ('libanl', 'libdl', 'libpthread', 'librt', 'libutil') + + +class BinariesCheck(AbstractCheck): + """ + Checks for binary files in the package. + """ + validso_regex = re.compile(r'(\.so\.\d+(\.\d+)*|\d\.so)$') + soversion_regex = re.compile(r'.*?(-(?P<pkgversion>[0-9][.0-9]*))?\.so(\.(?P<soversion>[0-9][.0-9]*))?') + usr_lib_regex = re.compile(r'^/usr/lib(64)?/') + ldso_soname_regex = re.compile(r'^ld(-linux(-(ia|x86_)64))?\.so') + + numeric_dir_regex = re.compile(r'/usr(?:/share)/man/man./(.*)\.[0-9](?:\.gz|\.bz2)') + versioned_dir_regex = re.compile(r'[^.][0-9]') + so_regex = re.compile(r'/lib(64)?/[^/]+\.so(\.[0-9]+)*$') + bin_regex = re.compile(r'^(/usr(/X11R6)?)?/s?bin/') + la_file_regex = re.compile(r'\.la$') + invalid_dir_ref_regex = re.compile(r'/(home|tmp)(\W|$)') + usr_arch_share_regex = re.compile(r'/share/.*/(?:x86|i.86|x86_64|ppc|ppc64|s390|s390x|ia64|m68k|arm|aarch64|mips|riscv)') + python_module_regex = re.compile(r'.*\.(\w*(python|pypy)\w*(-\w+){4}|abi3)\.so') + # Starts with ELF or with a special qualifiers like setuid or setgid + # https://github.com/rpm-software-management/rpmlint/issues/1088 + elf_regex = re.compile(r'^(\w+ )?ELF ') + + lto_text_like_sections = {'.preinit_array', '.init_array', '.fini_array'} + # The following sections are part of the RX ABI and do correspond to .text, .data and .bss + lto_text_like_sections |= {'P', 'D_1', 'B_1'} + + # The list is taken from glibc: sysdeps/${arch}/stackinfo.h + default_executable_stack_archs = re.compile(r'alpha|arm.*|hppa|i.86|m68k|microblaze|mips|ppc|s390|s390x|sh|sparc|x86_64') + + rpath_origin = '$ORIGIN' + hpc_locations = ('/usr/lib/mpi/', '/usr/lib64/mpi/', '/usr/lib/hpc/') + + def __init__(self, config, output): + super().__init__(config, output) + self.checked_files = 0 + self.system_lib_paths = tuple(config.configuration['SystemLibPaths']) + self.pie_exec_regex_list = [] + for regex in config.configuration['PieExecutables']: + self.pie_exec_regex_list.append(re.compile(regex)) + self.usr_lib_exception_regex = re.compile(config.configuration['UsrLibBinaryException']) + + self.setgid_call_regex = self.create_regexp_call(r'set(?:res|e)?gid') + self.setuid_call_regex = self.create_regexp_call(r'set(?:res|e)?uid') + self.setgroups_call_regex = self.create_regexp_call(r'(?:ini|se)tgroups') + self.mktemp_call_regex = self.create_regexp_call('mktemp') + self.gethostbyname_call_regex = self.create_regexp_call(r'(gethostbyname|gethostbyname2|gethostbyaddr|gethostbyname_r|gethostbyname2_r|gethostbyaddr_r)') + + # register all check functions + self.check_functions = [self._check_lto_section, + self._check_no_text_in_archive, + self._check_missing_symtab_in_archive, + self._check_missing_debug_info_in_archive, + self._check_executable_stack, + self._check_shared_library, + self._check_dependency, + self._check_library_dependency_location, + self._check_security_functions, + self._check_rpath, + self._check_library_dependency, + self._check_forbidden_functions, + self._check_executable_shlib, + self._check_optflags, + self._check_hash_sections, + self._check_no_patchable_function_entries_in_archive] + + @staticmethod + def create_nonlibc_regexp_call(call): + r = r'(%s)\s?.*$' % call + return re.compile(r) + + @staticmethod + def create_regexp_call(call): + r = r'(%s(?:@GLIBC\S+)?)(?:\s|$)' % call + return re.compile(r) + + def _check_libtool_wrapper(self, pkg, fname, pkgfile): + """ + Print an error if the fname file contains a libtool wrapper shell + script. + """ + if 'shell script' in pkgfile.magic: + file_start = None + with contextlib.suppress(IOError), open(pkgfile.path, 'rb') as inputf: + file_start = inputf.read(2048) + if (file_start and b'This wrapper script should never ' + b'be moved out of the build directory' + in file_start): + self.output.add_info('E', pkg, 'libtool-wrapper-in-package', + fname) + + def _check_invalid_la_file(self, pkg, fname): + """ + Check if the fname is an .la file and contains a reference to the + invalid directories ('/tmp' or '/home'). + + If so then print a corresponding error with the matching line numbers. + """ + if self.la_file_regex.search(fname): + line = pkg.grep(self.invalid_dir_ref_regex, fname) + if line: + self.output.add_info('E', pkg, 'invalid-la-file', fname, + f'(line {line})') + + def _check_binary_in_noarch(self, pkg, bin_name): + """ + Print an error if the binary file bin_name is in the noarch package. + """ + if pkg.arch == 'noarch': + self.output.add_info('E', pkg, + 'arch-independent-package-contains-binary-or-object', + bin_name) + + def _check_binary_in_usr_share(self, pkg, bin_name): + """ + Print an error if binary file bin_name is installed in /usr/share. + + We suppose that the package is arch dependent. + """ + if bin_name.startswith('/usr/share/') and \ + not self.usr_arch_share_regex.search(bin_name): + self.output.add_info('E', pkg, 'arch-dependent-file-in-usr-share', + bin_name) + + def _check_binary_in_etc(self, pkg, bin_name): + """ + Print an error if binary file bin_name is installed in /etc directory. + + We suppose that the package is arch dependent. + """ + if bin_name.startswith('/etc/'): + self.output.add_info('E', pkg, 'binary-in-etc', bin_name) + + def _check_unstripped_binary(self, bin_name, pkg, pkgfile): + """ + Print a warning if the bin_name binary has unstripped debug symbols. + + We suppose that the package is arch dependent and bin_name is not + ocaml native, lua bytecode, .o or .static. + """ + if 'not stripped' in pkgfile.magic: + self.output.add_info('W', pkg, 'unstripped-binary-or-object', + bin_name) + + def _check_non_pie(self, pkg, bin_name): + """ + Check if the bin_name binary is built with PIE. + + Print an error message if it's not while PIE is forced in + configuration. Print a warning if it's not forced. + We suppose that the package is arch dependent and bin_name is binary + executable. + """ + if not self.is_shobj and not self.is_pie_exec: + if any(regex.fullmatch(bin_name) for regex in self.pie_exec_regex_list): + self.output.add_info('E', pkg, + 'non-position-independent-executable', + bin_name) + else: + self.output.add_info('W', pkg, + 'position-independent-executable-suggested', + bin_name) + + def _check_exec_in_library(self, pkg, has_lib, exec_files): + """ + Check if the library package has an executable file installed. + + Print an error for every such file. + """ + if has_lib: + for f in exec_files: + self.output.add_info('E', pkg, 'executable-in-library-package', f) + + def _check_non_versioned(self, pkg, has_lib, exec_files): + """ + Check if the library package contains library files in non-versioned + directories. + + Print an error for every such file. + """ + if has_lib: + for f in pkg.files: + res = self.numeric_dir_regex.search(f) + fn = res and res.group(1) or f + if f not in exec_files and not self.so_regex.search(f) and \ + not self.versioned_dir_regex.search(fn): + self.output.add_info('E', pkg, 'non-versioned-file-in-library-package', f) + + def _check_no_binary(self, pkg, has_binary, has_file_in_lib64): + """ + Check if the arch dependent package contains any binaries. + + Print an error if there is no binary and it's not noarch. + """ + if not has_binary and not has_file_in_lib64 and \ + pkg.arch != 'noarch': + self.output.add_info('E', pkg, 'no-binary') + + def _check_noarch_with_lib64(self, pkg, has_file_in_lib64): + """ + Print an error if we have a noarch package that contains a file + in /usr/lib64. + """ + if pkg.arch == 'noarch' and has_file_in_lib64: + self.output.add_info('E', pkg, 'noarch-with-lib64') + + def _check_only_non_binary_in_usrlib(self, pkg, has_usr_lib_file, has_binary_in_usr_lib): + """ + Check and print a warning if we have _only_ non-binary files in the + '/usr/lib'. + + Note: non-binaries allowed via UsrLibBinaryException config option + are considered binaries. + """ + if has_usr_lib_file and not has_binary_in_usr_lib: + self.output.add_info('W', pkg, 'only-non-binary-in-usr-lib') + + def _check_no_text_in_archive(self, pkg, pkgfile): + """ + For an archive, test if any .text sections is non-empty. + """ + if self.is_archive: + for comment in self.readelf_parser.comment_section_info.comments: + if comment.startswith('GHC '): + return + + # Starting with glibc 2.34, some static libraries were moved to libc + # and there are empty archives for backward compatibility. Skip these + # libraries. + stem = Path(pkgfile.name).stem + if stem in GLIBC_EMPTY_ARCHIVES or (stem.endswith('_p') and stem[:-2] in GLIBC_EMPTY_ARCHIVES): + return + + for elf_file in self.readelf_parser.section_info.elf_files: + for section in elf_file: + sn = section.name + if ((sn in self.lto_text_like_sections or + sn == '.fini_array' or sn.startswith('.text') or + sn.startswith('.data')) and + section.size > 0): + return + self.output.add_info('E', pkg, 'lto-no-text-in-archive', pkgfile.name) + return + + def _check_no_patchable_function_entries_in_archive(self, pkg, pkgfile): + """ + For static libraries, we should not ship __patchable_function_entries as it can + accidentally make a shared library or an executable live-patchable. + """ + if self.is_archive: + for elf_file in self.readelf_parser.section_info.elf_files: + for section in elf_file: + if section.name == '__patchable_function_entries': + self.output.add_info('E', pkg, 'patchable-function-entry-in-archive', pkgfile.name) + return + + def _check_missing_symtab_in_archive(self, pkg, pkgfile): + """ + FIXME Add test coverage. + """ + if self.is_archive: + for elf_file in self.readelf_parser.section_info.elf_files: + for section in elf_file: + if section.name == '.symtab': + return + + self.output.add_info('E', pkg, 'static-library-without-symtab', pkgfile.name) + + def _check_missing_debug_info_in_archive(self, pkg, pkgfile): + if self.is_archive: + for elf_file in self.readelf_parser.section_info.elf_files: + for section in elf_file: + if section.name.startswith('.debug_'): + return + self.output.add_info('E', pkg, 'static-library-without-debuginfo', pkgfile.name) + + # Check for LTO sections + def _check_lto_section(self, pkg, pkgfile): + for elf_file in self.readelf_parser.section_info.elf_files: + for section in elf_file: + if '.gnu.lto_.' in section.name: + self.output.add_info('E', pkg, 'lto-bytecode', pkgfile.name) + return + + def _check_executable_stack(self, pkg, pkgfile): + """ + Check if the stack is declared as executable which is usually an error. + """ + + # Skip architectures that have non-executable stack by default + if pkg.arch and not self.default_executable_stack_archs.fullmatch(pkg.arch): + return + + # Do not check kernel modules and archives + if not self.is_archive and not any(pkgfile.name.startswith(p) for p in KERNEL_MODULES_PATHS): + stack_headers = [h for h in self.readelf_parser.program_header_info.headers if h.name == 'GNU_STACK'] + if not stack_headers: + self.output.add_info('E', pkg, 'missing-PT_GNU_STACK-section', pkgfile.name) + elif 'E' in stack_headers[0].flags: + self.output.add_info('E', pkg, 'executable-stack', pkgfile.name) + + def _check_soname_symlink(self, pkg, shlib, soname): + """ + Check that we have a symlink with the soname in the package and it + points to the checked shared library. + + Print an error if the symlink is invalid or missing. + """ + path = Path(shlib) + symlink = path.parent / soname + try: + + link = pkg.files[str(symlink)].linkto + if link not in (shlib, path.name, ''): + self.output.add_info('E', pkg, 'invalid-ldconfig-symlink', shlib, link) + except KeyError: + # if we do not have a symlink, report an issue + if path.name.startswith('lib') or path.name.startswith('ld-'): + self.output.add_info('E', pkg, 'no-ldconfig-symlink', shlib) + + def _check_shared_library(self, pkg, pkgfile): + """ + Various checks for the shared library. + + 1) Print 'no-soname' warning it the library has no soname present. + 2) Print 'invalid-soname' error if the soname is not valid. + 3) Print 'shlib-policy-name-error' error when the library major + version is not present in the package name. + 4) Print 'shlib-with-non-pic-code' error if the library contains + object code that was compiled without -fPIC. + """ + if not self.readelf_parser.is_shlib: + return + + soname = self.readelf_parser.dynamic_section_info.soname + if not soname: + self.output.add_info('W', pkg, 'no-soname', pkgfile.name) + else: + if not self.validso_regex.search(soname): + self.output.add_info('E', pkg, 'invalid-soname', pkgfile.name, soname) + else: + self._check_soname_symlink(pkg, pkgfile.name, soname) + + # check if the major version of the library is in the package + # name (check only for lib* packages) + # for now skip all HPC libraries (#901) + if pkg.name.startswith('lib') and not pkgfile.name.startswith(self.hpc_locations): + # SLPP is defined here: https://en.opensuse.org/openSUSE:Shared_library_packaging_policy#Package_naming + # Example: + # SONAME = libgame2-1.9.so.10.0.0 + # expected package name: libgame2-1_9-10_0_0 + res = self.soversion_regex.search(soname) + if res: + parts = [x.replace('.', '_') for x in (res.group('pkgversion'), res.group('soversion')) if x] + soversion = '-'.join(parts) + pkgname = pkg.name + if '.' in pkgname: + pkgname = pkgname[:pkgname.rindex('.')] + if soversion and not pkgname.endswith(soversion): + self.output.add_info('E', pkg, 'shlib-policy-name-error', + f'SONAME: {soname} ({pkgfile.name}), expected package suffix: {soversion}') + + # check if the object code in the library is compiled with PIC + if self.readelf_parser.dynamic_section_info['TEXTREL']: + self.output.add_info('E', pkg, 'shlib-with-non-pic-code', pkgfile.name) + + def _check_dependency(self, pkg, pkgfile): + """ + FIXME Add test coverage. + """ + # Undefined symbol and unused direct dependency checks make sense only + # for installed packages. + # skip debuginfo: https://bugzilla.redhat.com/190599 + # + # following issues are errors for shared libs and warnings for executables + if not self.is_dynamically_linked: + return + + # Skip python packages + if self.python_module_regex.fullmatch(pkgfile.name): + return + + if not self.is_archive and not self.readelf_parser.is_debug: + info_type = 'E' if self.readelf_parser.is_shlib else 'W' + for symbol in self.ldd_parser.undefined_symbols: + self.output.add_info(info_type, pkg, 'undefined-non-weak-symbol', pkgfile.name, symbol) + for dependency in self.ldd_parser.unused_dependencies: + self.output.add_info(info_type, pkg, 'unused-direct-shlib-dependency', + pkgfile.name, dependency) + + def _check_library_dependency_location(self, pkg, pkgfile): + """ + FIXME Add test coverage. + """ + if not self.is_dynamically_linked: + return + + if not self.is_archive: + for dependency in self.ldd_parser.dependencies: + if dependency.startswith('/opt/'): + self.output.add_info('E', pkg, 'linked-against-opt-library', pkgfile.name, dependency) + break + + nonusr = ('/bin', '/lib', '/sbin') + if pkgfile.name.startswith(nonusr): + for dependency in self.ldd_parser.dependencies: + if dependency.startswith('/usr/'): + self.output.add_info('W', pkg, 'linked-against-usr-library', pkgfile.name, dependency) + break + + def _check_security_functions(self, pkg, pkgfile): + setgid = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.setgid_call_regex)) + setuid = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.setuid_call_regex)) + setgroups = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.setgroups_call_regex)) + mktemp = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.mktemp_call_regex)) + gethostbyname = any(self.readelf_parser.symbol_table_info.get_functions_for_regex(self.gethostbyname_call_regex)) + + if setgid and setuid and not setgroups: + is_uid = stat.S_ISUID & pkgfile.mode + self.output.add_info('W' if is_uid else 'E', pkg, 'missing-call-to-setgroups-before-setuid', pkgfile.name) + + if mktemp: + self.output.add_info('E', pkg, 'call-to-mktemp', pkgfile.name) + + if gethostbyname: + self.output.add_info('W', pkg, 'binary-or-shlib-calls-gethostbyname', pkgfile.name) + + def _check_rpath(self, pkg, pkgfile): + for runpaths in self.readelf_parser.dynamic_section_info.runpaths: + for runpath in runpaths.split(':'): + if self.rpath_origin in runpath: + runpath = runpath.replace(self.rpath_origin, str(Path(pkgfile.name).parent)) + runpath = str(Path(runpath).resolve()) + if not runpath.startswith(self.system_lib_paths) and not self.usr_lib_regex.search(runpath): + self.output.add_info('E', pkg, 'binary-or-shlib-defines-rpath', pkgfile.name, f'(RUNPATH: {runpaths})') + return + + def _check_library_dependency(self, pkg, pkgfile): + if (self.is_archive or + any(pkgfile.name.startswith(p) for p in KERNEL_MODULES_PATHS) or + self.python_module_regex.fullmatch(pkgfile.name)): + return + + dyn_section = self.readelf_parser.dynamic_section_info + if not len(dyn_section.needed) and not (dyn_section.soname and + self.ldso_soname_regex.search(dyn_section.soname)): + if not self.is_shobj: + self.output.add_info('E', pkg, 'statically-linked-binary', pkgfile.name) + else: + # linked against libc ? + if 'libc.' not in dyn_section.runpaths and \ + (not dyn_section.soname or + ('libc.' not in dyn_section.soname and + not self.ldso_soname_regex.search(dyn_section.soname))): + for lib in dyn_section.needed: + if 'libc.' in lib: + return + if not self.is_shobj: + self.output.add_info('W', pkg, 'program-not-linked-against-libc', pkgfile.name) + + def _check_forbidden_functions(self, pkg, pkgfile): + forbidden_functions = self.config.configuration['WarnOnFunction'] + if forbidden_functions: + for name, func in forbidden_functions.items(): + # precompile regexps + f_name = func['f_name'] + func['f_regex'] = self.create_nonlibc_regexp_call(f_name) + if 'good_param' in func and func['good_param']: + func['waiver_regex'] = re.compile(func['good_param']) + # register descriptions + self.output.error_details.update({name: func['description']}) + + forbidden_calls = [] + for r_name, func in forbidden_functions.items(): + if any(self.readelf_parser.symbol_table_info.get_functions_for_regex(func['f_regex'])): + forbidden_calls.append(r_name) + + if not forbidden_calls: + return + + strings_parser = StringsParser(pkgfile.path) + failed_reason = strings_parser.parsing_failed_reason + if failed_reason: + self.output.add_info('E', pkg, 'strings-failed', pkgfile.name, failed_reason) + return + + forbidden_functions_filtered = [] + + for fn in forbidden_calls: + f = forbidden_functions[fn] + if 'waiver_regex' not in f: + forbidden_functions_filtered.append(fn) + continue + + # See: https://github.com/PyCQA/flake8-bugbear/issues/269 + waiver = any(f['waiver_regex'].search(string) for string in strings_parser.strings) + if not waiver: + forbidden_functions_filtered.append(fn) + + for fn in forbidden_functions_filtered: + self.output.add_info('W', pkg, fn, pkgfile.name, forbidden_functions[fn]['f_name']) + + def _check_executable_shlib(self, pkg, pkgfile): + if not (pkgfile.mode & stat.S_IEXEC) and self.readelf_parser.is_shlib: + self.output.add_info('E', pkg, 'shared-library-not-executable', pkgfile.name) + + def _check_optflags(self, pkg, pkgfile): + if self.is_archive: + return + + mandatory_optflags = self.config.configuration['MandatoryOptflags'] + forbidden_optflags = self.config.configuration['ForbiddenOptflags'] + if not mandatory_optflags and not forbidden_optflags: + return + + for dwarf_unit in self.objdump_parser.compile_units: + tokens = dwarf_unit['producer'].split(' ') + missing = [mo for mo in mandatory_optflags if mo not in tokens] + forbidden = [f for f in forbidden_optflags if f in tokens] + if missing: + self.output.add_info('W', pkg, 'missing-mandatory-optflags', pkgfile.name, ' '.join(missing)) + if forbidden: + self.output.add_info('E', pkg, 'forbidden-optflags', pkgfile.name, ' '.join(forbidden)) + + def _check_hash_sections(self, pkg, pkgfile): + if not self.readelf_parser.is_shlib: + return + + for elf_file in self.readelf_parser.section_info.elf_files: + needle = {'.hash', '.gnu.hash'} + for section in elf_file: + if not needle: + break + if section.name in needle: + needle.remove(section.name) + + if '.hash' in needle: + self.output.add_info('E', pkg, 'missing-hash-section', pkgfile.name) + if '.gnu.hash' in needle: + self.output.add_info('W', pkg, 'missing-gnu-hash-section', pkgfile.name) + + def _is_standard_archive(self, pkg, pkgfile): + # skip Klee bytecode archives + if pkgfile.path.endswith('.bca'): + return False + + # return false for e.g. Rust or Go packages that are archives + # but files in the archive are not an ELF container + ar_parser = ArParser(pkgfile.path) + failed_reason = ar_parser.parsing_failed_reason + if failed_reason: + self.output.add_info('E', pkg, 'ar-failed', pkgfile.name, failed_reason) + return False + + needles = ('__.PKGDEF', '_go_.o', 'lib.rmeta') + return not any(needle for needle in needles if needle in ar_parser.objects) + + def _detect_attributes(self, magic): + self.is_exec = 'executable' in magic + self.is_shobj = 'shared object' in magic + self.is_archive = 'current ar archive' in magic + self.is_dynamically_linked = 'dynamically linked' in magic + self.is_pie_exec = 'pie executable' in magic + self.is_nonstandard_archive = False + + def run_elf_checks(self, pkg, pkgfile): + if self.is_archive and not self._is_standard_archive(pkg, pkgfile): + self.is_nonstandard_archive = True + return + + self.readelf_parser = ReadelfParser(pkgfile.path, pkgfile.name) + failed_reason = self.readelf_parser.parsing_failed_reason() + if failed_reason: + self.output.add_info('E', pkg, 'readelf-failed', pkgfile.name, failed_reason) + return + + if not self.is_archive: + if self.is_dynamically_linked: + is_installed_pkg = isinstance(pkg, (InstalledPkg, FakePkg)) + self.ldd_parser = LddParser(pkgfile.path, pkgfile.name, is_installed_pkg) + failed_reason = self.ldd_parser.parsing_failed_reason + if failed_reason: + self.output.add_info('E', pkg, 'ldd-failed', pkgfile.name, failed_reason) + return + + if (self.config.configuration['MandatoryOptflags'] or + self.config.configuration['ForbiddenOptflags']): + self.objdump_parser = ObjdumpParser(pkgfile.path, pkgfile.name) + failed_reason = self.objdump_parser.parsing_failed_reason + if failed_reason: + self.output.add_info('E', pkg, 'objdump-failed', pkgfile.name, failed_reason) + return + + # NOTE: the speed benefit of the ThreadPoolExecutor is limited due to + # Global Interpreter Lock (GIL). + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = [] + for fn in self.check_functions: + futures.append(executor.submit(fn, pkg, pkgfile)) + concurrent.futures.wait(futures) + for future in futures: + err = future.exception() + if err: + raise err + + def check_binary(self, pkg): + exec_files = [] + pkg_has_lib = False + pkg_has_binary = False + pkg_has_binary_in_usrlib = False + pkg_has_usrlib_file = False + pkg_has_file_in_lib64 = False + + # go through the all files, run files checks and collect data that are + # needed later + for fname, pkgfile in pkg.files.items(): + # Common tests first + self._check_libtool_wrapper(pkg, fname, pkgfile) + self._check_invalid_la_file(pkg, fname) + + # consider non-binary in /usr/lib/ that is allowed by + # UsrLibBinaryException config option as a "fake" binary and + # do not throw 'only-non-binary-in-usr-lib' warning then + if not stat.S_ISDIR(pkgfile.mode) and self.usr_lib_regex.search(fname): + pkg_has_usrlib_file = True + if not pkg_has_binary_in_usrlib and \ + self.usr_lib_exception_regex.search(fname): + # Fake that we have binaries there to avoid + # only-non-binary-in-usr-lib false positives + pkg_has_binary_in_usrlib = True + + # find out if we have a file in /usr/lib64/ directory (needed later + # for the package checks) + if fname.startswith(('/usr/lib64', '/lib64')): + pkg_has_file_in_lib64 = True + + # skip the rest of the tests for non-binaries + # binary files only from here on + is_ocaml_native = 'Objective caml native' in pkgfile.magic + is_lua_bytecode = 'Lua bytecode' in pkgfile.magic + # Look for ELF in the file magic to check if it's really a binary + # file + if not (self.elf_regex.match(pkgfile.magic) or + 'current ar archive' in pkgfile.magic or + is_ocaml_native or is_lua_bytecode): + continue + + self.checked_files += 1 + + # mark this package as a one that has binary file + pkg_has_binary = True + + # if there is a binary in /usr/lib then mark this package + # accordingly + if pkg_has_usrlib_file and not pkg_has_binary_in_usrlib and \ + self.usr_lib_regex.search(fname): + pkg_has_binary_in_usrlib = True + + self._check_binary_in_noarch(pkg, fname) + + # skip the rest of the tests for noarch packages + # arch dependent packages only from here on + if pkg.arch == 'noarch': + continue + + self._check_binary_in_usr_share(pkg, fname) + self._check_binary_in_etc(pkg, fname) + + # skip the rest of the tests for ocaml native, Lua bytecode, + # Go .go and .gox, .o and .static + if is_ocaml_native or is_lua_bytecode or fname.endswith('.o') or \ + fname.endswith('.static') or fname.endswith('.gox') or \ + fname.endswith('.go'): + continue + + self._check_unstripped_binary(fname, pkg, pkgfile) + + # Detect attributes of an ELF file + self._detect_attributes(pkgfile.magic) + + # run ELF checks + self.run_elf_checks(pkg, pkgfile) + + if self.is_nonstandard_archive: + continue + + # inspect binary file + is_shlib = self.readelf_parser.is_shlib + + if is_shlib: + pkg_has_lib = True + + # skip non-exec and non-SO + # executables and shared objects only from here on + if not self.is_exec and not self.is_shobj: + continue + + if self.is_shobj and not self.is_exec and '.so' not in fname and \ + self.bin_regex.search(fname): + # pkgfile.magic does not contain 'executable' for PIEs + self.is_exec = True + + if self.is_exec: + # add to the list of the all exec files + if self.bin_regex.search(fname): + exec_files.append(fname) + + self._check_non_pie(pkg, fname) + + # run checks for the whole package + # it uses data collected in the previous for-cycle + self._check_exec_in_library(pkg, pkg_has_lib, exec_files) + self._check_non_versioned(pkg, pkg_has_lib, exec_files) + self._check_no_binary(pkg, pkg_has_binary, pkg_has_file_in_lib64) + self._check_noarch_with_lib64(pkg, pkg_has_file_in_lib64) + self._check_only_non_binary_in_usrlib(pkg, pkg_has_usrlib_file, + pkg_has_binary_in_usrlib) diff --git a/rpmlint/checks/BuildRootAndDateCheck.py b/rpmlint/checks/BuildRootAndDateCheck.py new file mode 100644 index 0000000..c4807b3 --- /dev/null +++ b/rpmlint/checks/BuildRootAndDateCheck.py @@ -0,0 +1,39 @@ +import re +import stat +import time + +import rpm +from rpmlint.checks.AbstractCheck import AbstractFilesCheck + + +class BuildRootAndDateCheck(AbstractFilesCheck): + """ + Check that the file doesn't contain the current date or time. + And check the file does not contain build root reference. + + If so, it causes the package to rebuild when it's not needed. + """ + def __init__(self, config, output): + super().__init__(config, output, r'.*') + self.looksliketime = re.compile('(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])') + self.istoday = re.compile(time.strftime('%b %e %Y')) + self.prepare_regex(rpm.expandMacro('%buildroot')) + + def prepare_regex(self, buildroot): + for m in ('name', 'version', 'release', 'NAME', 'VERSION', 'RELEASE'): + buildroot = buildroot.replace('%%{%s}' % (m), r'[\w\!-\.]{1,20}') + self.build_root_re = re.compile(buildroot) + + def check_file(self, pkg, filename): + if filename.startswith('/usr/lib/debug') or pkg.is_source or \ + not stat.S_ISREG(pkg.files[filename].mode): + return + + data = pkg.read_with_mmap(filename) + if self.istoday.search(data): + if self.looksliketime.search(data): + self.output.add_info('E', pkg, 'file-contains-date-and-time', filename) + else: + self.output.add_info('E', pkg, 'file-contains-current-date', filename) + if self.build_root_re.search(data): + self.output.add_info('E', pkg, 'file-contains-buildroot', filename) diff --git a/rpmlint/checks/ConfigFilesCheck.py b/rpmlint/checks/ConfigFilesCheck.py new file mode 100644 index 0000000..f169871 --- /dev/null +++ b/rpmlint/checks/ConfigFilesCheck.py @@ -0,0 +1,32 @@ +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class ConfigFilesCheck(AbstractCheck): + """ + Check that configuration files are in a proper location and marked as + 'noreplace'. + """ + def check_binary(self, pkg): + for filename in pkg.config_files: + self._check_non_confdir_files(pkg, filename) + self._check_noreplace_files(pkg, filename) + + def _check_non_confdir_files(self, pkg, fn): + """ + Check if the configuration file is in /etc or /var directory. + + Print a warning if it's not. + """ + if not fn.startswith('/etc/') and not fn.startswith('/var/'): + self.output.add_info('W', pkg, + 'non-etc-or-var-file-marked-as-conffile', fn) + + def _check_noreplace_files(self, pkg, fn): + """ + Check if the configuration file has 'noreplace' flag. + + Print a warning if there is no 'noreplace' tag. + """ + if fn not in pkg.noreplace_files: + self.output.add_info('W', pkg, + 'conffile-without-noreplace-flag', fn) diff --git a/rpmlint/checks/DBusPolicyCheck.py b/rpmlint/checks/DBusPolicyCheck.py new file mode 100644 index 0000000..9201774 --- /dev/null +++ b/rpmlint/checks/DBusPolicyCheck.py @@ -0,0 +1,56 @@ +from xml.dom.minidom import parse + +from rpmlint.checks.AbstractCheck import AbstractCheck + +DBUS_DIRECTORIES = ('/etc/dbus-1/system.d/', '/usr/share/dbus-1/system.d/') + + +class DBusPolicyCheck(AbstractCheck): + def check(self, pkg): + if pkg.is_source: + return + + for f in pkg.files: + if f in pkg.ghost_files: + continue + + # catch xml exceptions + try: + if any(f.startswith(d) for d in DBUS_DIRECTORIES): + send_policy_seen = False + lf = pkg.dir_name() + f + xml = parse(lf) + for policy in xml.getElementsByTagName('policy'): + send_policy_seen |= self._check_allow_policy_element(pkg, f, policy) + self._check_deny_policy_element(pkg, f, policy) + + if not send_policy_seen: + self.output.add_info('E', pkg, 'dbus-policy-missing-allow', f) + + except Exception as e: + self.output.add_info('E', pkg, 'dbus-parsing-exception', 'raised an exception: ' + str(e), f) + continue + + def _check_allow_policy_element(self, pkg, f, policy): + send_policy_seen = False + for allow in policy.getElementsByTagName('allow'): + if ((allow.hasAttribute('send_interface') or + allow.hasAttribute('send_member') or + allow.hasAttribute('send_path')) and not + allow.hasAttribute('send_destination')): + send_policy_seen = True + self.output.add_info('E', pkg, 'dbus-policy-allow-without-destination', allow.toxml(), f) + elif allow.hasAttribute('send_destination'): + send_policy_seen = True + + if (allow.hasAttribute('receive_sender') or + allow.hasAttribute('receive_interface')): + self.output.add_info('W', pkg, 'dbus-policy-allow-receive', allow.toxml(), f) + + return send_policy_seen + + def _check_deny_policy_element(self, pkg, f, policy): + for deny in policy.getElementsByTagName('deny'): + if (deny.hasAttribute('send_interface') and not + deny.hasAttribute('send_destination')): + self.output.add_info('E', pkg, 'dbus-policy-deny-without-destination', deny.toxml(), f) diff --git a/rpmlint/checks/DocCheck.py b/rpmlint/checks/DocCheck.py new file mode 100644 index 0000000..4e77e7a --- /dev/null +++ b/rpmlint/checks/DocCheck.py @@ -0,0 +1,122 @@ +from pathlib import Path +import stat + +import rpm +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.helpers import byte_to_string + + +class DocCheck(AbstractCheck): + """ + Package documentation checks. + """ + + def check_binary(self, pkg): + if not pkg.doc_files: + return + + self._check_executable_docs(pkg) + self._check_doc_file_dependencies(pkg) + self._check_unwanted_files(pkg) + + if not self._ignore_pkg(pkg.name): + self._check_huge_docs(pkg) + + @staticmethod + def _ignore_pkg(name): + if name.startswith('bundle-') or '-devel' in name or '-doc' in name: + return True + return False + + def _check_executable_docs(self, pkg): + """ + Check if the documentation in the package is executable and print an + error if it is. + """ + for f in pkg.doc_files: + mode = pkg.files[f].mode + if not stat.S_ISREG(mode) or not mode & 0o111: + continue + path = Path(f) + extensions = ['.txt', '.gif', '.jpg', '.html', '.pdf', '.ps', '.pdf.gz', '.ps.gz'] + if path.suffix in extensions: + self.output.add_info('E', pkg, 'executable-docs', f) + + for name in ['README', 'NEWS', 'COPYING', 'AUTHORS', 'LICENCE', 'LICENSE']: + if path.name.lower() == name.lower(): + self.output.add_info('E', pkg, 'executable-docs', f) + + def _check_doc_file_dependencies(self, pkg): + """ + Check if docfiles create additional dependencies in the package and + print a warning if so. + """ + files = pkg.files + + reqs = {} + for fname, pkgfile in files.items(): + reqs[fname] = [x[0] for x in pkgfile.requires] + + core_reqs = {} # dependencies of non-doc files + doc_reqs = {} # dependencies of doc files + + 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 + core_reqs[dep.N()] = [] + + # register things which are provided by the package + for i in pkg.header[rpm.RPMTAG_PROVIDES]: + core_reqs[byte_to_string(i)] = [] + for i in files: + core_reqs[i] = [] + + for i in files: + if not reqs[i]: + continue # skip empty dependencies + if i in pkg.doc_files: + target = doc_reqs + else: + target = core_reqs + + for r in reqs[i]: + if r not in target: + target[r] = [] + target[r].append(i) + + # go through the calculated requirements of the %doc files + for (dep, req_files) in doc_reqs.items(): + if dep not in core_reqs: + for f in req_files: + self.output.add_info('W', pkg, 'doc-file-dependency', f, dep) + + def _check_unwanted_files(self, pkg): + """ + Check if docfiles contain unwanted files. + + Now it checks the presence of the INSTALL file that is often not + relevant for the built package. + """ + for docfile in pkg.doc_files: + if docfile.endswith('/INSTALL'): + self.output.add_info('W', pkg, 'install-file-in-docs', docfile) + + def _check_huge_docs(self, pkg): + """ + Check the size of the documentation in the package and print a warning + if it's more than half of the size of the package. + """ + files = pkg.files + complete_size = 0 + for _, pkgfile in files.items(): + if stat.S_ISREG(pkgfile.mode): + complete_size += pkgfile.size + + doc_size = 0 + for f in pkg.doc_files: + if stat.S_ISREG(files[f].mode): + doc_size += files[f].size + + if doc_size * 2 >= complete_size and doc_size > 100 * 1024: + self.output.add_info('W', pkg, 'package-with-huge-docs', '%d%%' % (doc_size * 100 / complete_size)) diff --git a/rpmlint/checks/DuplicatesCheck.py b/rpmlint/checks/DuplicatesCheck.py new file mode 100644 index 0000000..f7c8db8 --- /dev/null +++ b/rpmlint/checks/DuplicatesCheck.py @@ -0,0 +1,121 @@ +import stat + +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class DuplicatesCheck(AbstractCheck): + """ + Search for the duplicated files in the package. + + It uses the following structures: + + md5s + - key: md5 hash of the file + - values: files with this hash + + sizes + - key: md5 hash of the file + - values: size of the file + """ + + DUPLICATES_DISPLAY_LIMIT = 5 + + def __init__(self, config, output): + super().__init__(config, output) + self.min_size = self.config.configuration.get('DuplicatesMinSize', 0) + + def check(self, pkg): + if pkg.is_source: + return + + md5s = {} + sizes = {} + hardlinks = {} + total_dup_size = 0 + + for fname, pkgfile in pkg.files.items(): + if fname in pkg.ghost_files or not stat.S_ISREG(pkgfile.mode): + continue + + # Skip small files + if pkgfile.size <= self.min_size: + continue + + # fillup md5s and sizes dicts + md5s.setdefault(pkgfile.md5, set()).add(pkgfile) + sizes[pkgfile.md5] = pkgfile.size + key = (pkgfile.rdev, pkgfile.inode) + if key not in hardlinks: + hardlinks[key] = 0 + hardlinks[key] += 1 + + # process duplicates + for md5_hash in md5s: + # obtain the list of the files with the same md5 hash + duplicates = md5s[md5_hash] + + # continue, there is no duplicate + if len(duplicates) == 1: + continue + + duplicates = sorted(duplicates, key=lambda x: x.name) + first = duplicates.pop() + first_is_config = False + if first.name in pkg.config_files: + first_is_config = True + + prefix = self._get_prefix(first) + + # 1 (first) + number of others - number of hard links + # (keeps track of how many directories have entries for this file) + # diff is a number of files that are duplicates but not hard-links + key = (first.rdev, first.inode) + diff = 1 + len(duplicates) - hardlinks[key] + + if diff <= 0: + # now we have just hard-links in duplicates + for duplicate in duplicates: + if prefix != self._get_prefix(duplicate): + self.output.add_info('E', pkg, + 'hardlink-across-partition', + first.name, duplicate.name) + if first_is_config and duplicate.name in pkg.config_files: + self.output.add_info('E', pkg, + 'hardlink-across-config-files', + first.name, duplicate.name) + continue + + # now we know that there are some duplicates that are not links + for duplicate in duplicates: + if prefix != self._get_prefix(duplicate): + # if the duplicate is in a different prefix, we can ignore + # it since it can't be linked anyway + diff = diff - 1 + + # if there is still a positive diff (i.e. there is a duplicate that + # is not a link and wasn't ignored by the previous step), + # report a warning + if sizes[md5_hash] and diff > 0: + display_duplicates = duplicates[:self.DUPLICATES_DISPLAY_LIMIT] + other_duplicates = len(duplicates[self.DUPLICATES_DISPLAY_LIMIT:]) + + description = ':'.join([x.name for x in display_duplicates]) + if other_duplicates > 0: + description += f':(and {other_duplicates} more)' + self.output.add_info('W', pkg, 'files-duplicate', first.name, + description) + total_dup_size += sizes[md5_hash] * diff + + # check the overall size of the duplicates and print an error if it's + # too much + if total_dup_size > 100000: + self.output.add_info('E', pkg, 'files-duplicated-waste', + total_dup_size) + + @staticmethod + def _get_prefix(pkgfile): + """Return first two directories in the given path.""" + pathlist = pkgfile.name.split('/') + if len(pathlist) == 3: + return '/'.join(pathlist[0:2]) + return '/'.join(pathlist[0:3]) diff --git a/rpmlint/checks/ErlangCheck.py b/rpmlint/checks/ErlangCheck.py new file mode 100644 index 0000000..2a666f1 --- /dev/null +++ b/rpmlint/checks/ErlangCheck.py @@ -0,0 +1,33 @@ +import re + +from pybeam import BeamFile +from rpm import expandMacro +from rpmlint.checks.AbstractCheck import AbstractFilesCheck +from rpmlint.helpers import byte_to_string + + +class ErlangCheck(AbstractFilesCheck): + def __init__(self, config, output): + super().__init__(config, output, r'.*?\.beam$') + build_dir = expandMacro('%_builddir') + self.source_re = re.compile(build_dir) + + def check_file(self, pkg, filename): + try: + beam = BeamFile(pkg.files[filename].path) + + if beam.compileinfo is None: + self.output.add_info('W', pkg, 'beam-compile-info-missed', filename) + return + + compile_state = byte_to_string(beam.compileinfo['source']) + if 'debug_info' not in beam.compileinfo['options']: + self.output.add_info('E', pkg, 'beam-compiled-without-debuginfo', filename) + + # This can't be an error as builddir can be user specific and vary between users + # it could be error in OBS where all the builds are done by user abuild, not in + # general. + if not self.source_re.match(compile_state): + self.output.add_info('W', pkg, 'beam-was-not-recompiled', filename, compile_state) + except Exception: + self.output.add_info('E', pkg, 'pybeam-failed', filename) diff --git a/rpmlint/checks/FHSCheck.py b/rpmlint/checks/FHSCheck.py new file mode 100644 index 0000000..513f0b3 --- /dev/null +++ b/rpmlint/checks/FHSCheck.py @@ -0,0 +1,89 @@ +import re + +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class FHSCheck(AbstractCheck): + """ + Validate that binary files are packaged according to FHS. + + We follow FHS 3.0 specification that can be found at + http://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.pdf + + FHS_usr_subdirs lists allowed directories in /usr (FHS chapter 4.2 and 4.3) + FHS_var_subdirs lists allowed directories in /var (FHS chapter 5.2 and 5.3) + """ + usr_regex = re.compile('^/usr/([^/]+)') + FHS_usr_subdirs = ('bin', 'lib', 'local', 'sbin', 'share', 'games', 'include', + 'libexec', 'lib64', 'src', 'spool', 'tmp') + + var_regex = re.compile('^/var/([^/]+)') + FHS_var_subdirs = ('cache', 'lib', 'local', 'lock', 'log', 'opt', 'run', + 'spool', 'tmp', 'account', 'crash', 'games', 'mail', + 'yp') + + def __init__(self, config, output): + super().__init__(config, output) + self.output.error_details.update(fhs_details_dict) + + def check_binary(self, pkg): + var_list = [] + usr_list = [] + + for fname in pkg.files: + usr_path = self.usr_regex.search(fname) + if usr_path: + # Run tests for /usr directory + usr_file = usr_path.group(1) + self._check_usr_standard_dir(usr_file, pkg, usr_list) + continue + + var_path = self.var_regex.search(fname) + if var_path: + # Run tests for /var directory + var_file = var_path.group(1) + self._check_var_standard_dir(var_file, pkg, var_list) + + def _check_usr_standard_dir(self, usr_file, pkg, usr_list): + """ + Check if the file is in valid subdirectory of /usr. + + FHS 3.0 says: "Large software packages must not use a direct + subdirectory under the /usr hierarchy." Check if this package contains + a directory in /usr that is not mentioned in FHS (FHS_usr_subdirs). + + Refer to http://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04.html for + details. + """ + if usr_file not in self.FHS_usr_subdirs and usr_file not in usr_list: + usr_list.append(usr_file) + self.output.add_info('W', pkg, 'non-standard-dir-in-usr', usr_file) + + def _check_var_standard_dir(self, var_file, pkg, var_list): + """ + Check if the file is in valid subdirectory of /var. + + FHS 3.0 says: "Applications must generally not add directories to the + top level of /var. Such directories should only be added if they have + some system-wide implication, and in consultation with the FHS + mailing list." + + Refer to http://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch05.htm + for details. + """ + if var_file not in self.FHS_var_subdirs and var_file not in var_list: + var_list.append(var_file) + self.output.add_info('W', pkg, 'non-standard-dir-in-var', var_file) + + +fhs_details_dict = { +'non-standard-dir-in-usr': +"""Your package is creating a non-standard subdirectory in /usr. The standard +directories are: +%s.""" % ', '.join(FHSCheck.FHS_usr_subdirs), + +'non-standard-dir-in-var': +"""Your package is creating a non-standard subdirectory in /var. The standard +directories are: +%s.""" % ', '.join(FHSCheck.FHS_var_subdirs), +} diff --git a/rpmlint/checks/FilesCheck.py b/rpmlint/checks/FilesCheck.py new file mode 100644 index 0000000..dec7561 --- /dev/null +++ b/rpmlint/checks/FilesCheck.py @@ -0,0 +1,1346 @@ +############################################################################# +# File : FilesCheck.py +# Package : rpmlint +# Author : Frederic Lepied +# Created on : Mon Oct 4 19:32:49 1999 +# Purpose : test various aspects on files: locations, owner, groups, +# permission, setuid, setgid... +############################################################################# + +from datetime import datetime +import os +from pathlib import Path +import re +import stat + +import rpm +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.helpers import byte_to_string +from rpmlint.pkg import is_utf8, is_utf8_bytestr + +# must be kept in sync with the filesystem package +STANDARD_DIRS = ( + '/', + '/bin', + '/boot', + '/etc', + '/etc/X11', + '/etc/opt', + '/etc/profile.d', + '/etc/skel', + '/etc/xinetd.d', + '/home', + '/lib', + '/lib/modules', + '/lib64', + '/media', + '/mnt', + '/mnt/cdrom', + '/mnt/disk', + '/mnt/floppy', + '/opt', + '/proc', + '/root', + '/run', + '/sbin', + '/selinux', + '/srv', + '/sys', + '/tmp', + '/usr', + '/usr/X11R6', + '/usr/X11R6/bin', + '/usr/X11R6/doc', + '/usr/X11R6/include', + '/usr/X11R6/lib', + '/usr/X11R6/lib64', + '/usr/X11R6/man', + '/usr/X11R6/man/man1', + '/usr/X11R6/man/man2', + '/usr/X11R6/man/man3', + '/usr/X11R6/man/man4', + '/usr/X11R6/man/man5', + '/usr/X11R6/man/man6', + '/usr/X11R6/man/man7', + '/usr/X11R6/man/man8', + '/usr/X11R6/man/man9', + '/usr/X11R6/man/mann', + '/usr/bin', + '/usr/bin/X11', + '/usr/etc', + '/usr/games', + '/usr/include', + '/usr/lib', + '/usr/lib/X11', + '/usr/lib/games', + '/usr/lib/gcc-lib', + '/usr/lib/menu', + '/usr/lib64', + '/usr/lib64/gcc-lib', + '/usr/local', + '/usr/local/bin', + '/usr/local/doc', + '/usr/local/etc', + '/usr/local/games', + '/usr/local/info', + '/usr/local/lib', + '/usr/local/lib64', + '/usr/local/man', + '/usr/local/man/man1', + '/usr/local/man/man2', + '/usr/local/man/man3', + '/usr/local/man/man4', + '/usr/local/man/man5', + '/usr/local/man/man6', + '/usr/local/man/man7', + '/usr/local/man/man8', + '/usr/local/man/man9', + '/usr/local/man/mann', + '/usr/local/sbin', + '/usr/local/share', + '/usr/local/share/man', + '/usr/local/share/man/man1', + '/usr/local/share/man/man2', + '/usr/local/share/man/man3', + '/usr/local/share/man/man4', + '/usr/local/share/man/man5', + '/usr/local/share/man/man6', + '/usr/local/share/man/man7', + '/usr/local/share/man/man8', + '/usr/local/share/man/man9', + '/usr/local/share/man/mann', + '/usr/local/src', + '/usr/sbin', + '/usr/share', + '/usr/share/dict', + '/usr/share/doc', + '/usr/share/icons', + '/usr/share/info', + '/usr/share/man', + '/usr/share/man/man1', + '/usr/share/man/man2', + '/usr/share/man/man3', + '/usr/share/man/man4', + '/usr/share/man/man5', + '/usr/share/man/man6', + '/usr/share/man/man7', + '/usr/share/man/man8', + '/usr/share/man/man9', + '/usr/share/man/mann', + '/usr/share/misc', + '/usr/src', + '/usr/tmp', + '/var', + '/var/cache', + '/var/db', + '/var/lib', + '/var/lib/games', + '/var/lib/misc', + '/var/lib/rpm', + '/var/local', + '/var/log', + '/var/mail', + '/var/nis', + '/var/opt', + '/var/preserve', + '/var/spool', + '/var/spool/mail', + '/var/tmp', +) + +compressions = r'\.(gz|z|Z|zip|bz2|lzma|xz|zst)' +sub_bin_regex = re.compile(r'^(/usr)?/s?bin/\S+/') +backup_regex = re.compile(r'(~|\#[^/]+\#|((\.orig|\.rej)(' + compressions + ')?))$') +compr_regex = re.compile(compressions + r'$') +absolute_regex = re.compile(r'^/([^/]+)') +absolute2_regex = re.compile(r'^/?([^/]+)') +points_regex = re.compile(r'^\.\./(.*)') +doc_regex = re.compile(r'^/usr(/share|/X11R6)?/(doc|man|info)/|^/usr/share/gnome/help') +bin_regex = re.compile(r'^/(?:usr/(?:s?bin|games)|s?bin)/(.*)') +includefile_regex = re.compile(r'\.(c|h)(pp|xx)?$', re.IGNORECASE) +develfile_regex = re.compile(r'\.(a|cmxa?|mli?|gir)$') +buildconfigfile_regex = re.compile(r'(\.pc|/bin/.+-config)$') +# room for improvement with catching more -R, but also for false positives... +buildconfig_rpath_regex = re.compile(r'(?:-rpath|Wl,-R)\b') +sofile_regex = re.compile(r'/lib(64)?/(.+/)?lib[^/]+\.so$') +devel_regex = re.compile(r'(.*)-(debug(info|source)?|devel|headers|source|static|prof)$') +debuginfo_package_regex = re.compile(r'-debug(info)?$') +debugsource_package_regex = re.compile(r'-debugsource$') +lib_regex = re.compile(r'/lib(?:64)?/lib[A-Za-z0-9](?:(?:|[\w\-\.]*[A-Za-z0-9])\.so\.[\w+\.]+|\w*-\d(?:|[\w\-\.]*[A-Za-z0-9])\.so)$') # see commit log for detail +ldconfig_regex = re.compile(r'^[^#]*ldconfig', re.MULTILINE) +depmod_regex = re.compile(r'^[^#]*depmod', re.MULTILINE) +install_info_regex = re.compile(r'^[^#]*install-info', re.MULTILINE) +perl_temp_file_regex = re.compile(r'.*perl.*/(\.packlist|perllocal\.pod)$') +scm_regex = re.compile( + r'/(?:RCS|CVS)/[^/]+$|/\.(?:bzr|cvs|git|hg|svn)ignore$|' + r',v$|/\.hgtags$|/\.(?:bzr|git|hg|svn)/|/(?:\.arch-ids|{arch})/') +games_path_regex = re.compile(r'^/usr(/lib(64)?)?/games/') +logrotate_regex = re.compile(r'^/etc/logrotate\.d/(.*)') +kernel_modules_regex = re.compile(r'^(?:/usr)/lib/modules/([0-9]+\.[0-9]+\.[0-9]+[^/]*?)/') +kernel_package_regex = re.compile(r'^kernel(22)?(-)?(smp|enterprise|bigmem|secure|BOOT|i686-up-4GB|p3-smp-64GB)?') +normal_zero_length_regex = re.compile(r'^/etc/security/console\.apps/' + r'|/\.nosearch$' + r'|/__init__\.py$' + r'|/py\.typed$' # https://www.python.org/dev/peps/pep-0561/#packaging-type-information + r'|\.dist-info/REQUESTED$' # https://www.python.org/dev/peps/pep-0376/#requested + r'|/gem\.build_complete$') +perl_regex = re.compile(r'^/usr/lib/perl5/(?:vendor_perl/)?([0-9]+\.[0-9]+)\.([0-9]+)/') +python_regex = re.compile(r'^/usr/lib(?:64)?/python([.0-9]+)/') +python_bytecode_regex_pep3147 = re.compile(r'^(.*)/__pycache__/(.*?)\.([^.]+)(\.opt-[12])?\.py[oc]$') +python_bytecode_regex = re.compile(r'^(.*)(\.py[oc])$') +log_regex = re.compile(r'^/var/log/[^/]+$') +lib_path_regex = re.compile(r'^(/usr(/X11R6)?)?/lib(64)?') +lib_package_regex = re.compile(r'^(lib|.+-libs)') +hidden_file_regex = re.compile(r'/\.[^/]*$') +manifest_perl_regex = re.compile(r'^/usr/share/doc/perl-.*/MANIFEST(\.SKIP)?$') +shebang_regex = re.compile(br'^#!\s*(\S+)(.*?)$', re.M) +interpreter_regex = re.compile(r'^/(?:usr/)?(?:s?bin|games|libexec(?:/.+)?|(?:lib(?:64)?|share)/.+)/([^/]+)$') +script_regex = re.compile(r'^/((usr/)?s?bin|etc/(rc\.d/init\.d|X11/xinit\.d|cron\.(hourly|daily|monthly|weekly)))/') +sourced_script_regex = re.compile(r'^/etc/(bash_completion\.d|profile\.d)/') +filesys_packages = ['filesystem'] # TODO: make configurable? +quotes_regex = re.compile(r'[\'"]+') +start_certificate_regex = re.compile(r'^-----BEGIN CERTIFICATE-----$') +start_private_key_regex = re.compile(r'^----BEGIN PRIVATE KEY-----$') +non_readable_regexs = (re.compile(r'^/var/log/'), + re.compile(r'^/etc/(g?shadow-?|securetty)$')) + +man_base_regex = re.compile(r'^/usr(?:/share)?/man(?:/overrides)?/man(?P<category>[^/]+)/(?P<filename>((?P<binary>[^.]+)\..+))') + +fsf_license_regex = re.compile(br'(GNU((\s+(Library|Lesser|Affero))?(\s+General)?\s+Public|\s+Free\s+Documentation)\s+Licen[cs]e|(GP|FD)L)', re.IGNORECASE) +fsf_wrong_address_regex = re.compile(br'(675\s+Mass\s+Ave|59\s+Temple\s+Place|02139)', re.IGNORECASE) + +scalable_icon_regex = re.compile(r'^/usr(?:/local)?/share/icons/.*/scalable/') +tcl_regex = re.compile(r'^/usr/lib(64)?/([^/]+/)?pkgIndex\.tcl') + +printable_extended_ascii = b'\n\r\t\f\b' +printable_extended_ascii += bytes(range(32, 256)) + + +# See Python sources for a full list of the values here. +# https://github.com/python/cpython/blob/master/Lib/importlib/_bootstrap_external.py +# https://github.com/python/cpython/blob/2.7/Python/import.c +# https://github.com/python/cpython/commit/93602e3af70d3b9f98ae2da654b16b3382b68d50 +_python_magic_values = { + '2.2': [60717], + '2.3': [62011], + '2.4': [62061], + '2.5': [62131], + '2.6': [62161], + '2.7': [62211], + '3.0': [3130], + '3.1': [3150], + '3.2': [3180], + '3.3': [3230], + '3.4': [3310], + '3.5': [3350, 3351], # 3350 for < 3.5.2 + '3.6': [3379], + '3.7': [3390, 3391, 3392, 3393, 3394], +} + + +def get_expected_pyc_magic(path, python_default_version): + """ + .pyc/.pyo files embed a 4-byte magic value identifying which version of + the python bytecode ABI they are for. Given a path to a .pyc/.pyo file, + return a (magic ABI values, python version) tuple. For example, + '/usr/lib/python3.1/foo.pyc' should return (3151, '3.1'). + The first value will be None if the python version was not resolved + from the given pathname and the PythonDefaultVersion configuration + variable is not set, or if we don't know the magic ABI values for the + python version (no matter from which source the version came from). + The second value will be None if a python version could not be resolved + from the given pathname. + """ + + ver_from_path = None + m = python_regex.search(path) + if m: + ver_from_path = m.group(1) + + expected_version = ver_from_path or python_default_version + expected_magic_values = _python_magic_values.get(expected_version) + + if not expected_magic_values: + return (None, ver_from_path) + + # In Python 2, if Py_UnicodeFlag is set, Python's import code uses a value + # one higher, but this is off by default. In Python 3.0 and 3.1 (but no + # longer in 3.2), it always uses the value one higher: + if expected_version[:3] in ('3.0', '3.1'): + expected_magic_values = [x + 1 for x in expected_magic_values] + + return (expected_magic_values, ver_from_path) + + +def py_demarshal_long(b): + """ + Counterpart to Python's PyMarshal_ReadLongFromFile, operating on the + bytes in a string. + """ + if isinstance(b, str): + b = map(ord, b) + return (b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24)) + + +def pyc_magic_from_chunk(chunk): + """From given chunk (beginning of the file), return Python magic number""" + return py_demarshal_long(chunk[:4]) & 0xffff + + +def pyc_mtime_from_chunk(chunk): + """From given chunk (beginning of the file), return mtime or None + + From Python 3.7, mtime is not always present. + + See https://www.python.org/dev/peps/pep-0552/#specification + """ + magic = pyc_magic_from_chunk(chunk) + second = py_demarshal_long(chunk[4:8]) + if magic >= _python_magic_values['3.7'][0]: + if second == 0: + return py_demarshal_long(chunk[8:12]) + return None # No mtime saved, TODO check hashes instead + return second + + +def python_bytecode_to_script(path): + """ + Given a python bytecode path, give the path of the .py file + (or None if not python bytecode). + """ + + res = python_bytecode_regex_pep3147.search(path) + if res: + return res.group(1) + '/' + res.group(2) + '.py' + + res = python_bytecode_regex.search(path) + if res: + return res.group(1) + '.py' + + return None + + +def script_interpreter(chunk): + res = shebang_regex.search(chunk) if chunk else None + return (byte_to_string(res.group(1)), byte_to_string(res.group(2)).strip()) \ + if res and res.start() == 0 else (None, '') + + +class FilesCheck(AbstractCheck): + man_regex = re.compile(r'/man(?:\d[px]?|n)/') + info_regex = re.compile(r'(/usr/share|/usr)/info/') + + def __init__(self, config, output): + super().__init__(config, output) + self.use_debugsource = self.config.configuration['UseDebugSource'] + self.games_group_regex = re.compile(self.config.configuration['RpmGamesGroup']) + self.dangling_exceptions = self.config.configuration['DanglingSymlinkExceptions'] + for item in self.dangling_exceptions.values(): + item['path'] = re.compile(item['path']) + self.module_rpms_ok = self.config.configuration['KernelModuleRPMsOK'] + self.python_default_version = self.config.configuration['PythonDefaultVersion'] + self.perl_version_trick = self.config.configuration['PerlVersionTrick'] + self.skipdocs_regex = re.compile(self.config.configuration['SkipDocsRegexp'], re.IGNORECASE) + self.meta_package_regex = re.compile(self.config.configuration['MetaPackageRegexp']) + self.use_relative_symlinks = self.config.configuration['UseRelativeSymlinks'] + self.standard_groups = self.config.configuration['StandardGroups'] + self.standard_users = self.config.configuration['StandardUsers'] + self.disallowed_dirs = self.config.configuration['DisallowedDirs'] + self.compress_ext = self.config.configuration['CompressExtension'] + self.output.error_details.update({ + 'non-standard-uid': + """A file in this package is owned by a non standard user. + Standard users are: + %s.""" % ', '.join(self.standard_users), + + 'non-standard-gid': + """A file in this package is owned by a non standard group. + Standard groups are: + %s.""" % ', '.join(self.standard_groups), + + 'manpage-not-compressed': + """This manual page is not compressed with the %s compression method + (does not have the %s extension). If the compression does not happen + automatically when the package is rebuilt, make sure that you have the + appropriate rpm helper and/or config packages for your target distribution + installed and try rebuilding again; if it still does not happen automatically, + you can compress this file in the %%install section of the spec file.""" + % (self.compress_ext, self.compress_ext), + + 'infopage-not-compressed': + """This info page is not compressed with the %s compression method + (does not have the %s extension). If the compression does not happen + automatically when the package is rebuilt, make sure that you have the + appropriate rpm helper and/or config packages for your target distribution + installed and try rebuilding again; if it still does not happen automatically, + you can compress this file in the %%install section of the spec file.""" + % (self.compress_ext, self.compress_ext), + }) + for i in self.disallowed_dirs: + self.output.error_details.update({'dir-or-file-in-%s' % '-'.join(i.split('/')[1:]): + """A file in the package is located in %s. It's not permitted + for packages to install files in this directory.""" % i}) + + def peek(self, filename, pkg, length=1024): + """ + Peek into a file, return a chunk from its beginning and a flag if it + seems to be a text file. + """ + chunk = None + try: + with open(filename, 'rb') as fobj: + chunk = fobj.read(length) + except OSError as e: # eg. https://bugzilla.redhat.com/209876 + self.output.add_info('W', pkg, 'read-error', e) + return (chunk, False) + + if b'\0' in chunk: + return (chunk, False) + + if not chunk: # Empty files are considered text + return (chunk, True) + + fl = filename.lower() + + # PDF's are binary but often detected as text by the algorithm below + if fl.endswith('.pdf') and chunk.startswith(b'%PDF-'): + return (chunk, False) + # Ditto RDoc RI files + if fl.endswith('.ri') and '/ri/' in fl: + return (chunk, False) + # And Sphinx inventory files + if fl.endswith('.inv') and chunk.startswith(b'# Sphinx inventory'): + return (chunk, False) + + # Binary if control chars are > 30% of the string + control_chars = chunk.translate(None, printable_extended_ascii) + nontext_ratio = float(len(control_chars)) / float(len(chunk)) + istext = nontext_ratio <= 0.30 + + return (chunk, istext) + + def check(self, pkg): + self._check_utf8(pkg) + + # Rest of the checks are for binary packages only + if pkg.is_source: + return + + self.devel_pkg = False + self.deps = pkg.requires + pkg.prereq + self.config_files = pkg.config_files + self.ghost_files = pkg.ghost_files + self.req_names = pkg.req_names + self.lib_package = lib_package_regex.search(pkg.name) + self.is_kernel_package = kernel_package_regex.search(pkg.name) + self.debuginfo_package = debuginfo_package_regex.search(pkg.name) + self.debugsource_package = debugsource_package_regex.search(pkg.name) + + # report these errors only once + self.perl_dep_error = False + self.python_dep_error = False + self.lib_file = False + self.non_lib_file = None + self.log_files = [] + self.logrotate_file = False + self.debuginfo_srcs = False + self.debuginfo_debugs = False + + # Prefetch scriptlets, strip quotes from them (#169) + self.postin = pkg[rpm.RPMTAG_POSTIN] or \ + pkg.scriptprog(rpm.RPMTAG_POSTINPROG) + if self.postin: + self.postin = quotes_regex.sub('', self.postin) + self.postun = pkg[rpm.RPMTAG_POSTUN] or \ + pkg.scriptprog(rpm.RPMTAG_POSTUNPROG) + if self.postun: + self.postun = quotes_regex.sub('', self.postun) + + # Unique (rdev, inode) combinations + self.hardlinks = {} + + # All executable files from standard bin dirs (basename => [paths]) + # Hack: basenames with empty paths links are symlinks (not subject + # to duplicate binary check, but yes for man page existence check) + self.bindir_exes = {} + + # All man page 'base' names (without section etc extensions) + self.man_basenames = set() + + self._check_devel(pkg) + self._check_nodoc(pkg) + self._check_meta_package(pkg) + self._check_empty_debuginfo(pkg) + + # Iterate over all pkg.files + self._check_files(pkg) + + self._check_log_files_without_logrotate(pkg) + self._check_outside_libdir_files(pkg) + self._check_debuginfo_without_sources(pkg) + self._check_bindir_exes(pkg) + + def _check_utf8(self, pkg): + for filename in pkg.header[rpm.RPMTAG_FILENAMES] or (): + if not is_utf8_bytestr(filename): + self.output.add_info('E', pkg, 'filename-not-utf8', byte_to_string(filename)) + + def _check_devel(self, pkg): + # Check if the package is a development package + self.devel_pkg = devel_regex.search(pkg.name) + + if not self.devel_pkg: + for p in pkg.provides: + if devel_regex.search(p[0]): + self.devel_pkg = True + break + + def _check_nodoc(self, pkg): + if not self.lib_package and not pkg.doc_files: + self.output.add_info('W', pkg, 'no-documentation') + + def _check_meta_package(self, pkg): + if pkg.files and self.meta_package_regex.search(pkg.name): + self.output.add_info('W', pkg, 'file-in-meta-package') + + def _check_empty_debuginfo(self, pkg): + debuginfo = self.debuginfo_package or self.debugsource_package + if not pkg.files and debuginfo: + self.output.add_info('E', pkg, 'empty-debuginfo-package') + + def _check_log_files_without_logrotate(self, pkg): + if len(self.log_files) and not self.logrotate_file: + self.output.add_info('W', pkg, 'log-files-without-logrotate', sorted(self.log_files)) + + def _check_outside_libdir_files(self, pkg): + if self.lib_package and self.lib_file and self.non_lib_file: + self.output.add_info('E', pkg, 'outside-libdir-files', self.non_lib_file) + + def _check_debuginfo_without_sources(self, pkg): + if not self.use_debugsource and self.debuginfo_package and self.debuginfo_debugs and not self.debuginfo_srcs: + self.output.add_info('E', pkg, 'debuginfo-without-sources') + + def _check_bindir_exes(self, pkg): + for exe, paths in self.bindir_exes.items(): + if len(paths) > 1: + self.output.add_info('W', pkg, 'duplicate-executable', exe, paths) + if exe not in self.man_basenames: + self.output.add_info('W', pkg, 'no-manual-page-for-binary', exe) + + def _check_files(self, pkg): + for f, pkgfile in pkg.files.items(): + self._check_file(pkg, f, pkgfile) + + def _check_file(self, pkg, fname, pkgfile): + if log_regex.search(fname): + self.log_files.append(fname) + + self._check_file_manpage(pkg, fname) + self._check_file_infopage_compressed(pkg, fname) + self._check_file_unexpandaed_macro(pkg, fname) + self._check_file_non_standard_uid(pkg, fname, pkgfile) + self._check_file_non_standard_gid(pkg, fname, pkgfile) + self._check_file_kernel_modules(pkg, fname) + self._check_file_dir_or_file(pkg, fname) + self._check_file_non_ghost_in_run(pkg, fname) + self._check_file_systemd_unit_in_etc(pkg, fname) + self._check_file_udev_rule_in_etc(pkg, fname) + self._check_file_tmpfiles_conf_in_etc(pkg, fname) + self._check_file_subdir_in_bin(pkg, fname) + self._check_file_siteperl_in_perl_module(pkg, fname) + self._check_file_backup_file_in_package(pkg, fname) + self._check_file_version_control_internal_file(pkg, fname) + self._check_file_htaccess_file(pkg, fname) + self._check_file_hidden_file_or_dir(pkg, fname) + self._check_file_manifest_in_perl_module(pkg, fname) + self._check_file_info_dir_file(pkg, fname) + self._check_file_makefile_junk(pkg, fname) + self._check_file_logrotate(pkg, fname) + self._check_file_crontab(pkg, fname) + self._check_file_xinetd(pkg, fname) + self._check_file_compressed_symlink(pkg, fname, pkgfile) + self._check_file_hardlink(pkg, fname, pkgfile) + + # normal file check + self._check_file_normal_file(pkg, fname, pkgfile) + # normal dir check + self._check_file_dir(pkg, fname, pkgfile) + # symbolic link check + self._check_file_link(pkg, fname, pkgfile) + + self._check_file_crond(pkg, fname, pkgfile) + + def _check_file_manpage(self, pkg, fname): + """ + Check if the the manual page is compressed with the compression method + stated in the rpmlint configuration (CompressExtension option). + Check also for a correct manual page location and if not included in a subfolder. + + Print a warning if it's not compressed. + """ + if stat.S_ISDIR(pkg.files[fname].mode): + return + + res = man_base_regex.fullmatch(fname) + if not res: + return + + category = res.group('category') + filename = Path(res.group('filename')) + suffixes = filename.suffixes + + if self.compress_ext: + if self.compress_ext != suffixes[-1][1:]: + self.output.add_info('W', pkg, 'manpage-not-compressed', + self.compress_ext, fname) + suffixes = suffixes[:-1] + + if suffixes: + file_category = suffixes[-1][1:] + if not file_category.startswith(category): + self.output.add_info('E', pkg, 'bad-manual-page-folder', fname, f'expected folder: man{file_category}') + if str(filename.parent) != '.': + self.output.add_info('E', pkg, 'manual-page-in-subfolder', fname) + + def _check_file_infopage_compressed(self, pkg, fname): + """ + Check if the the info page is compressed with the compression method + stated in the rpmlint configuration (CompressExtension option). + + Print a warning if it's not compressed. + """ + if self.compress_ext and self.info_regex.search(fname) and \ + not fname.endswith('/info/dir') and not fname.endswith(self.compress_ext): + self.output.add_info('W', pkg, 'infopage-not-compressed', + self.compress_ext, fname) + + def _check_file_crond(self, pkg, fname, pkgfile): + if not fname.startswith('/etc/cron.d/'): + return + + mode = pkgfile.mode + mode_is_exec = mode & 0o111 + if stat.S_ISLNK(mode): + self.output.add_info('E', pkg, 'symlink-crontab-file', fname) + + if mode_is_exec: + self.output.add_info('E', pkg, 'executable-crontab-file', fname) + + if stat.S_IWGRP & mode or stat.S_IWOTH & mode: + self.output.add_info('E', pkg, 'non-owner-writeable-only-crontab-file', fname) + + def _check_file_unexpandaed_macro(self, pkg, fname): + for match in self.macro_regex.findall(fname): + self.output.add_info('W', pkg, 'unexpanded-macro', fname, match) + + def _check_file_non_standard_uid(self, pkg, fname, pkgfile): + if pkgfile.user not in self.standard_users: + self.output.add_info('W', pkg, 'non-standard-uid', fname, pkgfile.user) + + def _check_file_non_standard_gid(self, pkg, fname, pkgfile): + if pkgfile.group not in self.standard_groups: + self.output.add_info('W', pkg, 'non-standard-gid', fname, pkgfile.group) + + def _check_file_kernel_modules(self, pkg, fname): + if not self.module_rpms_ok and kernel_modules_regex.search(fname) and not \ + self.is_kernel_package: + self.output.add_info('E', pkg, 'kernel-modules-not-in-kernel-packages', fname) + + def _check_file_dir_or_file(self, pkg, fname): + for i in self.disallowed_dirs: + if fname.startswith(i): + self.output.add_info('E', pkg, 'dir-or-file-in-%s' % + '-'.join(i.split('/')[1:]), fname) + + def _check_file_non_ghost_in_run(self, pkg, fname): + if fname.startswith('/run/'): + if fname not in self.ghost_files: + self.output.add_info('W', pkg, 'non-ghost-in-run', fname) + + def _check_file_systemd_unit_in_etc(self, pkg, fname): + if fname.startswith('/etc/systemd/system/'): + self.output.add_info('W', pkg, 'systemd-unit-in-etc', fname) + + def _check_file_udev_rule_in_etc(self, pkg, fname): + if fname.startswith('/etc/udev/rules.d/'): + self.output.add_info('W', pkg, 'udev-rule-in-etc', fname) + + def _check_file_tmpfiles_conf_in_etc(self, pkg, fname): + if fname.startswith('/etc/tmpfiles.d/'): + self.output.add_info('W', pkg, 'tmpfiles-conf-in-etc', fname) + + def _check_file_subdir_in_bin(self, pkg, fname): + if sub_bin_regex.search(fname): + self.output.add_info('E', pkg, 'subdir-in-bin', fname) + + def _check_file_siteperl_in_perl_module(self, pkg, fname): + if '/site_perl/' in fname: + self.output.add_info('W', pkg, 'siteperl-in-perl-module', fname) + + def _check_file_backup_file_in_package(self, pkg, fname): + if backup_regex.search(fname): + self.output.add_info('E', pkg, 'backup-file-in-package', fname) + + def _check_file_version_control_internal_file(self, pkg, fname): + if scm_regex.search(fname): + self.output.add_info('E', pkg, 'version-control-internal-file', fname) + + def _check_file_htaccess_file(self, pkg, fname): + if fname.endswith('/.htaccess'): + self.output.add_info('E', pkg, 'htaccess-file', fname) + + def _check_file_hidden_file_or_dir(self, pkg, fname): + if (hidden_file_regex.search(fname) and + not fname.startswith('/etc/skel/') and + not fname.endswith('/.build-id') and + not fname.endswith('/.cargo-checksum.json')): + self.output.add_info('W', pkg, 'hidden-file-or-dir', fname) + + def _check_file_manifest_in_perl_module(self, pkg, fname): + if manifest_perl_regex.search(fname): + self.output.add_info('W', pkg, 'manifest-in-perl-module', fname) + + def _check_file_info_dir_file(self, pkg, fname): + if fname == '/usr/info/dir' or fname == '/usr/share/info/dir': + self.output.add_info('E', pkg, 'info-dir-file', fname) + + def _check_file_makefile_junk(self, pkg, fname): + fpath = Path(fname) + is_doc = fname in pkg.doc_files + if fpath.name == 'Makefile.am' and str(fpath.with_suffix('.in')) in pkg.files and is_doc: + self.output.add_info('W', pkg, 'makefile-junk', fname) + + def _check_file_logrotate(self, pkg, fname): + res = logrotate_regex.search(fname) + if res: + self.logrotate_file = True + if res.group(1) != pkg.name: + self.output.add_info('E', pkg, 'incoherent-logrotate-file', fname) + + deps = [x[0] for x in pkg.requires + pkg.recommends + pkg.suggests] + if res and 'logrotate' not in deps and pkg.name != 'logrotate': + self.output.add_info('E', pkg, 'missing-dependency-to-logrotate', 'for logrotate script', fname) + + def _check_file_crontab(self, pkg, fname): + deps = [x[0] for x in pkg.requires + pkg.recommends + pkg.suggests] + if fname.startswith('/etc/cron.') and 'crontabs' not in deps and pkg.name != 'crontabs': + self.output.add_info('E', pkg, 'missing-dependency-to-crontabs', 'for cron script', fname) + + def _check_file_xinetd(self, pkg, fname): + deps = [x[0] for x in pkg.requires + pkg.recommends + pkg.suggests] + if fname.startswith('/etc/xinet.d/') and 'xinetd' not in deps and pkg.name != 'xinetd': + self.output.add_info('E', pkg, 'missing-dependency-to-xinetd', 'for xinet.d script', fname) + + def _check_file_compressed_symlink(self, pkg, fname, pkgfile): + link = pkgfile.linkto + if link != '': + ext = compr_regex.search(link) + if ext and not re.compile(r'\.%s$' % ext.group(1)).search(fname): + self.output.add_info('E', pkg, 'compressed-symlink-with-wrong-ext', + fname, link) + + def _check_file_hardlink(self, pkg, fname, pkgfile): + rdev = pkgfile.rdev + inode = pkgfile.inode + + # Hardlink check + for hardlink in self.hardlinks.get((rdev, inode), ()): + if Path(hardlink).parent != Path(fname).parent: + self.output.add_info('W', pkg, 'cross-directory-hard-link', fname, hardlink) + self.hardlinks.setdefault((rdev, inode), []).append(fname) + + def _check_file_link_devel(self, pkg, fname, pkgfile): + is_so = sofile_regex.search(fname) + if not self.devel_pkg and is_so and not pkgfile.linkto.endswith('.so'): + self.output.add_info('W', pkg, 'devel-file-in-non-devel-package', fname) + + def _check_file_link_man(self, pkg, fname): + res = man_base_regex.fullmatch(fname) + if res: + self.man_basenames.add(res.group('binary')) + + def _check_file_link_bindir_exes(self, pkg, fname): + res = bin_regex.search(fname) + if res: + exe = res.group(1) + if '/' not in exe: + self.bindir_exes.setdefault(exe, []) + + def _check_file_link_absolute(self, pkg, fname, pkgfile): + link = pkgfile.linkto + + # absolute link + r = absolute_regex.search(link) + if not r: + return + + is_so = sofile_regex.search(fname) + if not is_so and link not in pkg.files and \ + link not in self.req_names: + is_exception = False + for e in self.dangling_exceptions.values(): + if e['path'].search(link): + is_exception = e['name'] + break + if is_exception: + if is_exception not in self.req_names: + self.output.add_info('W', pkg, 'no-dependency-on', + is_exception) + else: + self.output.add_info('W', pkg, 'dangling-symlink', fname, link) + linktop = r.group(1) + + r = absolute_regex.search(fname) + if r: + filetop = r.group(1) + if filetop == linktop or self.use_relative_symlinks: + self.output.add_info('W', pkg, 'symlink-should-be-relative', + fname, link) + + def _check_file_link_relative(self, pkg, fname, pkgfile): + link = pkgfile.linkto + + # relative link + r = absolute_regex.search(link) + if r: + return + + is_so = sofile_regex.search(fname) + if not is_so: + abslink = f'{Path(fname).parent}/{link}' + abslink = os.path.normpath(abslink) + if abslink not in pkg.files and abslink not in self.req_names: + is_exception = False + for e in self.dangling_exceptions.values(): + if e['path'].search(link): + is_exception = e['name'] + break + if is_exception: + if is_exception not in self.req_names: + self.output.add_info('W', pkg, 'no-dependency-on', + is_exception) + else: + self.output.add_info('W', pkg, 'dangling-relative-symlink', + fname, link) + pathcomponents = fname.split('/')[1:] + r = points_regex.search(link) + lastpop = None + mylink = None + + while r: + mylink = r.group(1) + if len(pathcomponents) == 0: + self.output.add_info('E', pkg, 'symlink-has-too-many-up-segments', + fname, link) + break + else: + lastpop = pathcomponents[0] + pathcomponents = pathcomponents[1:] + r = points_regex.search(mylink) + + if mylink and lastpop: + r = absolute2_regex.search(mylink) + linktop = r.group(1) + + # does the link go up and then down into the same + # directory? + # if linktop == lastpop: + # self.output.add_info('W', pkg, 'lengthy-symlink', f, link) + + # have we reached the root directory? + if len(pathcomponents) == 0 and linktop != lastpop \ + and not self.use_relative_symlinks: + # relative link into other toplevel directory + self.output.add_info('W', pkg, 'symlink-should-be-absolute', fname, + link) + # check additional segments for mistakes like + # `foo/../bar/' + for linksegment in mylink.split('/'): + if linksegment == '..': + self.output.add_info('E', + pkg, + 'symlink-contains-up-and-down-segments', + fname, link) + + def _check_file_link_bindir_shebang(self, pkg, fname, pkgfile): + basedir = Path(fname).parent + linkto = str((basedir / Path(pkgfile.linkto)).resolve()) + # Link to a file not in the package, so ignore + if linkto not in pkg.files: + return + + realbin = pkg.files[linkto] + # Link to something in bindir is okay + if bin_regex.search(realbin.name): + return + if not stat.S_ISREG(realbin.mode): + return + + file_chunk, file_istext = self.peek(realbin.path, pkg) + file_interpreter, _file_interpreter_args = script_interpreter(file_chunk) + # Not a script with shebang, so ignore + if not file_interpreter: + return + + # If the shebang interpreter is a dependency, it's okay + deps = [x[0] for x in pkg.requires] + if file_interpreter in deps: + return + + self.output.add_info('W', pkg, 'symlink-to-binary-with-shebang', fname, + f'is a link to a script ({realbin.name}) but missing' + f' requires for {file_interpreter}') + + def _check_file_link(self, pkg, fname, pkgfile): + if not stat.S_ISLNK(pkgfile.mode): + return + + self._check_file_link_devel(pkg, fname, pkgfile) + self._check_file_link_man(pkg, fname) + self._check_file_link_bindir_exes(pkg, fname) + self._check_file_link_absolute(pkg, fname, pkgfile) + self._check_file_link_relative(pkg, fname, pkgfile) + self._check_file_link_bindir_shebang(pkg, fname, pkgfile) + + def _check_file_dir(self, pkg, fname, pkgfile): + if not stat.S_ISDIR(pkgfile.mode): + return + + mode = pkgfile.mode + perm = mode & 0o7777 + if mode & 0o1002 == 2: # world writable w/o sticky bit + self.output.add_info('E', pkg, 'world-writable', fname, '%o' % perm) + if perm != 0o755: + self.output.add_info('E', pkg, 'non-standard-dir-perm', fname, '%o' % perm) + if pkg.name not in filesys_packages and fname in STANDARD_DIRS: + self.output.add_info('E', pkg, 'standard-dir-owned-by-package', fname) + if hidden_file_regex.search(fname) and not fname.endswith('/.build-id'): + self.output.add_info('W', pkg, 'hidden-file-or-dir', fname) + + def _check_file_normal_file(self, pkg, fname, pkgfile): + if not stat.S_ISREG(pkgfile.mode): + return + + self._file_nonexec_file = False + self._file_chunk = None + self._file_istext = False + self._file_interpreter = None + self._file_interpreter_args = None + self._file_is_buildconfig = False + + # set[ug]id bit check + self._check_file_normal_file_setuid_bit(pkg, fname, pkgfile) + self._check_file_normal_file_libfile(pkg, fname) + self._check_file_normal_file_logfile(pkg, fname, pkgfile) + # Fill class attributes, chunk, istext, interpreter, is_buildconfig + self._check_file_normal_file_getdata(pkg, fname, pkgfile) + self._check_file_normal_file_doc(pkg, fname) + self._check_file_normal_file_non_devel(pkg, fname) + self._check_file_normal_file_lib(pkg, fname, pkgfile) + self._check_file_normal_file_depmod_call(pkg, fname) + self._check_file_normal_file_install_info(pkg, fname) + self._check_file_normal_file_perl_temp(pkg, fname) + self._check_file_normal_file_rpaths_in_buildconfig(pkg, fname) + # look for man pages + self._check_file_normal_file_look_for_man(pkg, fname) + self._check_file_normal_file_bin(pkg, fname, pkgfile) + self._check_file_normal_file_devel(pkg, fname, pkgfile) + self._check_file_normal_file_non_readable(pkg, fname, pkgfile) + self._check_file_normal_file_zero_length(pkg, fname, pkgfile) + self._check_file_normal_file_world_w(pkg, fname, pkgfile) + self._check_file_normal_file_perl_dep(pkg, fname) + self._check_file_normal_file_python_dep(pkg, fname) + self._check_file_normal_file_python_source(pkg, fname) + # normal executable check + self._check_file_normal_file_exec(pkg, fname, pkgfile) + self._check_file_normal_file_non_conf_in_etc(pkg, fname) + self._check_file_normal_file_python_noarch(pkg, fname) + self._check_file_normal_file_debuginfo(pkg, fname) + self._check_file_normal_file_gzipped_svg(pkg, fname) + self._check_file_normal_file_pem(pkg, fname) + self._check_file_normal_file_tcl(pkg, fname) + # text file checks + self._check_file_normal_file_text(pkg, fname, pkgfile) + self._check_file_normal_file_not_utf8(pkg, fname, pkgfile) + + def _check_file_normal_file_setuid_bit(self, pkg, fname, pkgfile): + user = pkgfile.user + group = pkgfile.group + mode = pkgfile.mode + perm = mode & 0o7777 + if stat.S_ISGID & mode or stat.S_ISUID & mode: + if stat.S_ISUID & mode: + self.output.add_info('E', pkg, 'setuid-binary', fname, user, '%o' % perm) + if (stat.S_ISGID & mode and + not (group == 'games' and + (games_path_regex.search(fname) or + self.games_group_regex.search(pkg[rpm.RPMTAG_GROUP])))): + self.output.add_info('E', pkg, 'setgid-binary', fname, group, + '%o' % perm) + if mode & 0o777 != 0o755: + self.output.add_info('E', pkg, 'non-standard-executable-perm', fname, + '%o' % perm) + + def _check_file_normal_file_libfile(self, pkg, fname): + is_doc = fname in pkg.doc_files + + if not self.devel_pkg: + if lib_path_regex.search(fname): + self.lib_file = True + elif not is_doc: + self.non_lib_file = fname + + def _check_file_normal_file_logfile(self, pkg, fname, pkgfile): + user = pkgfile.user + group = pkgfile.group + + if log_regex.search(fname): + self._file_nonexec_file = True + if user != 'root': + self.output.add_info('E', pkg, 'non-root-user-log-file', fname, user) + if group != 'root': + self.output.add_info('E', pkg, 'non-root-group-log-file', fname, group) + if fname not in self.ghost_files: + self.output.add_info('E', pkg, 'non-ghost-file', fname) + + def _check_file_normal_file_getdata(self, pkg, fname, pkgfile): + res = None + try: + res = os.access(pkgfile.path, os.R_OK) + except UnicodeError as e: # e.g. non-ASCII, C locale, python 3 + self.output.add_info('W', pkg, 'inaccessible-filename', fname, e) + else: + if res: + (self._file_chunk, self._file_istext) = self.peek(pkgfile.path, pkg) + + (self._file_interpreter, self._file_interpreter_args) = script_interpreter(self._file_chunk) + self._file_is_buildconfig = self._file_istext and buildconfigfile_regex.search(fname) + + def _check_file_normal_file_doc(self, pkg, fname): + is_doc = fname in pkg.doc_files + if doc_regex.search(fname): + if not self._file_interpreter: + self._file_nonexec_file = True + if not is_doc: + self.output.add_info('E', pkg, 'not-listed-as-documentation', fname) + + def _check_file_normal_file_non_devel(self, pkg, fname): + if self.devel_pkg and fname.endswith('.typelib'): + self.output.add_info('E', pkg, 'non-devel-file-in-devel-package', fname) + + def _check_file_normal_file_lib(self, pkg, fname, pkgfile): + # check ldconfig call in %post and %postun + mode = pkgfile.mode + if lib_regex.search(fname): + if self.devel_pkg and not (sofile_regex.search(fname) and stat.S_ISLNK(mode)): + self.output.add_info('E', pkg, 'non-devel-file-in-devel-package', fname) + if not self.postin: + self.output.add_info('E', pkg, 'library-without-ldconfig-postin', fname) + else: + if not ldconfig_regex.search(self.postin): + self.output.add_info('E', pkg, 'postin-without-ldconfig', fname) + + if not self.postun: + self.output.add_info('E', pkg, 'library-without-ldconfig-postun', fname) + else: + if not ldconfig_regex.search(self.postun): + self.output.add_info('E', pkg, 'postun-without-ldconfig', fname) + + def _check_file_normal_file_depmod_call(self, pkg, fname): + # check depmod call in %post and %postun + res = not self.is_kernel_package and kernel_modules_regex.search(fname) + if res: + kernel_version = res.group(1) + kernel_version_regex = re.compile( + r'\bdepmod\s+-a.*F\s+/boot/System\.map-' + + re.escape(kernel_version) + r'\b.*\b' + + re.escape(kernel_version) + r'\b', + re.MULTILINE | re.DOTALL) + + if not self.postin or not depmod_regex.search(self.postin): + self.output.add_info('E', pkg, 'module-without-depmod-postin', fname) + # check that we run depmod on the right kernel + elif not kernel_version_regex.search(self.postin): + self.output.add_info('E', pkg, 'postin-with-wrong-depmod', fname) + + if not self.postun or not depmod_regex.search(self.postun): + self.output.add_info('E', pkg, 'module-without-depmod-postun', fname) + # check that we run depmod on the right kernel + elif not kernel_version_regex.search(self.postun): + self.output.add_info('E', pkg, 'postun-with-wrong-depmod', fname) + + def _check_file_normal_file_install_info(self, pkg, fname): + # check install-info call in %post and %postun + if fname.startswith('/usr/share/info/'): + if not self.postin: + self.output.add_info('E', pkg, + 'info-files-without-install-info-postin', fname) + elif not install_info_regex.search(self.postin): + self.output.add_info('E', pkg, 'postin-without-install-info', fname) + + preun = pkg[rpm.RPMTAG_PREUN] or \ + pkg.scriptprog(rpm.RPMTAG_PREUNPROG) + if not self.postun and not preun: + self.output.add_info('E', pkg, + 'info-files-without-install-info-postun', fname) + elif not ((self.postun and install_info_regex.search(self.postun)) or + (preun and install_info_regex.search(preun))): + self.output.add_info('E', pkg, 'postin-without-install-info', fname) + + def _check_file_normal_file_perl_temp(self, pkg, fname): + # check perl temp file + if perl_temp_file_regex.search(fname): + self.output.add_info('W', pkg, 'perl-temp-file', fname) + + def _check_file_normal_file_rpaths_in_buildconfig(self, pkg, fname): + # check rpaths in buildconfig files + if self._file_is_buildconfig: + ln = pkg.grep(buildconfig_rpath_regex, fname) + if ln: + self.output.add_info('E', pkg, 'rpath-in-buildconfig', fname, 'lines', ln) + + def _check_file_normal_file_look_for_man(self, pkg, fname): + res = man_base_regex.fullmatch(fname) + if res: + self.man_basenames.add(res.group('binary')) + + def _check_file_normal_file_bin(self, pkg, fname, pkgfile): + mode = pkgfile.mode + perm = mode & 0o7777 + mode_is_exec = mode & 0o111 + res = bin_regex.search(fname) + if res: + if not mode_is_exec: + self.output.add_info('W', pkg, 'non-executable-in-bin', fname, + '%o' % perm) + else: + exe = res.group(1) + if '/' not in exe: + self.bindir_exes.setdefault(exe, []).append(fname) + + def _check_file_normal_file_devel(self, pkg, fname, pkgfile): + is_doc = fname in pkg.doc_files + if (not self.devel_pkg and not is_doc and + (self._file_is_buildconfig or includefile_regex.search(fname) or + develfile_regex.search(fname))): + self.output.add_info('W', pkg, 'devel-file-in-non-devel-package', fname) + + def _check_file_normal_file_non_readable(self, pkg, fname, pkgfile): + mode = pkgfile.mode + perm = mode & 0o7777 + if mode & 0o444 != 0o444 and perm & 0o7000 == 0: + ok_nonreadable = False + for regex in non_readable_regexs: + if regex.search(fname): + ok_nonreadable = True + break + if not ok_nonreadable: + self.output.add_info('E', pkg, 'non-readable', fname, '%o' % perm) + + def _check_file_normal_file_zero_length(self, pkg, fname, pkgfile): + size = pkgfile.size + if size == 0 and not normal_zero_length_regex.search(fname) and \ + fname not in self.ghost_files: + self.output.add_info('E', pkg, 'zero-length', fname) + + def _check_file_normal_file_world_w(self, pkg, fname, pkgfile): + mode = pkgfile.mode + perm = mode & 0o7777 + if mode & stat.S_IWOTH: + self.output.add_info('E', pkg, 'world-writable', fname, '%o' % perm) + + def _check_file_normal_file_perl_dep(self, pkg, fname): + if not self.perl_dep_error: + res = perl_regex.search(fname) + if res: + deps = [x[0] for x in pkg.requires + pkg.recommends + pkg.suggests] + if self.perl_version_trick: + vers = res.group(1) + '.' + res.group(2) + else: + vers = res.group(1) + res.group(2) + if not (pkg.check_versioned_dep('perl-base', vers) or + pkg.check_versioned_dep('perl', vers) or + f'perl(:MODULE_COMPAT_{vers})' in deps): + self.output.add_info('E', pkg, 'no-dependency-on', + 'perl-base', vers) + self.perl_dep_error = True + + def _check_file_normal_file_python_dep(self, pkg, fname): + if not self.python_dep_error: + res = python_regex.search(fname) + if (res and not + any(pkg.check_versioned_dep(dep, res.group(1)) + for dep in ( + 'python', 'python-base', 'python(abi)'))): + self.output.add_info('E', pkg, 'no-dependency-on', 'python-base', + res.group(1)) + self.python_dep_error = True + + def _check_file_normal_file_python_source(self, pkg, fname): + source_file = python_bytecode_to_script(fname) + if not source_file: + return + + if source_file in pkg.files: + if self._file_chunk: + # Verify that the magic ABI value embedded in the + # .pyc header is correct + found_magic = pyc_magic_from_chunk(self._file_chunk) + exp_magic, exp_version = get_expected_pyc_magic(fname, self.python_default_version) + if exp_magic and found_magic not in exp_magic: + found_version = 'unknown' + for (pv, pm) in _python_magic_values.items(): + if found_magic in pm: + found_version = pv + break + # If expected version was from the file path, + # issue # an error, otherwise a warning. + msg = (pkg, + 'python-bytecode-wrong-magic-value', + fname, 'expected %s (%s), found %d (%s)' % + (' or '.join(map(str, exp_magic)), + exp_version or self.python_default_version, + found_magic, found_version)) + if exp_version is not None: + self.output.add_info('E', *msg) + else: + self.output.add_info('W', *msg) + + # Verify that the timestamp embedded in the .pyc + # header matches the mtime of the .py file: + pyc_timestamp = pyc_mtime_from_chunk(self._file_chunk) + # If it's a symlink, check target file mtime. + srcfile = pkg.readlink(pkg.files[source_file]) + if not srcfile: + self.output.add_info('W', pkg, 'python-bytecode-without-source', fname) + elif (pyc_timestamp is not None and + pyc_timestamp != srcfile.mtime): + cts = datetime.fromtimestamp( + pyc_timestamp).isoformat() + sts = datetime.fromtimestamp( + srcfile.mtime).isoformat() + self.output.add_info('E', + pkg, 'python-bytecode-inconsistent-mtime', + fname, cts, srcfile.name, sts) + else: + self.output.add_info('W', pkg, 'python-bytecode-without-source', fname) + + def _check_file_normal_file_exec(self, pkg, fname, pkgfile): + mode = pkgfile.mode + perm = mode & 0o7777 + mode_is_exec = mode & 0o111 + if mode & stat.S_IXUSR and perm != 0o755: + self.output.add_info('E', pkg, 'non-standard-executable-perm', + fname, '%o' % perm) + if mode_is_exec: + if fname in self.config_files: + self.output.add_info('E', pkg, 'executable-marked-as-config-file', fname) + if not self._file_nonexec_file: + # doc_regex and log_regex checked earlier, no match, + # check rest of usual cases here. Sourced scripts have + # their own check, so disregard them here. + self._file_nonexec_file = fname.endswith('.pc') or \ + compr_regex.search(fname) or \ + includefile_regex.search(fname) or \ + develfile_regex.search(fname) or \ + logrotate_regex.search(fname) + if self._file_nonexec_file: + self.output.add_info('W', pkg, 'spurious-executable-perm', fname) + + def _check_file_normal_file_non_conf_in_etc(self, pkg, fname): + if fname.startswith('/etc/') and fname not in self.config_files and \ + fname not in self.ghost_files and not fname.startswith('/etc/ld.so.conf.d/'): + self.output.add_info('W', pkg, 'non-conffile-in-etc', fname) + + def _check_file_normal_file_python_noarch(self, pkg, fname): + if pkg.arch == 'noarch' and fname.startswith('/usr/lib64/python'): + self.output.add_info('E', pkg, 'noarch-python-in-64bit-path', fname) + + def _check_file_normal_file_debuginfo(self, pkg, fname): + if self.debuginfo_package: + if fname.endswith('.debug'): + self.debuginfo_debugs = True + else: + self.debuginfo_srcs = True + + def _check_file_normal_file_gzipped_svg(self, pkg, fname): + if fname.endswith('.svgz') and fname[0:-1] not in pkg.files \ + and scalable_icon_regex.search(fname): + self.output.add_info('W', pkg, 'gzipped-svg-icon', fname) + + def _check_file_normal_file_pem(self, pkg, fname): + if fname.endswith('.pem') and fname not in self.ghost_files: + if pkg.grep(start_certificate_regex, fname): + self.output.add_info('W', pkg, 'pem-certificate', fname) + if pkg.grep(start_private_key_regex, fname): + self.output.add_info('E', pkg, 'pem-private-key', fname) + + def _check_file_normal_file_tcl(self, pkg, fname): + if tcl_regex.search(fname): + self.output.add_info('E', pkg, 'tcl-extension-file', fname) + + def _check_file_normal_file_text(self, pkg, fname, pkgfile): + if not self._file_istext: + return + + mode = pkgfile.mode + perm = mode & 0o7777 + mode_is_exec = mode & 0o111 + is_doc = fname in pkg.doc_files + + # ignore perl module shebang -- TODO: disputed... + if fname.endswith('.pm'): + self._file_interpreter = None + # sourced scripts should not be executable + if sourced_script_regex.search(fname): + if self._file_interpreter: + self.output.add_info('E', pkg, + 'sourced-script-with-shebang', fname, + self._file_interpreter, self._file_interpreter_args) + if mode_is_exec: + self.output.add_info('E', pkg, 'executable-sourced-script', + fname, '%o' % perm) + # ...but executed ones should + elif self._file_interpreter or mode_is_exec or script_regex.search(fname): + if self._file_interpreter: + res = interpreter_regex.search(self._file_interpreter) + if (mode_is_exec or script_regex.search(fname)): + if res and res.group(1) == 'env': + self.output.add_info('E', pkg, 'env-script-interpreter', + fname, self._file_interpreter, + self._file_interpreter_args) + elif not res: + self.output.add_info('E', pkg, 'wrong-script-interpreter', + fname, self._file_interpreter, + self._file_interpreter_args) + elif not self._file_nonexec_file and not \ + (lib_path_regex.search(fname) and + fname.endswith('.la')): + self.output.add_info('E', pkg, 'script-without-shebang', fname) + + if not mode_is_exec and not is_doc and \ + self._file_interpreter and self._file_interpreter.startswith('/'): + self.output.add_info('E', pkg, 'non-executable-script', fname, + '%o' % perm, self._file_interpreter, + self._file_interpreter_args) + if b'\r' in self._file_chunk: + self.output.add_info('E', pkg, 'wrong-script-end-of-line-encoding', fname) + elif is_doc and not self.skipdocs_regex.search(fname): + if b'\r' in self._file_chunk: + self.output.add_info('W', pkg, 'wrong-file-end-of-line-encoding', fname) + # We check only doc text files for UTF-8-ness; + # checking everything may be slow and can generate + # lots of unwanted noise. + if not is_utf8(pkgfile.path): + self.output.add_info('W', pkg, 'file-not-utf8', fname) + if fsf_license_regex.search(self._file_chunk) and \ + fsf_wrong_address_regex.search(self._file_chunk): + self.output.add_info('E', pkg, 'incorrect-fsf-address', fname) + + def _check_file_normal_file_not_utf8(self, pkg, fname, pkgfile): + is_doc = fname in pkg.doc_files + if not self._file_istext and is_doc and self._file_chunk and compr_regex.search(fname): + ff = compr_regex.sub('', fname) + # compressed docs, eg. info and man files etc + if not self.skipdocs_regex.search(ff) and not is_utf8(pkgfile.path): + self.output.add_info('W', pkg, 'file-not-utf8', fname) diff --git a/I18NCheck.py b/rpmlint/checks/I18NCheck.py index ac2c300..618129b 100644 --- a/I18NCheck.py +++ b/rpmlint/checks/I18NCheck.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ############################################################################# # File : I18NCheck.py # Package : rpmlint @@ -10,10 +9,8 @@ import re import rpm - -from __isocodes__ import COUNTRIES, LANGUAGES -import AbstractCheck -from Filter import addDetails, printError, printWarning +from rpmlint.__isocodes__ import COUNTRIES, LANGUAGES +from rpmlint.checks.AbstractCheck import AbstractCheck # Associative array of invalid value => correct value @@ -50,12 +47,12 @@ EXCEPTION_DIRS = ( def is_valid_lang(lang): # TODO: @Foo and charset handling - lang = re.sub("[@.].*$", "", lang) + lang = re.sub('[@.].*$', '', lang) if lang in LANGUAGES: return True - ix = lang.find("_") + ix = lang.find('_') if ix == -1: return False @@ -71,13 +68,9 @@ def is_valid_lang(lang): return True -class I18NCheck(AbstractCheck.AbstractCheck): - - def __init__(self): - AbstractCheck.AbstractCheck.__init__(self, 'I18NCheck') - +class I18NCheck(AbstractCheck): def check_binary(self, pkg): - files = list(pkg.files().keys()) + files = list(pkg.files.keys()) files.sort() locales = [] # list of locales for this packages webapp = False @@ -87,7 +80,7 @@ class I18NCheck(AbstractCheck.AbstractCheck): for i in i18n_tags: try: correct = INCORRECT_LOCALES[i] - printError(pkg, 'incorrect-i18n-tag-' + correct, i) + self.output.add_info('E', pkg, 'incorrect-i18n-tag-' + correct, i) except KeyError: pass @@ -109,12 +102,12 @@ class I18NCheck(AbstractCheck.AbstractCheck): res2 = correct_subdir_regex.search(locale) if not res2: if locale not in EXCEPTION_DIRS: - printError(pkg, 'incorrect-locale-subdir', f) + self.output.add_info('E', pkg, 'incorrect-locale-subdir', f) else: locale_name = res2.group(2) try: correct = INCORRECT_LOCALES[locale_name] - printError(pkg, 'incorrect-locale-' + correct, f) + self.output.add_info('E', pkg, 'incorrect-locale-' + correct, f) except KeyError: pass res = lc_messages_regex.search(f) @@ -122,7 +115,7 @@ class I18NCheck(AbstractCheck.AbstractCheck): if res: subdir = res.group(1) if not is_valid_lang(subdir): - printError(pkg, 'invalid-lc-messages-dir', f) + self.output.add_info('E', pkg, 'invalid-lc-messages-dir', f) else: res = man_regex.search(f) if res: @@ -130,17 +123,16 @@ class I18NCheck(AbstractCheck.AbstractCheck): if is_valid_lang(subdir): subdir = None else: - printError(pkg, 'invalid-locale-man-dir', f) + self.output.add_info('E', pkg, 'invalid-locale-man-dir', f) - if f.endswith('.mo') or subdir: - if pkg.files()[f].lang == '' and not webapp: - printWarning(pkg, 'file-not-in-%lang', f) + if (f.endswith('.mo') or subdir) and (pkg.files[f].lang == '' and not webapp): + self.output.add_info('W', pkg, 'file-not-in-%lang', f) - main_dir, main_lang = ("", "") + main_dir, main_lang = ('', '') for f in files: - lang = pkg.files()[f].lang - if main_lang and lang == "" and is_prefix(main_dir + '/', f): - printError(pkg, 'subfile-not-in-%lang', f) + lang = pkg.files[f].lang + if main_lang and lang == '' and is_prefix(main_dir + '/', f): + self.output.add_info('E', pkg, 'subfile-not-in-%lang', f) if main_lang != lang: main_dir, main_lang = f, lang @@ -148,52 +140,9 @@ class I18NCheck(AbstractCheck.AbstractCheck): res = package_regex.search(name) if res: locales = 'locales-' + res.group(1) - if locales != name: - if locales not in (x[0] for x in pkg.requires()): - printError(pkg, 'no-dependency-on', locales) + if locales != name and locales not in (x[0] for x in pkg.requires): + self.output.add_info('E', pkg, 'no-dependency-on', locales) def is_prefix(p, s): return len(p) <= len(s) and p == s[:len(p)] - - -# Create an object to enable the auto registration of the test -check = I18NCheck() - -addDetails( -# Need to add a function to list all the locales -'incorrect-i18n-tag-', -""" -""", - -'incorrect-locale-subdir', -""" -""", - -'incorrect-locale-', -""" -""", - -'invalid-lc-messages-dir', -""" -""", - -'invalid-locale-man-dir', -""" -""", - -'file-not-in-lang', -""" -""", - -'no-dependency-on', -""" -""", - -'subfile-not-in-%lang', -""" If /foo/bar is not tagged %lang(XX) whereas /foo is, the package won't be -installable if XX is not in %_install_langs.""", - -) - -# I18NCheck.py ends here diff --git a/rpmlint/checks/IconSizesCheck.py b/rpmlint/checks/IconSizesCheck.py new file mode 100644 index 0000000..03b8e2d --- /dev/null +++ b/rpmlint/checks/IconSizesCheck.py @@ -0,0 +1,26 @@ +import re + +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class IconSizesCheck(AbstractCheck): + file_size_regex = re.compile(r'/icons/[^/]+/(?P<x>\d+)x(?P<y>\d+)/') + info_size_regex = re.compile(r'(?P<x>\d+) x (?P<y>\d+)') + + def check(self, pkg): + if pkg.is_source: + return + + for fname, pkgfile in pkg.files.items(): + if '/animations/' in fname: + continue + + res = self.file_size_regex.search(fname) + res2 = self.info_size_regex.search(pkgfile.magic) + if res and res2: + sizes = (res.group('x'), res.group('y')) + actualsizes = (res2.group('x'), res2.group('y')) + if abs(int(sizes[0]) - int(actualsizes[0])) > 2 or \ + abs(int(sizes[1]) - int(actualsizes[1])) > 2: + self.output.add_info('E', pkg, 'wrong-icon-size', fname, + 'expected:', 'x'.join(sizes), 'actual:', 'x'.join(actualsizes)) diff --git a/rpmlint/checks/InitScriptCheck.py b/rpmlint/checks/InitScriptCheck.py new file mode 100644 index 0000000..925a282 --- /dev/null +++ b/rpmlint/checks/InitScriptCheck.py @@ -0,0 +1,214 @@ +############################################################################# +# Project : Mandriva Linux +# Module : rpmlint +# File : InitScriptCheck.py +# Author : Frederic Lepied +# Created On : Fri Aug 25 09:26:37 2000 +# Purpose : check init scripts (files in /etc/rc.d/init.d) +############################################################################# + +from pathlib import Path +import re + +import rpm +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.helpers import readlines + + +chkconfig_content_regex = re.compile(r'^\s*#\s*chkconfig:\s*([-0-9]+)\s+[-0-9]+\s+[-0-9]+') +subsys_regex = re.compile(r'/var/lock/subsys/([^/"\'\s;&|]+)', re.MULTILINE) +chkconfig_regex = re.compile(r'^[^#]*(chkconfig|add-service|del-service)', re.MULTILINE) +status_regex = re.compile(r'^[^#]*status', re.MULTILINE) +reload_regex = re.compile(r'^[^#]*reload', re.MULTILINE) +lsb_tags_regex = re.compile(r'^# ([\w-]+):\s*(.*?)\s*$') +lsb_cont_regex = re.compile(r'^#(?:%s| )(.*?)\s*$' % '\t') + +LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop', 'Should-Start', + 'Should-Stop', 'Default-Start', 'Default-Stop', + 'Short-Description', 'Description') +RECOMMENDED_LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop', + 'Default-Stop', 'Short-Description') + + +var_regex = re.compile(r'^(.*)\${?(\w+)}?(.*)$') + + +def shell_var_value(var, script): + assign_regex = re.compile(r'\b' + re.escape(var) + + r'\s*=\s*(.+)\s*(#.*)*$', re.MULTILINE) + res = assign_regex.search(script) + if res: + res2 = var_regex.search(res.group(1)) + if res2 and res2.group(2) == var: # infinite loop + return None + return substitute_shell_vars(res.group(1), script) + else: + return None + + +def substitute_shell_vars(val, script): + res = var_regex.search(val) + if res: + value = shell_var_value(res.group(2), script) + if not value: + value = '' + return res.group(1) + value + \ + substitute_shell_vars(res.group(3), script) + else: + return val + + +class InitScriptCheck(AbstractCheck): + def __init__(self, config, output): + super().__init__(config, output) + self.use_deflevels = self.config.configuration['UseDefaultRunlevels'] + self.use_subsys = self.config.configuration['UseVarLockSubsys'] + + def check_binary(self, pkg): + initscript_list = [] + for fname, pkgfile in pkg.files.items(): + if not fname.startswith('/etc/init.d/') and \ + not fname.startswith('/etc/rc.d/init.d/'): + continue + + basename = Path(fname).name + initscript_list.append(basename) + if pkgfile.mode & 0o500 != 0o500: + self.output.add_info('E', pkg, 'init-script-non-executable', fname) + + if '.' in basename: + self.output.add_info('E', pkg, 'init-script-name-with-dot', fname) + + # check chkconfig call in %post and %preun + postin = pkg[rpm.RPMTAG_POSTIN] or \ + pkg.scriptprog(rpm.RPMTAG_POSTINPROG) + if not postin: + self.output.add_info('E', pkg, 'init-script-without-chkconfig-postin', fname) + elif not chkconfig_regex.search(postin): + self.output.add_info('E', pkg, 'postin-without-chkconfig', fname) + + preun = pkg[rpm.RPMTAG_PREUN] or \ + pkg.scriptprog(rpm.RPMTAG_PREUNPROG) + if not preun: + self.output.add_info('E', pkg, 'init-script-without-chkconfig-preun', fname) + elif not chkconfig_regex.search(preun): + self.output.add_info('E', pkg, 'preun-without-chkconfig', fname) + + status_found = False + reload_found = False + chkconfig_content_found = False + subsys_regex_found = False + in_lsb_tag = False + in_lsb_description = False + lastline = '' + lsb_tags = {} + # check common error in file content + content = None + try: + content = list(readlines(pkgfile.path)) + except Exception as e: + self.output.add_info('W', pkg, 'read-error', e) + continue + content_str = ''.join(content) + for line in content: + line = line[:-1] # chomp + # TODO check if there is only one line like this + if line.startswith('### BEGIN INIT INFO'): + in_lsb_tag = True + continue + if line.endswith('### END INIT INFO'): + in_lsb_tag = False + for kw, vals in lsb_tags.items(): + if len(vals) != 1: + self.output.add_info('E', pkg, 'redundant-lsb-keyword', kw) + + for kw in RECOMMENDED_LSB_KEYWORDS: + if kw not in lsb_tags: + self.output.add_info('W', pkg, 'missing-lsb-keyword', + f'{kw} in {fname}') + if in_lsb_tag: + # TODO maybe we do not have to handle this ? + if lastline.endswith('\\'): + line = lastline + line + else: + res = lsb_tags_regex.search(line) + if not res: + cres = lsb_cont_regex.search(line) + if not (in_lsb_description and cres): + in_lsb_description = False + self.output.add_info('E', + pkg, 'malformed-line-in-lsb-comment-block', + line) + else: + lsb_tags['Description'][-1] += \ + ' ' + cres.group(1) + else: + tag = res.group(1) + if not tag.startswith('X-') and \ + tag not in LSB_KEYWORDS: + self.output.add_info('E', pkg, 'unknown-lsb-keyword', line) + else: + in_lsb_description = (tag == 'Description') + if tag not in lsb_tags: + lsb_tags[tag] = [] + lsb_tags[tag].append(res.group(2)) + lastline = line + + if not status_found and status_regex.search(line): + status_found = True + + if not reload_found and reload_regex.search(line): + reload_found = True + + res = chkconfig_content_regex.search(line) + if res: + chkconfig_content_found = True + if self.use_deflevels: + if res.group(1) == '-': + self.output.add_info('W', pkg, 'no-default-runlevel', fname) + elif res.group(1) != '-': + self.output.add_info('W', pkg, 'service-default-enabled', fname) + + res = subsys_regex.search(line) + if res: + subsys_regex_found = True + name = res.group(1) + if self.use_subsys and name != basename: + error = True + if name[0] == '$': + value = substitute_shell_vars(name, content_str) + if value == basename: + error = False + else: + i = name.find('}') + if i != -1: + name = name[0:i] + error = name != basename + if error and len(name): + if name[0] == '$': + self.output.add_info('W', pkg, 'incoherent-subsys', fname, + name) + else: + self.output.add_info('E', pkg, 'incoherent-subsys', fname, + name) + + if 'Default-Start' in lsb_tags and ''.join(lsb_tags['Default-Start']): + self.output.add_info('W', pkg, 'service-default-enabled', fname) + + if not status_found: + self.output.add_info('E', pkg, 'no-status-entry', fname) + if not reload_found: + self.output.add_info('W', pkg, 'no-reload-entry', fname) + if not chkconfig_content_found: + self.output.add_info('E', pkg, 'no-chkconfig-line', fname) + if not subsys_regex_found and self.use_subsys: + self.output.add_info('E', pkg, 'subsys-not-used', fname) + elif subsys_regex_found and not self.use_subsys: + self.output.add_info('E', pkg, 'subsys-unsupported', fname) + + if len(initscript_list) == 1: + pkgname = re.sub('-sysvinit$', '', pkg.name.lower()) + goodnames = (pkgname, pkgname + 'd') + if initscript_list[0] not in goodnames: + self.output.add_info('W', pkg, 'incoherent-init-script-name', + initscript_list[0], str(goodnames)) diff --git a/rpmlint/checks/LSBCheck.py b/rpmlint/checks/LSBCheck.py new file mode 100644 index 0000000..9eaad4d --- /dev/null +++ b/rpmlint/checks/LSBCheck.py @@ -0,0 +1,51 @@ +import re + +import rpm +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class LSBCheck(AbstractCheck): + """ + Validate that package name, version and release number are LSB compliant. + + The rules are the intersection of compatible NVRs between RPM v3 and DPKG + for supporting portability across RPM and Debian systems through tools + like alien. + + Note: It uses values gained from rpm (RPMTAGs) not parsed from .rpm + filename. + """ + name_regex = re.compile('^[a-z0-9.+-]+$') + version_regex = re.compile('^[a-zA-Z0-9.+]+$') + + def check(self, pkg): + self._check_lsb_name(pkg) + self._check_lsb_version(pkg) + self._check_lsb_release(pkg) + + def _check_lsb_name(self, pkg): + """ + Check if the package name is LSB compliant (only lowercase + letters, numbers, '.', '+' or '-' characters). + """ + name = pkg.name + if name and not self.name_regex.search(name): + self.output.add_info('E', pkg, 'non-lsb-compliant-package-name', pkg.name) + + def _check_lsb_version(self, pkg): + """ + Check if the package version number is LSB compliant (only alphanumeric + symbols, '.' or '+' characters). + """ + version = pkg[rpm.RPMTAG_VERSION] + if version and not self.version_regex.search(version): + self.output.add_info('E', pkg, 'non-lsb-compliant-version', pkg[rpm.RPMTAG_VERSION]) + + def _check_lsb_release(self, pkg): + """ + Check if the package release number is LSB compliant (only alphanumeric + symbols, '.' or '+' characters). + """ + release = pkg[rpm.RPMTAG_RELEASE] + if release and not self.version_regex.search(release): + self.output.add_info('E', pkg, 'non-lsb-compliant-release', pkg[rpm.RPMTAG_RELEASE]) diff --git a/rpmlint/checks/LibraryDependencyCheck.py b/rpmlint/checks/LibraryDependencyCheck.py new file mode 100644 index 0000000..759b42b --- /dev/null +++ b/rpmlint/checks/LibraryDependencyCheck.py @@ -0,0 +1,58 @@ +from pathlib import Path +import stat + +from rpm import expandMacro +from rpmlint.checks import FilesCheck +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.pkg import FakePkg + + +class LibraryDependencyCheck(AbstractCheck): + def __init__(self, config, output): + super().__init__(config, output) + self.package_requires = {} + self.package_so_symlinks = {} + self.package_so_files = {} + self.package_arch_mapping = {} + self.isa = expandMacro('%{_isa}') + + def check_binary(self, pkg): + if pkg.is_source: + return + + is_devel = FilesCheck.devel_regex.search(pkg.name) + if is_devel: + self._process_devel_package(pkg, is_devel) + else: + self._process_nondevel_package(pkg) + + def _process_devel_package(self, pkg, is_devel): + self.package_requires[pkg.name] = [req[0] for req in pkg.requires + pkg.prereq] + self.package_so_symlinks[pkg.name] = [] + self.package_arch_mapping[pkg.name] = pkg.arch + + for pkgfile in pkg.files.values(): + if stat.S_ISLNK(pkgfile.mode) and pkgfile.name.endswith('.so'): + link = Path(pkgfile.name).parent / pkgfile.linkto + self.package_so_symlinks[pkg.name].append(str(link)) + + def _process_nondevel_package(self, pkg): + for pkgfile in pkg.files.values(): + if '.so' in pkgfile.name: + self.package_so_files[pkgfile.name] = pkg.name + + def after_checks(self): + for pkgname, so_symlinks in self.package_so_symlinks.items(): + for link in so_symlinks: + with FakePkg(pkgname) as pkg: + pkg.arch = self.package_arch_mapping[pkgname] + if link not in self.package_so_files: + self.output.add_info('E', pkg, 'no-library-dependency-for', link) + break + else: + definition = self.package_so_files[link] + + if (definition not in self.package_requires[pkgname] and + definition + self.isa not in self.package_requires[pkgname]): + self.output.add_info('E', pkg, 'no-library-dependency-on', definition, link) + break diff --git a/rpmlint/checks/LogrotateCheck.py b/rpmlint/checks/LogrotateCheck.py new file mode 100644 index 0000000..eb06fcb --- /dev/null +++ b/rpmlint/checks/LogrotateCheck.py @@ -0,0 +1,64 @@ +import os + +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class LogrotateCheck(AbstractCheck): + def check(self, pkg): + if pkg.is_source: + return + + files = pkg.files + dirs = {} + + for f in files: + if f in pkg.ghost_files: + continue + + if f.startswith('/etc/logrotate.d/'): + try: + for n, o in self.parselogrotateconf(pkg.dir_name(), f).items(): + if n in dirs and dirs[n] != o: + self.output.add_info('E', pkg, 'logrotate-duplicate', n) + else: + dirs[n] = o + except Exception as e: + self.output.add_info('E', pkg, 'logrotate-exception', f, str(e)) + + for d in sorted(dirs.keys()): + if d not in files: + self.output.add_info('E', pkg, 'logrotate-log-dir-not-packaged', d) + continue + mode = files[d].mode & 0o777 + if ((files[d].user != 'root' and (dirs[d] is None or dirs[d][0] != files[d].user)) or + (files[d].group != 'root' and mode & 0o20 and (dirs[d] is None or dirs[d][1] != files[d].group))): + self.output.add_info('E', pkg, 'logrotate-user-writable-log-dir', + f'{d} {files[d].user}:{files[d].group} {mode:04o}') + + # extremely primitive logrotate parser + def parselogrotateconf(self, root, f): + dirs = {} + with open('/'.join((root, f))) as fd: + currentdirs = [] + for line in fd.readlines(): + line = line.strip() + if line.startswith('#'): + continue + if not currentdirs: + if line.endswith('{'): + for logfile in line.split(' '): + logfile = logfile.strip() + if not logfile or logfile == '{': + continue + dn = os.path.dirname(logfile) + if dn not in dirs: + currentdirs.append(dn) + dirs[dn] = None + else: + if line.endswith('}'): + currentdirs = [] + elif line.startswith('su '): + a = line.split(' ') + for dn in currentdirs: + dirs[dn] = (a[1], a[2]) + return dirs diff --git a/rpmlint/checks/MenuCheck.py b/rpmlint/checks/MenuCheck.py new file mode 100644 index 0000000..9a8da74 --- /dev/null +++ b/rpmlint/checks/MenuCheck.py @@ -0,0 +1,228 @@ +############################################################################# +# Project : Mandriva Linux +# Module : rpmlint +# File : MenuCheck.py +# Author : Frederic Lepied +# Created On : Mon Mar 20 07:43:37 2000 +############################################################################# + +import re +import stat +import subprocess + +import rpm +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.helpers import ENGLISH_ENVIROMENT + + +menu_file_regex = re.compile(r'^/usr/lib/menu/([^/]+)$') +old_menu_file_regex = re.compile(r'^/usr/share/(gnome/apps|applnk)/([^/]+)$') +package_regex = re.compile(r'\?package\((.*)\):') +needs_regex = re.compile(r'needs=(\"([^\"]+)\"|([^ %s\"]+))' % '\t') +section_regex = re.compile(r'section=(\"([^\"]+)\"|([^ %s\"]+))' % '\t') +title_regex = re.compile(r'[\"\s]title=(\"([^\"]+)\"|([^ %s\"]+))' % '\t') +longtitle_regex = re.compile(r'longtitle=(\"([^\"]+)\"|([^ %s\"]+))' % '\t') +command_regex = re.compile(r'command=(?:\"([^\"]+)\"|([^ %s\"]+))' % '\t') +icon_regex = re.compile(r'icon=\"?([^\" ]+)') +update_menus_regex = re.compile(r'^[^#]*update-menus', re.MULTILINE) +xpm_ext_regex = re.compile(r'/usr/share/icons/(mini/|large/).*\.xpm$') +version_regex = re.compile(r'([0-9.][0-9.]+)($|\s)') +xdg_migrated_regex = re.compile(r'xdg=\"?([^\" ]+)') + + +class MenuCheck(AbstractCheck): + def __init__(self, config, output): + super().__init__(config, output) + self.valid_sections = self.config.configuration['ValidMenuSections'] + self.standard_needs = self.config.configuration['ExtraMenuNeeds'] + self.icon_paths = self.config.configuration['IconPath'] + self.launchers = self.config.configuration['MenuLaunchers'] + self.icon_ext_regex = re.compile(self.config.configuration['IconFilename']) + # compile regexps + for value in self.launchers.values(): + value['regexp'] = re.compile(value['regexp']) + + def check_binary(self, pkg): + files = pkg.files + menus = [] + + for fname, pkgfile in files.items(): + # Check menu files + res = menu_file_regex.search(fname) + mode = pkgfile.mode + if res: + basename = res.group(1) + if not stat.S_ISREG(mode): + self.output.add_info('E', pkg, 'non-file-in-menu-dir', fname) + else: + if basename != pkg.name: + self.output.add_info('W', pkg, 'non-coherent-menu-filename', fname) + if mode & 0o444 != 0o444: + self.output.add_info('E', pkg, 'non-readable-menu-file', fname) + if mode & 0o111: + self.output.add_info('E', pkg, 'executable-menu-file', fname) + menus.append(fname) + else: + # Check old menus from KDE and GNOME + res = old_menu_file_regex.search(fname) + if res: + if stat.S_ISREG(mode): + self.output.add_info('E', pkg, 'old-menu-entry', fname) + else: + # Check non transparent xpm files + res = xpm_ext_regex.search(fname) + if res and stat.S_ISREG(mode) and not pkg.grep('None",', fname): + self.output.add_info('W', pkg, 'non-transparent-xpm', fname) + if fname.startswith('/usr/lib64/menu'): + self.output.add_info('E', pkg, 'menu-in-wrong-dir', fname) + + if menus: + postin = pkg[rpm.RPMTAG_POSTIN] or \ + pkg.scriptprog(rpm.RPMTAG_POSTINPROG) + if not postin: + self.output.add_info('E', pkg, 'menu-without-postin') + elif not update_menus_regex.search(postin): + self.output.add_info('E', pkg, 'postin-without-update-menus') + + postun = pkg[rpm.RPMTAG_POSTUN] or \ + pkg.scriptprog(rpm.RPMTAG_POSTUNPROG) + if not postun: + self.output.add_info('E', pkg, 'menu-without-postun') + elif not update_menus_regex.search(postun): + self.output.add_info('E', pkg, 'postun-without-update-menus') + + directory = pkg.dir_name() + for f in menus: + # remove comments and handle cpp continuation lines + text = subprocess.run(('/lib/cpp', directory + f), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=ENGLISH_ENVIROMENT, text=True).stdout + if text.endswith('\n'): + text = text[:-1] + + for line in text.splitlines(): + if not line.startswith('?'): + continue + res = package_regex.search(line) + if res: + package = res.group(1) + if package != pkg.name: + self.output.add_info('W', pkg, + 'incoherent-package-value-in-menu', + package, f) + else: + self.output.add_info('I', pkg, 'unable-to-parse-menu-entry', line) + + command = True + res = command_regex.search(line) + if res: + command_line = (res.group(1) or res.group(2)).split() + command = command_line[0] + for launcher in self.launchers.values(): + if not launcher['regexp'].search(command): + continue + found = False + if launcher['binaries']: + found = '/bin/' + command_line[0] in files or \ + '/usr/bin/' + command_line[0] in files or \ + '/usr/X11R6/bin/' + command_line[0] \ + in files + if not found: + for i in launcher['binaries']: + if i in pkg.req_names: + found = True + break + if not found: + self.output.add_info('E', pkg, + 'use-of-launcher-in-menu-but-no-requires-on', + launcher['binaries'][0]) + command = command_line[1] + break + + if command[0] == '/': + if command not in files: + self.output.add_info('W', + pkg, 'menu-command-not-in-package', + command) + elif not ('/bin/' + command in files or + '/usr/bin/' + command in files or + '/usr/X11R6/bin/' + command in files): + self.output.add_info('W', pkg, 'menu-command-not-in-package', + command) + else: + self.output.add_info('W', pkg, 'missing-menu-command') + command = False + + res = longtitle_regex.search(line) + if res: + grp = res.groups() + title = grp[1] or grp[2] + if title[0] != title[0].upper(): + self.output.add_info('W', pkg, 'menu-longtitle-not-capitalized', + title) + res = version_regex.search(title) + if res: + self.output.add_info('W', pkg, 'version-in-menu-longtitle', + title) + else: + self.output.add_info('E', pkg, 'no-longtitle-in-menu', f) + title = None + + res = title_regex.search(line) + if res: + grp = res.groups() + title = grp[1] or grp[2] + if title[0] != title[0].upper(): + self.output.add_info('W', pkg, 'menu-title-not-capitalized', + title) + res = version_regex.search(title) + if res: + self.output.add_info('W', pkg, 'version-in-menu-title', title) + if '/' in title: + self.output.add_info('E', pkg, 'invalid-title', title) + else: + self.output.add_info('E', pkg, 'no-title-in-menu', f) + title = None + + res = needs_regex.search(line) + if res: + grp = res.groups() + needs = (grp[1] or grp[2]).lower() + if needs in ('x11', 'text', 'wm'): + res = section_regex.search(line) + if res: + grp = res.groups() + section = grp[1] or grp[2] + # don't warn entries for sections + if command and section not in self.valid_sections: + self.output.add_info('E', pkg, 'invalid-menu-section', + section, f) + else: + self.output.add_info('I', pkg, 'unable-to-parse-menu-section', + line) + elif needs not in self.standard_needs: + self.output.add_info('I', pkg, 'strange-needs', needs, f) + else: + self.output.add_info('I', pkg, 'unable-to-parse-menu-needs', line) + + res = icon_regex.search(line) + if res: + icon = res.group(1) + if not self.icon_ext_regex.search(icon): + self.output.add_info('W', pkg, 'invalid-menu-icon-type', icon) + if icon[0] == '/' and needs == 'x11': + self.output.add_info('W', pkg, 'hardcoded-path-in-menu-icon', + icon) + else: + for value in self.icon_paths.values(): + if (value['path'] + icon) not in files: + self.output.add_info('E', + pkg, value['type'] + '-icon-not-in-package', + icon, f) + else: + self.output.add_info('W', pkg, 'no-icon-in-menu', title) + + res = xdg_migrated_regex.search(line) + if res: + if res.group(1).lower() != 'true': + self.output.add_info('E', pkg, 'non-xdg-migrated-menu') + else: + self.output.add_info('E', pkg, 'non-xdg-migrated-menu') diff --git a/rpmlint/checks/MenuXDGCheck.py b/rpmlint/checks/MenuXDGCheck.py new file mode 100644 index 0000000..5b71056 --- /dev/null +++ b/rpmlint/checks/MenuXDGCheck.py @@ -0,0 +1,102 @@ +# +# check xdg file format violation +# +# http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html +# + +import codecs +import configparser as cfgparser +from pathlib import Path +import subprocess + +from rpmlint.checks.AbstractCheck import AbstractFilesCheck +from rpmlint.helpers import ENGLISH_ENVIROMENT + +STANDARD_BIN_DIRS = ('/bin', '/sbin', '/usr/bin', '/usr/sbin') + + +class MenuXDGCheck(AbstractFilesCheck): + """ + Check whether MenuXDG files installed by a package are valid. + """ + def __init__(self, config, output): + # desktop file need to be in $XDG_DATA_DIRS + # $ echo $XDG_DATA_DIRS/applications + # /var/lib/menu-xdg:/usr/share + super().__init__(config, output, r'/usr/share/applications/.*\.desktop$') + + def parse_desktop_file(self, pkg, root, f, filename): + """ + Check the structure of a desktop file. + """ + cfp = cfgparser.RawConfigParser() + try: + with codecs.open(f, encoding='utf-8') as inputf: + cfp.read_file(inputf, filename) + except cfgparser.Error as e: + self._handle_parser_error(pkg, filename, e) + except UnicodeDecodeError as e: + self.output.add_info('E', pkg, 'non-utf8-desktopfile', filename, f'Unicode error: {e}') + else: + self._has_binary(pkg, root, cfp, filename) + + def check_file(self, pkg, filename): + root = pkg.dir_name() + f = root + filename + try: + command = subprocess.run(('desktop-file-validate', f), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env=ENGLISH_ENVIROMENT, text=True) + text = command.stdout + if command.returncode: + error_printed = False + for line in text.splitlines(): + if 'error: ' in line: + self.output.add_info('E', pkg, 'invalid-desktopfile', filename, + line.split('error: ')[1]) + error_printed = True + if not error_printed: + self.output.add_info('E', pkg, 'invalid-desktopfile', filename) + + self.parse_desktop_file(pkg, root, f, filename) + except UnicodeDecodeError as e: + self.output.add_info('E', pkg, 'non-utf8-desktopfile', filename, f'Unicode error: {e}') + + def _handle_parser_error(self, pkg, filename, e): + """ + Determine what to do with a caught configparser error. + """ + # I would love to use switch, however, each warning is printed differently + if (isinstance(e, cfgparser.MissingSectionHeaderError)): + self.output.add_info('E', pkg, 'desktopfile-missing-header', filename) + elif (isinstance(e, cfgparser.DuplicateSectionError)): + self.output.add_info('E', pkg, 'desktopfile-duplicate-section', filename, + '[{e.section}]') + elif (isinstance(e, cfgparser.DuplicateOptionError)): + self.output.add_info('E', pkg, 'desktopfile-duplicate-option', filename, + '[{e.section}]/{e.option}') + else: + self.output.add_info('E', pkg, 'invalid-desktopfile', filename, + e.message.partition(':')[0]) + + def _has_binary(self, pkg, root, cfp, filename): + """ + Check whether there is a binary assigned to the desktop file. + + Needs configparser instance, it is assumed to be called in parse_desktop_file. + """ + binary = None + if cfp.has_option('Desktop Entry', 'Exec'): + binary = cfp.get('Desktop Entry', 'Exec').partition(' ')[0] + # If there is no binary mentioned it is OK + if not binary: + return + if binary.startswith('/'): + if (Path(root + binary).exists()): + return + else: + for i in STANDARD_BIN_DIRS: + if Path(root + i + '/' + binary).exists(): + # no need to check if the binary is +x, rpmlint does it + # in another place + return + self.output.add_info('W', pkg, 'desktopfile-without-binary', filename, binary) diff --git a/rpmlint/checks/MixedOwnershipCheck.py b/rpmlint/checks/MixedOwnershipCheck.py new file mode 100644 index 0000000..166ca4b --- /dev/null +++ b/rpmlint/checks/MixedOwnershipCheck.py @@ -0,0 +1,28 @@ +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class MixedOwnershipCheck(AbstractCheck): + def check(self, pkg): + """ + Check for mixed permissions in the directory path. + + If folder is owned by i.e. nobody then the files there shouldn't + be owned by other user either, as then nobody could replace the files + and inject anything. + """ + if pkg.is_source: + return + + for path, info in pkg.files.items(): + parent = path.rpartition('/')[0] + + # In case parent folder is not part of this RPM we can't verify it + if parent not in pkg.files: + continue + parent_owner = pkg.files[parent].user + # root user is trusted + if parent_owner in ('root', '0'): + continue + if info.user != parent_owner: + message = f'Path "{path}" owned by "{info.user}" is stored in directory owned by "{parent_owner}"' + self.output.add_info('E', pkg, 'file-parent-ownership-mismatch', message) diff --git a/rpmlint/checks/PAMModulesCheck.py b/rpmlint/checks/PAMModulesCheck.py new file mode 100644 index 0000000..f057734 --- /dev/null +++ b/rpmlint/checks/PAMModulesCheck.py @@ -0,0 +1,25 @@ +import re + +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class PAMModulesCheck(AbstractCheck): + pam_module_re = re.compile(r'^(?:/usr)?/lib(?:64)?/security/([^/]+\.so)$') + + def __init__(self, config, output): + super().__init__(config, output) + self.pam_authorized_modules = config.configuration['PAMAuthorizedModules'] + + def check(self, pkg): + if pkg.is_source: + return + + for f in pkg.files: + if f in pkg.ghost_files: + continue + + m = self.pam_module_re.match(f) + if m: + bn = m.groups()[0] + if bn not in self.pam_authorized_modules: + self.output.add_info('E', pkg, 'pam-unauthorized-module', bn) diff --git a/rpmlint/checks/PkgConfigCheck.py b/rpmlint/checks/PkgConfigCheck.py new file mode 100644 index 0000000..09e7e3a --- /dev/null +++ b/rpmlint/checks/PkgConfigCheck.py @@ -0,0 +1,65 @@ +import re +import stat + +from rpmlint.checks.AbstractCheck import AbstractFilesCheck + + +class PkgConfigCheck(AbstractFilesCheck): + """ + Validate that .pc files are correct. + """ + suspicious_dir = re.compile(r'[=:](?:/usr/src/\w+/BUILD|/var/tmp|/tmp|/home)') + + def __init__(self, config, output): + super().__init__(config, output, r'.*/pkgconfig/.*\.pc$') + + def check(self, pkg): + # check for references to /lib when in lib64 mode and vice versa + if pkg.arch in ('x86_64', 'ppc64', 's390x', 'aarch64'): + self.wronglib_dir = re.compile(r'-L/usr/lib\b') + else: + self.wronglib_dir = re.compile(r'-L/usr/lib64\b') + + AbstractFilesCheck.check(self, pkg) + + def check_file(self, pkg, filename): + if pkg.is_source or not stat.S_ISREG(pkg.files[filename].mode): + return + + try: + with open(pkg.dir_name() + '/' + filename, encoding='utf-8') as pc_file: + for line in pc_file: + self._check_invalid_pkgconfig_file(pkg, filename, line) + self._check_invalid_libs_dir(pkg, filename, line) + self._check_double_slash(pkg, filename, line) + except Exception as e: + self.output.add_info('E', pkg, 'pkgconfig-exception', filename, str(e)) + + def _check_invalid_pkgconfig_file(self, pkg, filename, line): + """ + Check that .pc file is valid (it runs various checks). + + E.g. it doesn't contain traces of $RPM_BUILD_ROOT or $RPM_BUILD_DIR, + unreplaced macros or invalid paths. + """ + if self.suspicious_dir.search(line): + self.output.add_info('E', pkg, 'invalid-pkgconfig-file', filename) + + def _check_invalid_libs_dir(self, pkg, filename, line): + """ + Check that .pc file contains correct libs dir based on the build + target (32-bit, 64-bit). + + That means: + -L/usr/lib or -L/lib for 32-bit, + -L/usr/lib64 or -L/lib64 for 64-bit + """ + if line.startswith('Libs:') and self.wronglib_dir.search(line): + self.output.add_info('E', pkg, 'pkgconfig-invalid-libs-dir', filename, line.rstrip()) + + def _check_double_slash(self, pkg, filename, line): + """ + Check that .pc file doesn't contain a path with a double slash ('//') + """ + if '//' in line and '://' not in line: + self.output.add_info('E', pkg, 'double-slash-in-pkgconfig-path', filename, line.rstrip()) diff --git a/rpmlint/checks/PostCheck.py b/rpmlint/checks/PostCheck.py new file mode 100644 index 0000000..81da9b6 --- /dev/null +++ b/rpmlint/checks/PostCheck.py @@ -0,0 +1,214 @@ +############################################################################# +# Project : Mandriva Linux +# Module : rpmlint +# File : PostCheck.py +# Author : Frederic Lepied +# Created On : Wed Jul 5 13:30:17 2000 +# Purpose : Check post/pre scripts +############################################################################# + +import os +import re +import subprocess +import tempfile + +import rpm +from rpmlint import pkg as Pkg +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.helpers import byte_to_string, ENGLISH_ENVIROMENT + + +# shells that grok the -n switch for debugging +syntaxcheck_shells = ('/bin/sh', '/bin/bash') + +percent_regex = re.compile(r'^[^#]*%{?\w{3,}', re.MULTILINE) +bracket_regex = re.compile(r'^[^#]*if\s+[^ :\]]\]', re.MULTILINE) +home_regex = re.compile(r'[^a-zA-Z]+~/|\${?HOME(\W|$)', re.MULTILINE) +dangerous_command_regex = re.compile(r'(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(cp|mv|ln|tar|rpm|chmod|chown|rm|cpio|install|perl|userdel|groupdel)\s', re.MULTILINE) +selinux_regex = re.compile(r'(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(chcon|runcon)\s', re.MULTILINE) +single_command_regex = re.compile(r'^[ %s]*([^ %s]+)[ %s]*$' % (('\n',) * 3)) +tmp_regex = re.compile(r'^[^#]*\s(/var)?/tmp', re.MULTILINE) +menu_regex = re.compile(r'^/usr/lib/menu/|^/etc/menu-methods/|^/usr/share/applications/') +bogus_var_regex = re.compile(r'(\${?RPM_BUILD_(ROOT|DIR)}?)') + +prereq_assoc = ( + # ['chkconfig', ('chkconfig', '/sbin/chkconfig')], + ['chkfontpath', ('chkfontpath', '/usr/sbin/chkfontpath')], + ['rpm-helper', ('rpm-helper',)], +) + +for p in prereq_assoc: + p[0] = re.compile(r'^[^#]+' + p[0], re.MULTILINE) + +# pychecker fix +del p + + +def incorrect_shell_script(prog, shellscript): + return check_syntax_script(prog, '-n', shellscript) + + +def incorrect_perl_script(prog, perlscript): + return check_syntax_script(prog, '-wc', perlscript) + + +def check_syntax_script(prog, commandline, script): + if not script: + return False + if isinstance(script, str): + script = script.encode('utf-8') + # TODO: test that 'prog' is available/executable + tmpfd, tmpname = tempfile.mkstemp(prefix='rpmlint.') + tmpfile = os.fdopen(tmpfd, 'wb') + try: + tmpfile.write(script) + tmpfile.close() + ret = subprocess.run((prog, commandline, tmpname), env=ENGLISH_ENVIROMENT) + finally: + tmpfile.close() + os.remove(tmpname) + return ret.returncode + + +class PostCheck(AbstractCheck): + def __init__(self, config, output): + super().__init__(config, output) + self.valid_shells = config.configuration['ValidShells'] + self.empty_shells = config.configuration['ValidEmptyShells'] + post_details_dict = { + 'postin-without-ghost-file-creation': + """A file tagged as ghost is not created during %prein nor during %postin.""", + } + for scriptlet in ('%' + x for x in Pkg.RPM_SCRIPTLETS): + post_details_dict.update({ + 'one-line-command-in-%s' % scriptlet: + """You should use {} -p <command> instead of using: + + {} + <command> + + It will avoid the fork of a shell interpreter to execute your command as + well as allows rpm to automatically mark the dependency on your command + for the execution of the scriptlet.""".format(scriptlet, scriptlet), + + 'percent-in-%s' % scriptlet: + """The %s scriptlet contains a '%%' in a context which might indicate it being + fallout from an rpm macro/variable which was not expanded during build. + Investigate whether this is the case and fix if appropriate.""" % scriptlet, + + 'spurious-bracket-in-%s' % scriptlet: + """The %s scriptlet contains an 'if []' construct without a space before + the ']'.""" % scriptlet, + + 'forbidden-selinux-command-in-%s' % scriptlet: + """A command which requires intimate knowledge about a specific SELinux + policy type was found in the scriptlet. These types are subject to change + on a policy version upgrade. Use the restorecon command which queries the + currently loaded policy for the correct type instead.""", + + 'non-empty-%s' % scriptlet: + """Scriptlets for the interpreter mentioned in the message should be empty. + One common case where they are unintentionally not is when the specfile + contains comments after the scriptlet and before the next section. Review + and clean up the scriptlet contents if appropriate.""", + }) + self.output.error_details.update(post_details_dict) + + def check_binary(self, pkg): + prereq = [x[0] for x in pkg.prereq] + + for tag in Pkg.SCRIPT_TAGS: + script = pkg[tag[0]] + + if not isinstance(script, list): + prog = pkg.scriptprog(tag[1]) + if prog: + prog = prog.split()[0] + self.check_aux(pkg, prog, pkg.header[tag[0]], + tag[2], prereq) + else: + prog = pkg[tag[1]] + for idx in range(0, len(prog)): + self.check_aux( + pkg, prog[idx], + pkg.header[tag[0]][idx], tag[2], prereq) + + ghost_files = pkg.ghost_files + if ghost_files: + postin = pkg[rpm.RPMTAG_POSTIN] + prein = pkg[rpm.RPMTAG_PREIN] + for f in ghost_files: + if f in pkg.missingok_files: + continue + if not postin and not prein: + self.output.add_info('W', pkg, 'ghost-files-without-postin') + if (not postin or f not in postin) and \ + (not prein or f not in prein): + self.output.add_info('W', pkg, + 'postin-without-ghost-file-creation', f) + + def check_aux(self, pkg, prog, script, tag, prereq): + files = pkg.files + if script: + script_str = byte_to_string(script) + if prog: + if prog not in self.valid_shells: + self.output.add_info('E', pkg, 'invalid-shell-in-' + tag, prog) + if prog in self.empty_shells: + self.output.add_info('E', pkg, 'non-empty-' + tag, prog) + if prog in syntaxcheck_shells or prog == '/usr/bin/perl': + if percent_regex.search(script_str): + self.output.add_info('W', pkg, 'percent-in-' + tag) + if bracket_regex.search(script_str): + self.output.add_info('W', pkg, 'spurious-bracket-in-' + tag) + res = dangerous_command_regex.search(script_str) + if res: + self.output.add_info('W', pkg, 'dangerous-command-in-' + tag, + res.group(2)) + res = selinux_regex.search(script_str) + if res: + self.output.add_info('E', pkg, 'forbidden-selinux-command-in-' + tag, + res.group(2)) + + if 'update-menus' in script_str: + menu_error = True + for f in files: + if menu_regex.search(f): + menu_error = False + break + if menu_error: + self.output.add_info('E', pkg, + 'update-menus-without-menu-file-in-' + tag) + if tmp_regex.search(script_str): + self.output.add_info('E', pkg, 'use-tmp-in-' + tag) + for c in prereq_assoc: + if c[0].search(script_str): + found = False + for p in c[1]: + if p in prereq or p in files: + found = True + break + if not found: + self.output.add_info('E', pkg, 'no-prereq-on', c[1][0]) + + if prog in syntaxcheck_shells: + if incorrect_shell_script(prog, script): + self.output.add_info('E', pkg, 'shell-syntax-error-in-' + tag) + if home_regex.search(script_str): + self.output.add_info('E', pkg, 'use-of-home-in-' + tag) + res = bogus_var_regex.search(script_str) + if res: + self.output.add_info('W', pkg, 'bogus-variable-use-in-' + tag, + res.group(1)) + + if prog == '/usr/bin/perl': + if incorrect_perl_script(prog, script): + self.output.add_info('E', pkg, 'perl-syntax-error-in-' + tag) + elif prog.endswith('sh'): + res = single_command_regex.search(script_str) + if res: + self.output.add_info('W', pkg, 'one-line-command-in-' + tag, + res.group(1)) + + elif prog not in self.empty_shells and prog in self.valid_shells: + self.output.add_info('W', pkg, 'empty-' + tag) diff --git a/rpmlint/checks/PythonCheck.py b/rpmlint/checks/PythonCheck.py new file mode 100644 index 0000000..ff603a9 --- /dev/null +++ b/rpmlint/checks/PythonCheck.py @@ -0,0 +1,270 @@ +from pathlib import Path +import platform +import re + +from packaging.requirements import InvalidRequirement, Requirement +from rpmlint.checks.AbstractCheck import AbstractFilesCheck + +# Warning messages +WARNS = { + 'doc': 'python-doc-in-package', + 'sphinx': 'python-sphinx-doctrees-leftover', +} + +# Error messages +ERRS = { + 'egg-distutils': 'python-egg-info-distutils-style', + 'tests': 'python-tests-in-site-packages', + 'doc': 'python-doc-in-site-packages', + 'src': 'python-src-in-site-packages', +} + +SITELIB_RE = '/usr/lib[^/]*/python[^/]*/site-packages' + +# Paths that shouldn't be in any packages, ever, because they clobber global +# name space. +ERR_PATHS = [ + (re.compile(f'{SITELIB_RE}/tests?$'), 'tests'), + (re.compile(f'{SITELIB_RE}/docs?$'), 'doc'), + (re.compile(f'{SITELIB_RE}/src$'), 'src'), +] + +# Paths that shouldn't be in any packages, but might need to be under +# sufficiently special circumstances. +WARN_PATHS = [ + (re.compile(f'{SITELIB_RE}/[^/]+/docs?$'), 'doc'), + (re.compile(r'.*/\.doctrees$'), 'sphinx'), +] + + +EGG_INFO_RE = re.compile('.*egg-info$') +PYC_RE = re.compile(r'cpython-(\d+)') + + +class PythonCheck(AbstractFilesCheck): + def __init__(self, config, output): + super().__init__(config, output, r'.*') + + def check_binary(self, pkg): + self._pyc_version = None + super().check_binary(pkg) + + def check_file(self, pkg, filename): + # egg-info format + if filename.endswith('egg-info/requires.txt'): + self._check_requires_txt(pkg, filename) + # dist-info format + if filename.endswith('dist-info/METADATA'): + self._check_requires_metadata(pkg, filename) + + if EGG_INFO_RE.match(filename): + self._check_egginfo(pkg, filename) + + for path_re, key in WARN_PATHS: + if path_re.match(filename): + if key == 'doc': + # Check for __init__.py file inside doc, maybe this is a + # module, not documentation + module_file = f'{filename}/__init__.py' + if module_file in pkg.files.keys(): + continue + self.output.add_info('W', pkg, WARNS[key], filename) + + for path_re, key in ERR_PATHS: + if path_re.match(filename): + self.output.add_info('E', pkg, ERRS[key], filename) + + if filename.endswith('pyc'): + # Check for .pyc from different python versions in the same package + self._check_multiple_python_pyc(pkg, filename) + + def _check_multiple_python_pyc(self, pkg, filename): + """ + """ + search = PYC_RE.search(filename) + if not search: + return + + version = search.group(1) + if self._pyc_version is None: + # First pyc file, just store the version + self._pyc_version = version + elif self._pyc_version != version: + self.output.add_info('W', pkg, 'python-pyc-multiple-versions', + 'expected:', self._pyc_version, filename) + + def _check_egginfo(self, pkg, filename): + """ + Check type of egg-info metadata and check Requires against egg-info + metadata if applicable. + """ + + filepath = Path(pkg.dir_name() or '/', filename.lstrip('/')) + # Check for (deprecated) distutils style metadata. + if filepath.is_file(): + self.output.add_info('E', pkg, ERRS['egg-distutils'], filename) + + def _check_requires_txt(self, pkg, filename): + """ + Look for all requirements defined in the python package and + compare with the requirements defined in the rpm package + """ + + filepath = Path(pkg.dir_name() or '/', filename.lstrip('/')) + requirements = [] + with filepath.open() as f: + for requirement in f.readlines(): + # Ignore sections, just check for default requirements + if requirement.startswith('['): + break + + # Ignore broken requirements + try: + req = Requirement(requirement) + except InvalidRequirement: + continue + + requirements.append(req) + + self._check_requirements(pkg, requirements) + + def _check_requires_metadata(self, pkg, filename): + """ + Look for all requirements defined in the python package and + compare with the requirements defined in the rpm package + """ + + regex = re.compile(r'^Requires-Dist: (?P<req>.*)$', re.IGNORECASE) + + filepath = Path(pkg.dir_name() or '/', filename.lstrip('/')) + requirements = [] + with filepath.open() as f: + for requirement in f.readlines(): + match = regex.match(requirement) + if not match: + continue + + requirement = match.group('req') + + # Ignore broken requirements + try: + req = Requirement(requirement) + except InvalidRequirement: + continue + + requirements.append(req) + + self._check_requirements(pkg, requirements) + + def _check_requirements(self, pkg, requirements): + """ + Check mismatch between the list of requirements and the rpm + 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: + 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, requirement): + """ + Look for the module_name in the package requirements, looking + for common python rpm package names like python-foo, + python3-foo, etc. + """ + + names = self._module_names(requirement.name, extras=requirement.extras) + + # 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(rf'^\(?({regex})({version_req})?({richop_req})?\)?\s*$', re.IGNORECASE) + except re.error: + # Bad regular expression, it could be a name with weird + # characters + return False + + for req in pkg.req_names: + if regex.match(req): + return True + + self.output.add_info('W', pkg, 'python-missing-require', requirement.name) + return False + + def _check_leftover_requirements(self, pkg, requirements): + """ + Look for python-foo requirements in the rpm package that are + not in the list of requirements of this package. + """ + + pythonpac = re.compile(r'^python\d*-(?P<name>.+)$') + reqs = set() + for i in requirements: + for n in self._module_names(i.name, extras=i.extras): + reqs.add(n.lower()) + + for req in pkg.req_names: + match = pythonpac.match(req) + if not match: + continue + + module_name = match.group('name').strip().lower() + # ignore python-base, python-devel packages, that are + # not python modules + if module_name in ('base', 'devel'): + continue + + names = set(self._module_names(module_name)) + + if not (names & reqs): + self.output.add_info('W', pkg, 'python-leftover-require', req) + + def _module_names(self, module_name, extras=None): + """ + Return a list with possible variants of the module name, + replacing "-", "_". + """ + + # Name variants changing '-' with '_' + variants = [] + 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_name, + *variants, + ] diff --git a/rpmlint/checks/SharedLibraryPolicyCheck.py b/rpmlint/checks/SharedLibraryPolicyCheck.py new file mode 100644 index 0000000..bf19649 --- /dev/null +++ b/rpmlint/checks/SharedLibraryPolicyCheck.py @@ -0,0 +1,129 @@ +from pathlib import Path +import re +import stat + +import rpm +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.pkg import formatRequire +from rpmlint.readelfparser import ReadelfParser + + +class SharedLibraryPolicyCheck(AbstractCheck): + """ + Verify shared library packaging policy rules + + This package validates the shared libraries naming scheme based + on the Debian/openSUSE shared libraries policy: + https://en.opensuse.org/openSUSE:Shared_library_packaging_policy + https://www.debian.org/doc/debian-policy/ch-sharedlibs.html + """ + + def __init__(self, config, output): + super().__init__(config, output) + self.re_soname_strongly_versioned = re.compile(r'-[\d\.]+\.so$') + # the pkgname is based on soname if ending with number; special option is flavor build + self.re_soname_pkg = re.compile(r'^lib\S+(\d+(-(32|64)bit)?)$') + self.re_so_files = re.compile(r'\S+.so((\.(\d+))*)$') + + def _check_missing_policy_lib(self, pkg): + # check the pkg has any libname + libfiles = [] + for fname in pkg.files.keys(): + if not self.re_so_files.match(fname): + continue + libfiles.append(fname) + + # if we didn't find any library files then we + # don't need to check anything + + # verify if name does not match the slpp and if we still don't have any lib then error out + if not libfiles and self.re_soname_pkg.match(pkg.name): + self.output.add_info('E', pkg, 'shlib-policy-missing-lib') + + def check(self, pkg): + if pkg.is_source: + return + + # Consider only non-development, non-language library packages + if (not pkg.name.startswith('lib') or + pkg.name.endswith('-devel') or + pkg.name.endswith('-lang')): + return + + self._check_missing_policy_lib(pkg) + + # the soname validation matching the name is done + # already in BinaryCheck._check_shared_library + # Search for shared libraries in this package + libs = set() + libs_needed = set() + libs_to_dir = {} + reqlibs = set() + pkg_requires = {x.name.split('(')[0] for x in pkg.requires} + + for filename, pkgfile in pkg.files.items(): + path = Path(filename) + if (('.so.' in filename or filename.endswith('.so')) and + stat.S_ISREG(pkg.files[filename].mode) and pkgfile.magic.startswith('ELF ')): + readelf_parser = ReadelfParser(pkgfile.path, filename) + failed_reason = readelf_parser.parsing_failed_reason() + if failed_reason: + self.output.add_info('E', pkg, 'readelf-failed', filename, failed_reason) + return + dyn_section = readelf_parser.dynamic_section_info + libs_needed = libs_needed.union(dyn_section.needed) + if dyn_section.soname: + lib_dir = str(path.parent) + libs.add(dyn_section.soname) + libs_to_dir[dyn_section.soname] = lib_dir + if dyn_section.soname in pkg_requires: + # But not if the library is used by the pkg itself + # This avoids program packages with their own + # private lib + # FIXME: we'd need to check if somebody else links + # to this lib + reqlibs.add(dyn_section.soname) + + if not libs.difference(reqlibs): + return + + if pkg.name[-1].isdigit(): + # ignore libs in a versioned non_std_dir + for lib in libs.copy(): + lib_dir = libs_to_dir[lib] + for lib_part in lib_dir.split('/'): + if not lib_part: + continue + if lib_part[-1].isdigit() and not lib_part.endswith('lib64'): + libs.remove(lib) + break + + # Check for non-versioned libs in a std lib package + for lib in libs.copy(): + if (not (lib[-1].isdigit() or + self.re_soname_strongly_versioned.search(lib))): + self.output.add_info('W', pkg, 'shlib-unversioned-lib', lib) + + # Verify shared lib policy package doesn't have hard dependency on non-lib packages + for dep in pkg.requires: + if dep[0].startswith('rpmlib(') or dep[0].startswith('config('): + continue + if (dep[1] & (rpm.RPMSENSE_GREATER | rpm.RPMSENSE_EQUAL)) == rpm.RPMSENSE_EQUAL: + self.output.add_info('W', pkg, 'shlib-fixed-dependency', formatRequire(dep[0], dep[1], dep[2])) + + # Verify non-lib stuff does not add dependencies + if libs: + for dep in pkg_requires: + if '.so.' in dep and dep not in libs and dep not in libs_needed: + self.output.add_info('E', pkg, 'shlib-policy-excessive-dependency', dep) + + # FIXME: implement (#567) + # Check if the files/folders are unversioned in the library package. + # In general you can't co-install the soname packages if they all provide some datafiles + # or configuration files. + # When testing one of examples is libsemanage1: + # /etc/selinux + # /etc/selinux/semanage.conf + # /usr/lib64/libsemanage.so.1 + # The above would be fine if the semanage.conf would be update-alternatived, or suffixed + # but if someone introduces libsemanage2 they can't be installed both at once. diff --git a/rpmlint/checks/SignatureCheck.py b/rpmlint/checks/SignatureCheck.py new file mode 100644 index 0000000..ef633d0 --- /dev/null +++ b/rpmlint/checks/SignatureCheck.py @@ -0,0 +1,60 @@ +import re + +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.helpers import print_warning + + +class SignatureCheck(AbstractCheck): + """ + Checks for PGP signature in the package. + + It checks if the signature is present, known (imported in RPM DB) and + valid. It uses 'rpm -Kv' command than returns detailed information about + the package digests and signature. + """ + any_sig_regex = re.compile(r'[Ss]ignature, key ID') + nokey_sig_regex = re.compile(r'[Ss]ignature, key ID ([\w\d]*): NOKEY') + invalid_sig_regex = re.compile(r'invalid OpenPGP signature') + + def check(self, pkg): + retcode, output = pkg.check_signature() + + # Skip all signature checks if check_signature output is empty + if output is None: + print_warning(f'No output from check_signature() for ' + f'{pkg.filename}. Skipping signature checks.') + return + + self._check_no_signature(pkg, retcode, output) + self._check_unknown_key(pkg, retcode, output) + self._check_invalid_signature(pkg, retcode, output) + + def _check_no_signature(self, pkg, retcode, output): + """ + Check if the package contains a signature. + + Print an error if there is no signature present. That means that + there is no mention about any signature in the 'rpm -Kv' output. + """ + if retcode == 0 and not SignatureCheck.any_sig_regex.search(output): + self.output.add_info('E', pkg, 'no-signature') + + def _check_unknown_key(self, pkg, retcode, output): + """ + Check if the public key is imported in the RPM database. + + Print an error if it's not imported and signature is therefore unknown. + """ + if retcode == 1: + nokey = SignatureCheck.nokey_sig_regex.search(output) + if nokey and not SignatureCheck.invalid_sig_regex.search(output): + self.output.add_info('E', pkg, 'unknown-key', nokey.group(1)) + + def _check_invalid_signature(self, pkg, retcode, output): + """ + Check if the signature is valid. + + Print an error if the signature is corrupted. + """ + if retcode == 1 and SignatureCheck.invalid_sig_regex.search(output): + self.output.add_info('E', pkg, 'invalid-signature') diff --git a/rpmlint/checks/SourceCheck.py b/rpmlint/checks/SourceCheck.py new file mode 100644 index 0000000..7d6b059 --- /dev/null +++ b/rpmlint/checks/SourceCheck.py @@ -0,0 +1,77 @@ +import re + +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class SourceCheck(AbstractCheck): + """ + Validate files in a source package. + """ + source_regex = re.compile(r'\.(tar|tgz)$') + compressed_fileext_magic = { + 'xz': 'XZ compressed', + 'gz': 'gzip compressed', + 'tgz': 'gzip compressed', + 'bz2': 'bzip2 compressed', + 'zst': 'ZSTD compressed', + } + + def __init__(self, config, output): + super().__init__(config, output) + self.compress_ext = config.configuration['CompressExtension'] + self.valid_src_perms = [int(value, 8) for value in config.configuration['ValidSrcPerms']] + self.spec_file = None + + source_details_dict = { + 'source-not-compressed': + """A source archive or file in your package is not compressed using the %s + compression method (doesn't have the %s extension).""" % + (self.compress_ext, self.compress_ext), + } + self.output.error_details.update(source_details_dict) + + def check_source(self, pkg): + # process file list + for fname, pkgfile in pkg.files.items(): + self._check_file_ext(fname, pkgfile, pkg) + self._check_permissions(fname, pkgfile, pkg) + self._check_compressed_source(fname, pkg) + self._check_multiple_specfiles(fname, pkg) + + def _check_file_ext(self, fname, pkgfile, pkg): + """ + Check if the filename extension is the same as what file(1) says. + """ + file_ext = fname.rpartition('.')[2] + + if (file_ext in self.compressed_fileext_magic and + pkgfile.magic and + self.compressed_fileext_magic[file_ext] not in pkgfile.magic): + self.output.add_info('W', pkg, 'inconsistent-file-extension', fname) + + def _check_permissions(self, fname, pkgfile, pkg): + """ + Check if the file permissions are valid according to 'ValidSrcPerms' configuration option. + """ + perm = pkgfile.mode & 0o7777 + if perm not in self.valid_src_perms: + self.output.add_info('W', pkg, 'strange-permission', fname, '%o' % perm) + + def _check_compressed_source(self, fname, pkg): + """ + Check if the Source is compressed if CompressExtension configuration options is used (gz, tgz, bz2, xz or zst). + """ + if (self.source_regex.search(fname) and self.compress_ext and + not fname.endswith(self.compress_ext)): + self.output.add_info('W', pkg, 'source-not-compressed', + self.compress_ext, fname) + + def _check_multiple_specfiles(self, fname, pkg): + """ + Check if the source package contains multiple spec files. + """ + if fname.endswith('.spec'): + if self.spec_file: + self.output.add_info('E', pkg, 'multiple-specfiles', self.spec_file, fname) + else: + self.spec_file = fname diff --git a/rpmlint/checks/SpecCheck.py b/rpmlint/checks/SpecCheck.py new file mode 100644 index 0000000..ce371ce --- /dev/null +++ b/rpmlint/checks/SpecCheck.py @@ -0,0 +1,777 @@ +from pathlib import Path +import re +import subprocess +from urllib.parse import urlparse + +import rpm +from rpmlint import pkg as Pkg +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.helpers import ENGLISH_ENVIROMENT, readlines + +# Don't check for hardcoded library paths in biarch packages +DEFAULT_BIARCH_PACKAGES = '^(gcc|glibc)' + + +def re_tag_compile(tag): + rpm_tag = fr'^{tag}\s*:\s*(\S.*?)\s*$' + return re.compile(rpm_tag, re.IGNORECASE) + + +patch_regex = re_tag_compile(r'Patch(\d*)') +applied_patch_regex = re.compile(r'^%patch\s*(\d*)') +applied_patch_p_regex = re.compile(r'\s-P\s*(\d+)\b') +applied_patch_pipe_regex = re.compile(r'\s%\{PATCH(\d+)\}\s*\|\s*(%\{?__)?patch\b') +applied_patch_i_regex = re.compile(r'(?:%\{?__)?patch\}?.*?\s+(?:<|-i)\s+%\{PATCH(\d+)\}') +source_dir_regex = re.compile(r'^[^#]*(\$RPM_SOURCE_DIR|%{?_sourcedir}?)') +obsolete_tags_regex = re_tag_compile(r'(?:Serial|Copyright)') +buildroot_regex = re_tag_compile('BuildRoot') +prefix_regex = re_tag_compile('Prefix') +packager_regex = re_tag_compile('Packager') +buildarch_regex = re_tag_compile('BuildArch(?:itectures)?') +buildprereq_regex = re_tag_compile('BuildPreReq') +prereq_regex = re_tag_compile(r'PreReq(\(.*\))') + +make_check_regex = re.compile(r'(^|\s|%{?__)make}?\s+(check|test)') +rm_regex = re.compile(r'(^|\s)((.*/)?rm|%{?__rm}?) ') +rpm_buildroot_regex = re.compile(r'^[^#]*?(?:(\\*)\${?RPM_BUILD_ROOT}?|(%+){?buildroot}?)') +configure_libdir_spec_regex = re.compile(r'ln |\./configure[^#]*--libdir=(\S+)[^#]*') +lib_package_regex = re.compile(r'^%package.*\Wlib') +ifarch_regex = re.compile(r'^\s*%ifn?arch\s') +if_regex = re.compile(r'^\s*%if\s') +endif_regex = re.compile(r'^\s*%endif\b') +biarch_package_regex = re.compile(DEFAULT_BIARCH_PACKAGES) +libdir_regex = re.compile(r'%{?_lib(?:dir)?\}?\b') +section_regexs = {x: re.compile('^%' + x + r'(?:\s|$)') + for x in ('build', 'changelog', 'check', 'clean', 'description', 'files', + 'install', 'package', 'prep') + Pkg.RPM_SCRIPTLETS} +deprecated_grep_regex = re.compile(r'\b[ef]grep\b') + +# Only check for /lib, /usr/lib, /usr/X11R6/lib +# TODO: better handling of X libraries and modules. +hardcoded_library_paths = '(/lib|/usr/lib|/usr/X11R6/lib/(?!([^/]+/)+)[^/]*\\.([oa]|la|so[0-9.]*))' +hardcoded_library_path_regex = re.compile(r'^[^#]*((^|\s+|\.\./\.\.|\${?RPM_BUILD_ROOT}?|%{?buildroot}?|%{?_prefix}?)' + hardcoded_library_paths + r'(?=[\s;/])([^\s,;]*))') + +DEFINE_RE = r'(^|\s)%(define|global)\s+' +depscript_override_regex = re.compile(DEFINE_RE + r'__find_(requires|provides)\s') +depgen_disable_regex = re.compile(DEFINE_RE + r'_use_internal_dependency_generator\s+0') +patch_fuzz_override_regex = re.compile(DEFINE_RE + r'_default_patch_fuzz\s+(\d+)') + +# See https://bugzilla.redhat.com/488146 for details +indent_spaces_regex = re.compile('( \t|(^|\t)([^\t]{8})*[^\t]{4}[^\t]?([^\t][^\t.!?]|[^\t]?[.!?] ) )') + +requires_regex = re.compile(r'^(?:Build)?(?:Pre)?Req(?:uires)?(?:\([^\)]+\))?:\s*(.*)', re.IGNORECASE) +provides_regex = re.compile(r'^Provides(?:\([^\)]+\))?:\s*(.*)', re.IGNORECASE) +obsoletes_regex = re.compile(r'^Obsoletes:\s*(.*)', re.IGNORECASE) +conflicts_regex = re.compile(r'^(?:Build)?Conflicts:\s*(.*)', re.IGNORECASE) + +compop_regex = re.compile(r'[<>=]') + +setup_regex = re.compile(r'%setup\b') # intentionally no whitespace before! +setup_q_regex = re.compile(r' -[A-Za-z]*q') +setup_t_regex = re.compile(r' -[A-Za-z]*T') +setup_ab_regex = re.compile(r' -[A-Za-z]*[ab]') +autosetup_regex = re.compile(r'^\s*%autosetup(\s.*|$)') +autosetup_n_regex = re.compile(r' -[A-Za-z]*N') +autopatch_regex = re.compile(r'^\s*%autopatch(?:\s|$)') + +filelist_regex = re.compile(r'\s+-f\s+\S+') +pkgname_regex = re.compile(r'\s+(?:-n\s+)?(\S+)') +tarball_regex = re.compile(r'\.(?:t(?:ar|[glx]z|bz2?)|zip)\b', re.IGNORECASE) + +python_setup_test_regex = re.compile(r'^[^#]*(setup.py test)') +python_module_def_regex = re.compile(r'^[^#]*%{\?!python_module:%define python_module()') +python_sitelib_glob_regex = re.compile(r'^[^#]*%{python_site(lib|arch)}/\*\s*$') + +UNICODE_NBSP = '\xa0' + + +def unversioned(deps): + """Yield unversioned dependency names from the given list.""" + for dep in deps: + if not dep[1]: + yield dep[0] + + +def contains_buildroot(line): + """Check if the given line contains use of rpm buildroot.""" + res = rpm_buildroot_regex.search(line) + if res and \ + (not res.group(1) or len(res.group(1)) % 2 == 0) and \ + (not res.group(2) or len(res.group(2)) % 2 != 0): + return True + return False + + +class SpecCheck(AbstractCheck): + """Contain check methods that catch errors and warnings in a specfile.""" + + def __init__(self, config, output): + super().__init__(config, output) + self._spec_file = None + self._spec_name = None + self.valid_groups = config.configuration['ValidGroups'] + self.output.error_details.update({'non-standard-group': + """The value of the Group tag in the package is not valid. Valid groups are: + '%s'.""" % ', '.join(self.valid_groups)}) + self.hardcoded_lib_path_exceptions_regex = re.compile(config.configuration['HardcodedLibPathExceptions']) + + # Default state + self.patches = {} + self.applied_patches = [] + self.applied_patches_ifarch = [] + self.patches_auto_applied = False + self.source_dir = False + self.buildroot = False + self.configure_linenum = None + self.configure_cmdline = '' + self.mklibname = False + self.is_lib_pkg = False + self.if_depth = 0 + self.ifarch_depth = -1 + self.depscript_override = False + self.depgen_disabled = False + self.patch_fuzz_override = False + self.indent_spaces = 0 + self.indent_tabs = 0 + self.section = {} + + self.current_section = 'package' + # None == main package + self.current_package = None + self.package_noarch = {} + + def check_source(self, pkg): + """Find specfile in SRPM and run spec file related checks.""" + wrong_spec = False + self._spec_file = None + self._spec_name = None + + # Check if a specfile exist in a specified path + for fname, pkgfile in pkg.files.items(): + if fname.endswith('.spec'): + self._spec_file = pkgfile.path + self._spec_name = pkgfile.name + if fname == pkg.name + '.spec': + wrong_spec = False + break + else: + wrong_spec = True + + # method call + self._check_no_spec_file(pkg) + self._check_invalid_spec_name(pkg, wrong_spec) + + if self._spec_file: + # check content of spec file + with Pkg.FakePkg(self._spec_file) as package: + self.check_spec(package) + + def check_spec(self, pkg): + """Find specfile in specified path and run spec file related checks.""" + self._spec_file = pkg.name + self._spec_file_dir = str(Path(self._spec_file).parent) + + # method call + self._check_non_utf8_spec_file(pkg) + + self.pkg = pkg + self.spec_only = isinstance(pkg, Pkg.FakePkg) + + spec_lines = readlines(self._spec_file) + # Analyse specfile line by line to check for (E)rrors or (W)arnings + # And initialize the SpecCheck instance for following checks + self._check_lines(spec_lines) + + # Run checks for whole package + self._check_no_buildroot_tag(pkg, self.buildroot) + self._check_no_s_section(pkg, self.section) + self._check_superfluous_clean_section(pkg, self.section) + self._check_more_than_one_changelog_section(pkg, self.section) + self._check_lib_package_without_mklibname(pkg, self.is_lib_pkg, self.mklibname) + self._check_descript_without_disabling_depgen(pkg, self.depscript_override, + self.depgen_disabled) + self._check_patch_fuzz_is_changed(pkg, self.patch_fuzz_override) + self._check_mixed_use_of_space_and_tabs(pkg, self.indent_spaces, self.indent_tabs) + self.check_ifarch_and_not_applied_patches(pkg, self.patches_auto_applied, self.patches, + self.applied_patches_ifarch, self.applied_patches) + + # Checks below require a real spec file + if not self._spec_file: + return + self._check_specfile_error(pkg) + self._check_invalid_url(pkg, rpm) + + def _check_no_spec_file(self, pkg): + """Check if no spec file is found in RPM meta data.""" + if not self._spec_file: + self.output.add_info('E', pkg, 'no-spec-file') + + def _check_invalid_spec_name(self, pkg, wrong_spec): + """Check if spec file has same name as the 'Name: ' tag.""" + if wrong_spec and self._spec_file: + self.output.add_info('E', pkg, 'invalid-spec-name') + + def _check_non_utf8_spec_file(self, pkg): + """Check if spec file has UTF-8 character encoding.""" + if self._spec_file and not Pkg.is_utf8(self._spec_file): + self.output.add_info('E', pkg, 'non-utf8-spec-file', + self._spec_name or self._spec_file) + + def _check_no_buildroot_tag(self, pkg, buildroot): + """Check if BuildRoot tag is used in the specfile.""" + if not buildroot: + self.output.add_info('W', pkg, 'no-buildroot-tag') + + def _check_no_s_section(self, pkg, section): + """Check if there is no (%prep, %build, %install) + in the specfile. + """ + for sec in ('prep', 'build', 'install'): + if not section.get(sec): + self.output.add_info('W', pkg, 'no-%%%s-section' % sec) + + def _check_superfluous_clean_section(self, pkg, section): + """Check for a superfluous %clean section in the specfile. + """ + if section.get('clean'): + self.output.add_info('E', pkg, 'superfluous-%clean-section') + + def _check_more_than_one_changelog_section(self, pkg, section): + """Check if specfile has more than one %changelog. + prep, build, install, check prevented by rpmbuild 4.4 + """ + if section.get('changelog', 0) > 1: + self.output.add_info('W', pkg, 'more-than-one-%changelog-section') + + def _check_lib_package_without_mklibname(self, pkg, is_lib_pkg, mklibname): + """Check if package name is built using %mklibname to allow lib64 and lib32 + coexistence. This check is specific to Mandriva and it's derivatives, + check issue #9 in rpm-software-management/rpmlint/issues + """ + if is_lib_pkg and not mklibname: + self.output.add_info('E', pkg, 'lib-package-without-%mklibname') + + def _check_descript_without_disabling_depgen(self, pkg, depscript_override, depgen_disabled): + """Check if specfile has %define _use_internal_dependency_generator set to 0 + to disable it, or does not have define __find_provides/requires. + """ + if depscript_override and not depgen_disabled: + self.output.add_info('W', pkg, 'depscript-without-disabling-depgen') + + def _check_patch_fuzz_is_changed(self, pkg, patch_fuzz_override): + """Check if specfile has internal patch fuzz was changed.""" + if patch_fuzz_override: + self.output.add_info('W', pkg, 'patch-fuzz-is-changed') + + def _check_mixed_use_of_space_and_tabs(self, pkg, indent_spaces, indent_tabs): + """Check if specfile has mixed uses of spaces and tabs.""" + if indent_spaces and indent_tabs: + pkg.current_linenum = max(indent_spaces, indent_tabs) + self.output.add_info('W', pkg, 'mixed-use-of-spaces-and-tabs', + '(spaces: line %d, tab: line %d)' % + (indent_spaces, indent_tabs)) + pkg.current_linenum = None + + def check_ifarch_and_not_applied_patches(self, pkg, patches_auto_applied, + patches, applied_patches_ifarch, applied_patches): + """Check if specfile has a patch applied inside an %ifarch block. + and check if a patch was included but not applied.""" + if not patches_auto_applied: + for pnum, pfile in patches.items(): + if pnum in applied_patches_ifarch: + self.output.add_info('W', pkg, '%ifarch-applied-patch', + 'Patch%d:' % pnum, pfile) + + # Check if a patch is included in specfile but was not applied. + if pnum not in applied_patches: + self.output.add_info('W', pkg, 'patch-not-applied', + 'Patch%d:' % pnum, pfile) + + def _check_specfile_error(self, pkg): + """It parse the specfile with rpm and forward errors to rpmlint output.""" + + # We'd like to parse the specfile only once using python bindings, + # but it seems errors from rpmlib get logged to stderr and we can't + # capture and print them nicely, so we do it once each way :P + try: + outcmd = subprocess.run( + ('rpm', '-q', '--qf=', '-D', '_sourcedir %s' % self._spec_file_dir, + '--specfile', self._spec_file), stderr=subprocess.PIPE, encoding='utf8', env=ENGLISH_ENVIROMENT) + + for line in outcmd.stderr.splitlines(): + line = line.strip() + if line and 'warning:' not in line: + self.output.add_info('E', pkg, 'specfile-error', line) + except UnicodeDecodeError as e: + self.output.add_info('E', pkg, 'specfile-error', str(e)) + + def _check_invalid_url(self, pkg, rpm): + """Check if specfile has an invalid url.""" + # grab sources and patches from parsed spec object to get + # them with macros expanded for URL checking + spec_obj = None + rpm.addMacro('_sourcedir', self._spec_file_dir) + try: + transaction_set = rpm.TransactionSet() + spec_obj = transaction_set.parseSpec(str(self._spec_file)) + except (ValueError, rpm.error) as e: + self.output.add_info('E', pkg, 'specfile-error', str(e).strip(), + str(self._spec_file)) + rpm.delMacro('_sourcedir') + if spec_obj: + for src in spec_obj.sources: + (url, num, flags) = src + (scheme, netloc) = urlparse(url)[0:2] + if flags & 1: # rpmspec.h, rpm.org ticket #123 + srctype = 'Source' + else: + srctype = 'Patch' + tag = f'{srctype}{num}' + if scheme and netloc: + continue + elif srctype == 'Source' and tarball_regex.search(url): + self.output.add_info('W', pkg, 'invalid-url', '%s:' % tag, url) + + def _check_lines(self, lines): + # gather info from spec lines + self.pkg.current_linenum = 0 + for line in lines: + self.pkg.current_linenum += 1 + self._check_line(line) + # Last line read is not useful after this point + self.pkg.current_linenum = None + + def _check_line(self, line): + """ + Run check methods for this line. + """ + + self._checkline_break_space(line) + if self._checkline_section(line): + return + self._checkline_buildroot_usage(line) + self._checkline_make_check(line) + self._checkline_setup(line) + self._checkline_autopatch(line) + self._checkline_applied_patch(line) + self._checkline_sourcedir(line) + self._checkline_configure(line) + self._checkline_hardcoded_library_path(line) + self._checkline_mklibname(line) + self._checkline_package(line) + self._checkline_changelog(line) + self._checkline_files(line) + self._checkline_indent(line) + self._checkline_deprecated_grep(line) + self._checkline_valid_groups(line) + self._checkline_macros_in_comments(line) + self._checkline_python_setup_test(line) + self._checkline_python_module_def(line) + self._checkline_python_sitelib_glob(line) + + # If statement, starts + if ifarch_regex.search(line): + self.if_depth = self.if_depth + 1 + self.ifarch_depth = self.if_depth + elif if_regex.search(line): + self.if_depth = self.if_depth + 1 + + # If statement, ends + elif endif_regex.search(line): + if self.ifarch_depth == self.if_depth: + self.ifarch_depth = -1 + self.if_depth = self.if_depth - 1 + + # line checks methods + + def _checkline_break_space(self, line): + char = line.find(UNICODE_NBSP) + if char != -1: + self.output.add_info('W', self.pkg, 'non-break-space', 'line %s, char %d' % + (self.pkg.current_linenum, char)) + + def _checkline_section(self, line): + section_marker = False + for sec, regex in section_regexs.items(): + res = regex.search(line) + if res: + self.current_section = sec + section_marker = True + self.section[sec] = self.section.get(sec, 0) + 1 + if sec in ('package', 'files'): + rest = filelist_regex.sub('', line[res.end() - 1:]) + res = pkgname_regex.search(rest) + if res: + self.current_package = res.group(1) + else: + self.current_package = None + break + + if section_marker: + if not self.is_lib_pkg and lib_package_regex.search(line): + self.is_lib_pkg = True + return True + + def _checkline_buildroot_usage(self, line): + if (self.current_section in Pkg.RPM_SCRIPTLETS + ('prep', 'build') and + contains_buildroot(line)): + self.output.add_info('E', self.pkg, 'rpm-buildroot-usage', '%' + self.current_section, + line[:-1].strip()) + + def _checkline_make_check(self, line): + if make_check_regex.search(line) and self.current_section not in \ + ('check', 'changelog', 'package', 'description'): + self.output.add_info('W', self.pkg, 'make-check-outside-check-section', + line[:-1]) + + def _checkline_setup(self, line): + # %setup check + 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 setup_ab_regex.search(line): + self.output.add_info('W', self.pkg, 'setup-not-quiet') + else: + self.output.add_info('W', self.pkg, 'setup-not-quiet') + + if self.current_section != 'prep': + self.output.add_info('W', self.pkg, 'setup-not-in-prep') + return + + res = autosetup_regex.search(line) + if res: + if not autosetup_n_regex.search(res.group(1)): + self.patches_auto_applied = True + if self.current_section != 'prep': + self.output.add_info('W', self.pkg, '%autosetup-not-in-prep') + + def _checkline_autopatch(self, line): + # %autopach check + if autopatch_regex.search(line): + self.patches_auto_applied = True + if self.current_section != 'prep': + self.output.add_info('W', self.pkg, '%autopatch-not-in-prep') + + def _checkline_applied_patch(self, line): + # Check for %patch -P + res = applied_patch_regex.search(line) + if res: + pnum = res.group(1) or 0 + for tmp in applied_patch_p_regex.findall(line) or [pnum]: + pnum = int(tmp) + self.applied_patches.append(pnum) + if self.ifarch_depth > 0: + self.applied_patches_ifarch.append(pnum) + return + + # Check for %{PATCH0} | patch + res = applied_patch_pipe_regex.search(line) + if res: + pnum = int(res.group(1)) + self.applied_patches.append(pnum) + if self.ifarch_depth > 0: + self.applied_patches_ifarch.append(pnum) + return + + # Check for patch < %{PATCH0} + res = applied_patch_i_regex.search(line) + if res: + pnum = int(res.group(1)) + self.applied_patches.append(pnum) + if self.ifarch_depth > 0: + self.applied_patches_ifarch.append(pnum) + return + + def _checkline_sourcedir(self, line): + if self.source_dir: + return + + res = source_dir_regex.search(line) + if res: + self.source_dir = True + self.output.add_info('E', self.pkg, 'use-of-RPM_SOURCE_DIR') + + def _checkline_configure(self, line): + if self.configure_linenum: + if self.configure_cmdline[-1] == '\\': + self.configure_cmdline = self.configure_cmdline[:-1] + line.strip() + else: + res = configure_libdir_spec_regex.search(self.configure_cmdline) + if not res: + # Hack to get the correct (start of ./configure) line + # number displayed: + real_linenum = self.pkg.current_linenum + self.pkg.current_linenum = self.configure_linenum + self.output.add_info('W', self.pkg, 'configure-without-libdir-spec') + self.pkg.current_linenum = real_linenum + elif res.group(1): + res = re.match(hardcoded_library_paths, res.group(1)) + if res: + self.output.add_info('E', self.pkg, 'hardcoded-library-path', + res.group(1), 'in configure options') + self.configure_linenum = None + + hash_pos = line.find('#') + + if self.current_section != 'changelog': + cfg_pos = line.find('./configure') + if cfg_pos != -1 and (hash_pos == -1 or hash_pos > cfg_pos): + # store line where it started + self.configure_linenum = self.pkg.current_linenum + self.configure_cmdline = line.strip() + + def _checkline_hardcoded_library_path(self, line): + res = hardcoded_library_path_regex.search(line) + if self.current_section != 'changelog' and res and not \ + (biarch_package_regex.match(self.pkg.name) or + self.hardcoded_lib_path_exceptions_regex.search( + res.group(1).lstrip())): + self.output.add_info('E', self.pkg, 'hardcoded-library-path', 'in', + res.group(1).lstrip()) + + def _checkline_mklibname(self, line): + self.mklibname = '%mklibname' in line + + # line checks package methods + def _checkline_package_patch(self, line): + # Would be cleaner to get sources and patches from the + # specfile parsed in Python (see below), but we want to + # catch %ifarch'd etc ones as well, and also catch these when + # the specfile is not parseable. + res = patch_regex.search(line) + if res: + pnum = int(res.group(1) or 0) + self.patches[pnum] = res.group(2) + + def _checkline_package_obsolete_tags(self, line): + res = obsolete_tags_regex.search(line) + if res: + self.output.add_info('W', self.pkg, 'obsolete-tag', res.group(1)) + + def _checkline_package_buildroot(self, line): + res = buildroot_regex.search(line) + if res: + self.buildroot = True + if res.group(1).startswith('/'): + self.output.add_info('W', self.pkg, 'hardcoded-path-in-buildroot-tag', + res.group(1)) + + def _checkline_package_buildarch(self, line): + res = buildarch_regex.search(line) + if res: + if res.group(1) != 'noarch': + self.output.add_info('E', self.pkg, + 'buildarch-instead-of-exclusivearch-tag', + res.group(1)) + else: + self.package_noarch[self.current_package] = True + + def _checkline_package_packager(self, line): + res = packager_regex.search(line) + if res: + self.output.add_info('W', self.pkg, 'hardcoded-packager-tag', res.group(1)) + + def _checkline_package_prefix(self, line): + res = prefix_regex.search(line) + if res and not res.group(1).startswith('%'): + self.output.add_info('W', self.pkg, 'hardcoded-prefix-tag', res.group(1)) + + def _checkline_package_prereq(self, line): + res = prereq_regex.search(line) + if res: + self.output.add_info('E', self.pkg, 'prereq-use', res.group(2)) + + def _checkline_package_buildprereq(self, line): + res = buildprereq_regex.search(line) + if res: + self.output.add_info('E', self.pkg, 'buildprereq-use', res.group(1)) + + def _checkline_package_requires(self, line): + res = requires_regex.search(line) + if res: + reqs = Pkg.parse_deps(res.group(1)) + deptoken = Pkg.has_forbidden_controlchars(reqs) + if deptoken: + self.output.add_info('E', self.pkg, + 'forbidden-controlchar-found', + f'Requires: {deptoken}') + for req in unversioned(reqs): + if compop_regex.search(req): + self.output.add_info('W', self.pkg, + 'comparison-operator-in-deptoken', + req) + + def _checkline_package_provides(self, line): + res = provides_regex.search(line) + if res: + provs = Pkg.parse_deps(res.group(1)) + deptoken = Pkg.has_forbidden_controlchars(provs) + if deptoken: + self.output.add_info('E', self.pkg, + 'forbidden-controlchar-found', + f'Provides: {deptoken}') + for prov in unversioned(provs): + if not prov.startswith('/'): + self.output.add_info('W', self.pkg, 'unversioned-explicit-provides', + prov) + if compop_regex.search(prov): + self.output.add_info('W', self.pkg, + 'comparison-operator-in-deptoken', + prov) + + def _checkline_package_obsoletes(self, line): + res = obsoletes_regex.search(line) + if res: + obses = Pkg.parse_deps(res.group(1)) + deptoken = Pkg.has_forbidden_controlchars(obses) + if deptoken: + self.output.add_info('E', self.pkg, + 'forbidden-controlchar-found', + f'Obsoletes: {deptoken}') + for obs in unversioned(obses): + if not obs.startswith('/'): + self.output.add_info('W', self.pkg, 'unversioned-explicit-obsoletes', + obs) + if compop_regex.search(obs): + self.output.add_info('W', self.pkg, + 'comparison-operator-in-deptoken', + obs) + + def _checkline_package_conflicts(self, line): + res = conflicts_regex.search(line) + if res: + confs = Pkg.parse_deps(res.group(1)) + deptoken = Pkg.has_forbidden_controlchars(confs) + if deptoken: + self.output.add_info('E', self.pkg, + 'forbidden-controlchar-found', + f'Conflicts: {deptoken}') + for conf in unversioned(confs): + if compop_regex.search(conf): + self.output.add_info('W', self.pkg, + 'comparison-operator-in-deptoken', + conf) + + def _checkline_package(self, line): + if self.current_section != 'package': + return + + self._checkline_package_patch(line) + self._checkline_package_obsolete_tags(line) + self._checkline_package_buildroot(line) + self._checkline_package_buildarch(line) + self._checkline_package_packager(line) + self._checkline_package_prefix(line) + self._checkline_package_prereq(line) + self._checkline_package_buildprereq(line) + self._checkline_package_requires(line) + self._checkline_package_provides(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) + if deptoken: + self.output.add_info('E', self.pkg, + 'forbidden-controlchar-found', + '%%changelog: %s' % deptoken) + for match in self.macro_regex.findall(line): + res = re.match('%+', match) + if len(res.group(0)) % 2 and match != '%autochangelog': + self.output.add_info('W', self.pkg, 'macro-in-%changelog', match) + else: + if not self.depscript_override: + self.depscript_override = \ + depscript_override_regex.search(line) is not None + if not self.depgen_disabled: + self.depgen_disabled = \ + depgen_disable_regex.search(line) is not None + if not self.patch_fuzz_override: + self.patch_fuzz_override = \ + patch_fuzz_override_regex.search(line) is not None + + def _checkline_files(self, line): + # TODO: check scriptlets for these too? + if (self.current_section == 'files' and + (self.package_noarch.get(self.current_package) or + (self.current_package not in self.package_noarch and self.package_noarch.get(None)))): + res = libdir_regex.search(line) + if res: + pkgname = self.current_package + if pkgname is None: + pkgname = '(main package)' + self.output.add_info('W', self.pkg, 'libdir-macro-in-noarch-package', + pkgname, line.rstrip()) + + def _checkline_indent(self, line): + if not self.indent_tabs and '\t' in line: + self.indent_tabs = self.pkg.current_linenum + if not self.indent_spaces and indent_spaces_regex.search(line): + self.indent_spaces = self.pkg.current_linenum + + def _checkline_deprecated_grep(self, line): + # Check if egrep or fgrep is used + if self.current_section not in \ + ('package', 'changelog', 'description', 'files'): + greps = deprecated_grep_regex.findall(line) + if greps: + self.output.add_info('W', self.pkg, 'deprecated-grep', greps) + + def _checkline_valid_groups(self, line): + # If not checking spec file only, we're checking one inside a + # SRPM -> skip this check to avoid duplicate warnings (#167) + + if self.spec_only and self.valid_groups and \ + line.lower().startswith('group:'): + group = line[6:].strip() + if group not in self.valid_groups: + self.output.add_info('W', self.pkg, 'non-standard-group', group) + + def _checkline_macros_in_comments(self, line): + hash_pos = line.find('#') + # Test if there are macros in comments + if hash_pos != -1 and \ + (hash_pos == 0 or line[hash_pos - 1] in (' ', '\t')): + + 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) + + def _checkline_python_setup_test(self, line): + # Test if the "python setup.py test" deprecated subcommand is used + if self.current_section == 'check' and python_setup_test_regex.search(line): + self.output.add_info('W', self.pkg, 'python-setup-test', line[:-1]) + + def _checkline_python_module_def(self, line): + """ + Test if the "python_module" macro is defined in the spec file + This macro was in py2pack but now it should be provided by + python-rpm-macros + """ + if python_module_def_regex.search(line): + self.output.add_info('W', self.pkg, 'python-module-def', line[:-1]) + + def _checkline_python_sitelib_glob(self, line): + """Test if %{python_sitelib}/* is present in %files section.""" + if self.current_section != 'files': + return + + 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') diff --git a/rpmlint/checks/SysVInitOnSystemdCheck.py b/rpmlint/checks/SysVInitOnSystemdCheck.py new file mode 100644 index 0000000..5cada5d --- /dev/null +++ b/rpmlint/checks/SysVInitOnSystemdCheck.py @@ -0,0 +1,50 @@ +from pathlib import Path + +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class SysVInitOnSystemdCheck(AbstractCheck): + def __init__(self, config, output): + super().__init__(config, output) + self.initscripts = set() + self.bootscripts = set() + self.systemdscripts = set() + + def check(self, pkg): + if pkg.is_source: + return + + self._find_services_and_scripts(pkg) + + for req in pkg.requires + pkg.prereq: + if req[0] == 'insserv': + self.output.add_info('E', pkg, 'obsolete-insserv-requirement') + + for filename in self.bootscripts: + self.output.add_info('E', pkg, 'deprecated-boot-script', filename) + for filename in self.initscripts: + self.output.add_info('E', pkg, 'deprecated-init-script', filename) + for filename in self.initscripts: + if filename in self.systemdscripts: + self.output.add_info('E', pkg, 'systemd-shadowed-initscript', filename) + + def _find_services_and_scripts(self, pkg): + # Find all regular systemd services and initscripts + for filename, _pkgfile in pkg.files.items(): + if filename in pkg.ghost_files: + continue + + if filename.startswith('/usr/lib/systemd/system/'): + basename = Path(filename).name + # @ means it is socket service which is not what we look for + if '@' in filename: + continue + if filename.endswith('.service') or filename.endswith('.target'): + self.systemdscripts.add(basename.rpartition('.')[0]) + + if filename.startswith('/etc/init.d/') or filename.startswith('/etc/rc.d/init.d'): + basename = Path(filename).name + if basename.startswith('boot.'): + self.bootscripts.add(basename) + elif not basename.startswith('rc'): + self.initscripts.add(basename) diff --git a/rpmlint/checks/TagsCheck.py b/rpmlint/checks/TagsCheck.py new file mode 100644 index 0000000..64088f5 --- /dev/null +++ b/rpmlint/checks/TagsCheck.py @@ -0,0 +1,778 @@ +import calendar +from pathlib import Path +import re +import time +from urllib.parse import urlparse + +import rpm +from rpmlint import pkg as Pkg +from rpmlint.checks import FilesCheck +from rpmlint.checks.AbstractCheck import AbstractCheck +from rpmlint.helpers import byte_to_string +from rpmlint.spellcheck import Spellcheck + +CAPITALIZED_IGNORE_LIST = ('jQuery', 'openSUSE', 'wxWidgets', 'a', 'an', 'uWSGI') + +changelog_version_regex = re.compile(r'[^>]([^ >]+)\s*$') +changelog_text_version_regex = re.compile(r'^\s*-\s*((\d+:)?[\w\.]+-[\w\.]+)') +devel_number_regex = re.compile(r'(.*?)([0-9.]+)(_[0-9.]+)?-devel') +lib_devel_number_regex = re.compile(r'^lib(.*?)([0-9.]+)(_[0-9.]+)?-devel') +lib_package_regex = re.compile(r'(?:^(?:compat-)?lib.*?(\.so.*)?|libs?[\d-]*)$', re.IGNORECASE) +leading_space_regex = re.compile(r'^\s+') +pkg_config_regex = re.compile(r'^/usr/(?:lib\d*|share)/pkgconfig/') +license_regex = re.compile(r'\(([^)]+)\)|\s(?:and|or|AND|OR)\s') +license_exception_regex = re.compile(r'([^(\s]+)\s(?:WITH|with)\s([^)\s]+)') +invalid_version_regex = re.compile(r'([0-9](?:rc|alpha|beta|pre).*)', re.IGNORECASE) +# () are here for grouping purpose in the regexp +tag_regex = re.compile(r'^((?: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) +punct = '.,:;!?' +so_dep_regex = re.compile(r'\.so(\.[0-9a-zA-Z]+)*(\([^)]*\))*$') +# we assume that no rpm packages existed before rpm itself existed... +oldest_changelog_timestamp = calendar.timegm(time.strptime('1995-01-01', '%Y-%m-%d')) + + +class TagsCheck(AbstractCheck): + def __init__(self, config, output): + super().__init__(config, output) + self.valid_groups = config.configuration['ValidGroups'] + self.valid_licenses = config.configuration['ValidLicenses'] + self.invalid_requires = map(re.compile, config.configuration['InvalidRequires']) + self.packager_regex = re.compile(config.configuration['Packager']) + self.release_ext = config.configuration['ReleaseExtension'] + self.extension_regex = self.release_ext and re.compile(self.release_ext) + self.use_version_in_changelog = config.configuration['UseVersionInChangelog'] + self.invalid_url_regex = re.compile(config.configuration['InvalidURL'], re.IGNORECASE) + self.forbidden_words_regex = re.compile(r'(%s)' % config.configuration['ForbiddenWords'], re.IGNORECASE) + self.valid_buildhost_regex = re.compile(config.configuration['ValidBuildHost']) + self.use_epoch = config.configuration['UseEpoch'] + self.max_line_len = config.configuration['MaxLineLength'] + self.spellcheck = config.configuration['UseEnchant'] + self.valid_license_exceptions = config.configuration['ValidLicenseExceptions'] + if self.spellcheck: + self.spellchecker = Spellcheck() + + for i in ('obsoletes', 'conflicts', 'provides', 'recommends', 'suggests', + 'enhances', 'supplements'): + self.output.error_details.update({f'no-epoch-in-{i}': + 'Your package contains a versioned %s entry without an Epoch.' + % i.capitalize()}) + self.output.error_details.update({'non-standard-group': + """The value of the Group tag in the package is not valid. Valid groups are: + '%s'.""" % ', '.join(self.valid_groups), + 'not-standard-release-extension': + 'Your release tag must match the regular expression ' + self.release_ext + '.', + 'summary-too-long': + "The 'Summary:' must not exceed %d characters." % self.max_line_len, + 'description-line-too-long': + """Your description lines must not exceed %d characters. If a line is exceeding + this number, cut it to fit in two lines.""" % self.max_line_len, + 'invalid-license': + """The value of the License tag was not recognized. Known values are: + '%s'.""" % ', '.join(self.valid_licenses), + }) + + def _unexpanded_macros(self, pkg, tagname, value, is_url=False): + if not value: + return + if not isinstance(value, (list, tuple)): + value = [value] + for val in value: + for match in self.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 + self.output.add_info('W', pkg, 'unexpanded-macro', tagname, match) + + def check(self, pkg): + """Contains methods that checks tags and values in a spec file of a package.""" + + version = pkg[rpm.RPMTAG_VERSION] + release = pkg[rpm.RPMTAG_RELEASE] + epoch = pkg[rpm.RPMTAG_EPOCH] + group = pkg[rpm.RPMTAG_GROUP] + buildhost = pkg[rpm.RPMTAG_BUILDHOST] + langs = pkg[rpm.RPMTAG_HEADERI18NTABLE] + summary = byte_to_string(pkg[rpm.RPMTAG_SUMMARY]) + description = byte_to_string(pkg[rpm.RPMTAG_DESCRIPTION]) + changelog = pkg[rpm.RPMTAG_CHANGELOGNAME] + rpm_license = pkg[rpm.RPMTAG_LICENSE] + name = pkg.name + deps = pkg.requires + pkg.prereq + is_devel = FilesCheck.devel_regex.search(name) + is_source = pkg.is_source + + # List of words to ignore in spell check + ignored_words = set() + for pf in pkg.files: + ignored_words.update(pf.split('/')) + for tag in ('provides', 'requires', 'conflicts', 'obsoletes'): + ignored_words.update(x[0] for x in 'pkg.' + str(tag)) + + # Run checks for whole package + self._check_invalid_packager(pkg) + self._check_invalid_version_and_no_version_tag(pkg, version) + self._check_non_standard_release_extension(pkg, release) + self._check_no_epoch_tag(pkg, epoch) + self._check_no_epoch_in_tags(pkg) + self._check_multiple_dependencies(pkg, deps, is_devel, is_source) + self._unexpanded_macros(pkg, 'Name', name) + self._check_multiple_tags(pkg, name, is_devel, is_source, deps, epoch, version) + self._check_summary_tag(pkg, summary, langs, ignored_words) + self._check_description_tag(pkg, description, langs, ignored_words) + self._check_group_tag(pkg, group) + self._check_buildhost_tag(pkg, buildhost) + self._check_changelog_tag(pkg, changelog, version, release, name, epoch) + self._check_license(pkg, rpm_license) + self._check_url(pkg) + + prov_names = [x[0] for x in pkg.provides] + + self._check_obsolete_not_provided(pkg, prov_names) + + for dep_token in pkg.obsoletes: + value = Pkg.formatRequire(*dep_token) + self._unexpanded_macros(pkg, f'Obsoletes {value}', value) + + self._check_useless_provides(pkg, pkg.provides) + self._check_forbidden_controlchar(pkg) + self._check_self_obsoletion(pkg) + self._check_non_coherent_filename(pkg) + + for tag in ('Distribution', 'DistTag', 'ExcludeArch', 'ExcludeOS', + 'Vendor'): + if hasattr(rpm, 'RPMTAG_%s' % tag.upper()): + res = byte_to_string(pkg[getattr(rpm, 'RPMTAG_%s' % tag.upper())]) + self._unexpanded_macros(pkg, tag, res) + + def check_description(self, pkg, lang, ignored_words): + description = pkg.langtag(rpm.RPMTAG_DESCRIPTION, lang) + description = byte_to_string(description) + self._unexpanded_macros(pkg, '%%description -l %s' % lang, description) + if self.spellcheck: + pkgname = byte_to_string(pkg.header[rpm.RPMTAG_NAME]) + typos = self.spellchecker.spell_check(description, '%description -l {}', lang, pkgname, ignored_words) + for typo in typos.items(): + self.output.add_info('E', pkg, 'spelling-error', typo) + for i in description.splitlines(): + if len(i) > self.max_line_len: + self.output.add_info('E', pkg, 'description-line-too-long', self._lang_for_error(lang), i) + res = self.forbidden_words_regex.search(i) + if res and self.config.configuration['ForbiddenWords']: + self.output.add_info('W', pkg, 'description-use-invalid-word', self._lang_for_error(lang), + res.group(1)) + res = tag_regex.search(i) + if res: + self.output.add_info('W', pkg, 'tag-in-description', self._lang_for_error(lang), res.group(1)) + + def _lang_for_error(self, lang): + return lang if lang != 'C' and lang != 'C.UTF-8' else None + + def check_summary(self, pkg, lang, ignored_words): + summary = pkg.langtag(rpm.RPMTAG_SUMMARY, lang) + summary = byte_to_string(summary) + self._unexpanded_macros(pkg, 'Summary(%s)' % lang, summary) + if self.spellcheck: + pkgname = byte_to_string(pkg.header[rpm.RPMTAG_NAME]) + typos = self.spellchecker.spell_check(summary, 'Summary({})', lang, pkgname, ignored_words) + for typo in typos.items(): + self.output.add_info('E', pkg, 'spelling-error', typo) + if any(nl in summary for nl in ('\n', '\r')): + self.output.add_info('E', pkg, 'summary-on-multiple-lines', self._lang_for_error(lang)) + if (summary[0] != summary[0].upper() and + summary.partition(' ')[0] not in CAPITALIZED_IGNORE_LIST): + self.output.add_info('W', pkg, 'summary-not-capitalized', self._lang_for_error(lang), summary) + if summary[-1] == '.': + self.output.add_info('W', pkg, 'summary-ended-with-dot', self._lang_for_error(lang), summary) + if len(summary) > self.max_line_len: + self.output.add_info('E', pkg, 'summary-too-long', self._lang_for_error(lang), summary) + if leading_space_regex.search(summary): + self.output.add_info('E', pkg, 'summary-has-leading-spaces', self._lang_for_error(lang), summary) + res = self.forbidden_words_regex.search(summary) + if res and self.config.configuration['ForbiddenWords']: + self.output.add_info('W', pkg, 'summary-use-invalid-word', self._lang_for_error(lang), res.group(1)) + if pkg.name: + sepchars = r'[\s%s]' % punct + res = re.search(r'(?:^|\s)(%s)(?:%s|$)' % + (re.escape(pkg.name), sepchars), + summary, re.IGNORECASE | re.UNICODE) + if res: + self.output.add_info('W', pkg, 'name-repeated-in-summary', self._lang_for_error(lang), + res.group(1)) + + def _check_invalid_packager(self, pkg): + """Trigger invalid-packager and no-packager-tag + + The packager email must end with an email compatible with the Packager + option of rpmlint. Please change it and rebuild your package. + + Args: + pkg: Variable used to store package name in STDOUT + + Returns: + Output info to STDOUT + """ + + packager = pkg[rpm.RPMTAG_PACKAGER] + if packager: + self._unexpanded_macros(pkg, 'Packager', packager) + if self.config.configuration['Packager'] and \ + not self.packager_regex.search(packager): + self.output.add_info('W', pkg, 'invalid-packager', packager) + else: + self.output.add_info('E', pkg, 'no-packager-tag') + + def _check_invalid_version_and_no_version_tag(self, pkg, version): + """Trigger check invalid-version, no-version-tag. + + Args: + version: Variable used to find Version: value tag in rpm package + + Returns: + Output info to STDOUT + """ + + if version: + self._unexpanded_macros(pkg, 'Version', version) + res = invalid_version_regex.search(version) + # Check if a package has a version tag value start with + # pre, alpha, beta or rc suffixes + if res: + self.output.add_info('E', pkg, 'invalid-version', version) + # Check if a package has no Version: tag in its spec file + else: + self.output.add_info('E', pkg, 'no-version-tag') + + def _check_non_standard_release_extension(self, pkg, release): + """Trigger check not-standard-release-extension, no-release-tag + + Args: + release: Variable checks Realease: tag value + + Returns: + Output info to STDOUT + """ + if release: + self._unexpanded_macros(pkg, 'Release', release) + # [This check is dynamically produced] + # Check if the release tag matches the regex expression self.release_ext + if self.release_ext and not self.extension_regex.search(release): + self.output.add_info('W', pkg, 'not-standard-release-extension', release) + # Check if there is no Release tag in spec file + else: + self.output.add_info('E', pkg, 'no-release-tag') + + def _check_no_epoch_tag(self, pkg, epoch): + """Trigger check no-epoch-tag, unreasonable-epoch + + Args: + epoch: Finds the Epoch: tag + + Returns: + Output info to STDOUT + """ + if epoch is None: + # Check if a package does not contain an Epoch: tag + if self.use_epoch: + self.output.add_info('E', pkg, 'no-epoch-tag') + else: + # Check if a package has an Epoch: value of greater than 99 + if epoch > 99: + self.output.add_info('W', pkg, 'unreasonable-epoch', epoch) + + def _check_no_epoch_in_tags(self, pkg): + """Trigger check no-epoch-in-{} multiple tags + + Check if versioned dependency is not used in tags even when + UseEpoch is set to true and trigger checks in tags + ['Obsoletes', 'Conflicts', 'Provides', 'Recommends', + 'Suggests', 'Enhances', 'Supplements'] + + Returns: + Output info to STDOUT + """ + if self.use_epoch: + for tag in ('obsoletes', 'conflicts', 'provides', 'recommends', + 'suggests', 'enhances', 'supplements'): + for x in (x for x in getattr(pkg, tag)() + if x[1] and x[2][0] is None): + self.output.add_info('W', pkg, f'no-epoch-in-{tag}', + Pkg.formatRequire(*x)) + + def _check_multiple_dependencies(self, pkg, deps, is_source, is_devel): + """Contain multiple check, no-epoch-in-dependency, invalid-dependency, + invalid-build-requires, devel-dependency, explicit-devel-dependency + + Args: + deps: Variable to find PreReq and Requires tag + is_source: Variable to check if a package is of source type + is_devel: The param to check if a package name ends with *-devel + + Returns: + Output info to STDOUT + example: + tmp.x86_64: W: requires-on-release foo = 2.1-1 + """ + + devel_depend = False + for dep in deps: + value = Pkg.formatRequire(*dep) + # Check if a package has a versioned dependency in spec file without Epoch: tag + if self.use_epoch and dep[1] and dep[2][0] is None and \ + not dep[0].startswith('rpmlib('): + self.output.add_info('W', pkg, 'no-epoch-in-dependency', value) + # Check if a package has a invalid-dependency in spec file + for req in self.invalid_requires: + if req.search(dep[0]): + self.output.add_info('E', pkg, 'invalid-dependency', dep[0]) + + # Check if a dependency requirement starts with /usr/local + # For Ex:- Requires: /usr/local/something + if dep[0].startswith('/usr/local/'): + self.output.add_info('E', pkg, 'invalid-dependency', dep[0]) + + # Check if a package contains a dependency whose name is not docile with + # lib64 naming standards. + if is_source: + if lib_devel_number_regex.search(dep[0]): + self.output.add_info('E', pkg, 'invalid-build-requires', dep[0]) + + # Check if a package containing a devel dependency + # is not a devel package itself + elif not is_devel: + if not devel_depend and FilesCheck.devel_regex.search(dep[0]): + self.output.add_info('E', pkg, 'devel-dependency', dep[0]) + devel_depend = True + if not dep[1]: + res = lib_package_regex.search(dep[0]) + # Check if a package cannot find the lib dependencies by itself + # without the packager using explicit Requires: TagsCheck + # For Ex:- Requires: lib* + if res and not res.group(1): + self.output.add_info('E', pkg, 'explicit-lib-dependency', dep[0]) + + # Check if a package requires a specfic version of another package. + # For Ex:- Requires: python==3.8 + if dep[1] == rpm.RPMSENSE_EQUAL and dep[2][2] is not None: + self.output.add_info('W', pkg, 'requires-on-release', value) + self._unexpanded_macros(pkg, f'dependency {value}', value) + + def _check_multiple_tags(self, pkg, name, is_devel, + is_source, deps, epoch, version): + """Trigger checks no-name-tag check, + no-version-dependency-on, missing-dependency-on, + no-major-in-name, no-provides, no-pkg-config-provides + + Args: + name: Variable to find if Name: tag + + Returns: + Output info to STDOUT + """ + + if not name: + # Check if a package does not have a Name: tag + self.output.add_info('E', pkg, 'no-name-tag') + else: + if is_devel and not is_source: + base = is_devel.group(1) + dep = None + has_so = False + has_pc = False + for fname in pkg.files: + if fname.endswith('.so'): + has_so = True + if pkg_config_regex.match(fname) and fname.endswith('.pc'): + has_pc = True + if has_so: + base_or_libs = base + '*' + '/' + base + '-libs/lib' + base + '*' + # try to match *%_isa as well (e.g. '(x86-64)', '(x86-32)') + base_or_libs_re = re.compile( + r'^(lib)?%s(-libs)?[\d_-]*(\(\w+-\d+\))?$' % re.escape(base)) + for d in deps: + if base_or_libs_re.match(d[0]): + dep = d + break + if dep and version: + exp = (epoch, version, None) + sexp = Pkg.versionToString(exp) + if not dep[1]: + self.output.add_info('W', pkg, 'no-version-dependency-on', + base_or_libs, sexp) + elif dep[2][:2] != exp[:2]: + version = Pkg.versionToString((dep[2][0], dep[2][1], None)) + self.output.add_info('W', pkg, + 'missing-dependency-on', + f'{base_or_libs} = {version}') + res = devel_number_regex.search(name) + if not res: + self.output.add_info('W', pkg, 'no-major-in-name', name) + else: + if res.group(3): + prov = res.group(1) + res.group(2) + '-devel' + else: + prov = res.group(1) + '-devel' + + if prov not in (x[0] for x in pkg.provides): + self.output.add_info('W', pkg, 'no-provides', prov) + + if has_pc: + found_pkg_config_dep = False + for p in (x[0] for x in pkg.provides): + if p.startswith('pkgconfig('): + found_pkg_config_dep = True + break + if not found_pkg_config_dep: + self.output.add_info('E', pkg, 'no-pkg-config-provides') + + def _check_summary_tag(self, pkg, summary, langs, ignored_words): + """Trigger check no-summary-tag + + Check if a package does not have a summary tag + + Args: + summary: Variable to find Summary: tag + langs: + Variable to find RPMTAG_HEADERI18NTABLE which + Contains a list of locales for which strings are provided in other parts of + the package. + ignored_words: Find ignored words list in the Require: tag + + Returns: + Output info to STDOUT + """ + if summary: + if not langs: + self._unexpanded_macros(pkg, 'Summary', summary) + else: + for lang in langs: + self.check_summary(pkg, lang, ignored_words) + else: + self.output.add_info('E', pkg, 'no-summary-tag') + + def _check_description_tag(self, pkg, description, langs, ignored_words): + """Trigger check description-shorter-than-summary, no-description-tag + + Args: + description: Find %description tag in package + + Returns: + Output info to STDOUT + """ + if description: + if not langs: + self._unexpanded_macros(pkg, '%description', description) + else: + for lang in langs: + self.check_description(pkg, lang, ignored_words) + + # Check if a package has a description shorter than Summary + if len(description) < len(pkg[rpm.RPMTAG_SUMMARY]): + self.output.add_info('W', pkg, 'description-shorter-than-summary') + else: + # Check if a package does not have a %description tag in spec file + self.output.add_info('E', pkg, 'no-description-tag') + + def _check_group_tag(self, pkg, group): + """Trigger check no-group-tag, devel-package-with-non-devel-group, + non-standard-group + + Args: + group: Find Group: tag in package + + Returns: + Output info to STDOUT + """ + self._unexpanded_macros(pkg, 'Group', group) + # Check if a package does not have a group tag + if not group: + self.output.add_info('E', pkg, 'no-group-tag') + # Check if a package name end with -devel but + # has a Group: tag with value start other than Development/ + elif pkg.name.endswith('-devel') and not group.startswith('Development/'): + self.output.add_info('W', pkg, 'devel-package-with-non-devel-group', group) + # Check if a package has a non-standard-group + # which does not comply with the standard group list + elif self.valid_groups and group not in self.valid_groups: + self.output.add_info('W', pkg, 'non-standard-group', group) + + def _check_buildhost_tag(self, pkg, buildhost): + """Trigger check no-buildhost-tag, invalid-buildhost + + Args: + buildhost: Variable to find BuildHost: tag_regex + + Returns: + Output info to STDOUT + """ + self._unexpanded_macros(pkg, 'BuildHost', buildhost) + # Check if a package has no buildhost tag + if not buildhost: + self.output.add_info('E', pkg, 'no-buildhost-tag') + # Check if a package has a invalid-buildhost which does not comply + # with configuration ValidBuildHost + elif self.config.configuration['ValidBuildHost'] and \ + not self.valid_buildhost_regex.search(buildhost): + self.output.add_info('W', pkg, 'invalid-buildhost', buildhost) + + def _check_changelog_tag(self, pkg, changelog, version, release, name, epoch): + """Trigger multiple check of type *-changelog, *-changelogname-*, changelog-* + and forbidden-controlchar + + Contains all the checks that cause an issue during build of the rpm + in the %changelog of the specfile + + Args: + changelog: Find the %changelog in the specfile + + Returns: + Output info to STDOUT + """ + + # Check if a package does not have a %changelog in its spec file + if not changelog: + self.output.add_info('E', pkg, 'no-changelogname-tag') + else: + clt = pkg[rpm.RPMTAG_CHANGELOGTEXT] + if self.use_version_in_changelog: + ret = changelog_version_regex.search(byte_to_string(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(byte_to_string(clt[0])) + # Check if a package does not have version in the %changelog in latest version + if not ret: + self.output.add_info('W', pkg, 'no-version-in-last-changelog') + elif version and release: + srpm = pkg[rpm.RPMTAG_SOURCERPM] or '' + # only check when source name correspond to name + if srpm[0:-8] == f'{name}-{version}-{release}': + expected = [version + '-' + release] + if epoch is not None: # regardless of use_epoch + expected[0] = str(epoch) + ':' + expected[0] + # Allow EVR in changelog without release extension, + # the extension is often a macro or otherwise dynamic. + if self.release_ext: + expected.append(self.extension_regex.sub('', expected[0])) + # Check if a package does not have a version that is + # compatible with epoch:vesrion-release tuple + if ret.group(1) not in expected: + if len(expected) == 1: + expected = expected[0] + self.output.add_info('W', pkg, 'incoherent-version-in-changelog', + ret.group(1), expected) + if clt: + changelog = changelog + clt + for deptoken in changelog: + dep = Pkg.has_forbidden_controlchars(deptoken) + # Check if a package contains a forbidden character in %changelog + if dep: + self.output.add_info('E', pkg, 'forbidden-controlchar-found', '%%changelog : %s' % dep) + break + + clt = pkg[rpm.RPMTAG_CHANGELOGTIME][0] + if clt: + # Rollback in order to cover different timezones + # The largest difference between the time zones of two countries is + # 26 hours between the Howland Islands and the Line Islands. + clt -= 26 * 3600 + # Check if a package contains a changelog entry that is suspiciously too far behind + if clt < oldest_changelog_timestamp: + self.output.add_info('W', pkg, 'changelog-time-overflow', + time.strftime('%Y-%m-%d', time.gmtime(clt))) + # Check if a package contians a entry in %changelog + # with timestamp thats in the future of its writing + elif clt > time.time(): + self.output.add_info('E', pkg, 'changelog-time-in-future', + time.strftime('%Y-%m-%d', time.gmtime(clt))) + + def _check_license(self, pkg, rpm_license): + """Trigger check no-license, invalid-license-exception, invalid-license + + Checks are triggered due to the configuration set by the user in the configdefaults.toml + + Args: + rpm_license: Find License: tag in the rpm package + + Returns: + Output info to STDOUT + """ + + def split_license(text): + return (x.strip() for x in + (i for i in license_regex.split(text) if i)) + + def split_license_exception(text): + x, y = license_exception_regex.split(text)[1:3] or (text, '') + return x.strip(), y.strip() + + # Check if a package spec file conatins a License: tag + if not rpm_license: + self.output.add_info('E', pkg, 'no-license') + else: + valid_license = True + if rpm_license not in self.valid_licenses: + license_string = rpm_license + + l1, lexception = split_license_exception(rpm_license) + # SPDX allows "<license> WITH <license-exception>" + if lexception: + license_string = l1 + # Check if a package contains 'with <x>' license exception + if lexception not in self.valid_license_exceptions: + self.output.add_info('W', pkg, 'invalid-license-exception', lexception) + valid_license = False + + for l1 in split_license(license_string): + if l1 in self.valid_licenses: + continue + for l2 in split_license(l1): + # Check if a package has a License: value other than ValidLicenses + if l2 not in self.valid_licenses: + self.output.add_info('W', pkg, 'invalid-license', l2) + valid_license = False + if not valid_license: + self._unexpanded_macros(pkg, 'License', rpm_license) + + def _check_url(self, pkg): + """Trigger check invalid-url, no-url-tag """ + for tag in ('URL', 'DistURL', 'BugURL'): + if hasattr(rpm, f'RPMTAG_{tag.upper()}'): + url = byte_to_string(pkg[getattr(rpm, f'RPMTAG_{tag.upper()}')]) + self._unexpanded_macros(pkg, tag, url, is_url=True) + if url: + (scheme, netloc) = urlparse(url)[0:2] + # Check if a package contains a unreasonable URL + # [This check is also triggered with Source: tag value] + if not scheme or not netloc or '.' not in netloc or \ + scheme not in ('http', 'https', 'ftp') or \ + (self.config.configuration['InvalidURL'] and + self.invalid_url_regex.search(url)): + self.output.add_info('W', pkg, 'invalid-url', tag, url) + # Check if a package does not have a URL: tag in its spec file + elif tag == 'URL': + self.output.add_info('W', pkg, 'no-url-tag') + + def _check_obsolete_not_provided(self, pkg, prov_names): + """Check if a package has the obsoleted package still provided + in spec file to avoid dependency breakage + + Args: + prov_names: Find the value of Provides: tag in specfile + + Returns: + Output info to STDOUT + """ + obs_names = [x[0] for x in pkg.obsoletes] + for dep_token in (x for x in obs_names if x not in prov_names): + self.output.add_info('W', pkg, 'obsolete-not-provided', dep_token) + + def _check_useless_provides(self, pkg, provides): + """Trigger check useless-provides + + Check if a package has a multiple number of Provides + (versioned and unversioned): of the same dependency + + example: + Provides: foo + Provides: foo = 1.0 + + Returns: + Output info to STDOUT + """ + + # TODO: should take versions, <, <=, =, >=, > into account here + # https://bugzilla.redhat.com/460872 + no_version_provides = set() + version_provides = set() + + for provide in provides: + prov = provide[0] + if prov.startswith('debuginfo('): + continue + version = Pkg.versionToString(provide[2]) + if version: + version_provides.add(prov) + else: + no_version_provides.add(prov) + + for prov in sorted(no_version_provides): + if prov in version_provides: + self.output.add_info('E', pkg, 'useless-provides', prov) + + def _check_forbidden_controlchar(self, pkg): + """Trigger check forbidden-controlchar-found + + Check if package contains a forbidden_words or character in + tags: Provides, Conflicts, Obsoletes, + Supplements, Suggests, Enhances, Recommends and Requires + + Returns: + Output info to STDOUT + """ + + for tagname, items in ( + ('Provides', pkg.provides), + ('Conflicts', pkg.conflicts), + ('Obsoletes', pkg.obsoletes), + ('Supplements', pkg.supplements), + ('Suggests', pkg.suggests), + ('Enhances', pkg.enhances), + ('Recommends', pkg.recommends)): + for item in items: + dep = Pkg.has_forbidden_controlchars(item) + if dep: + self.output.add_info('E', + pkg, + 'forbidden-controlchar-found', + f'{tagname}: {dep}') + value = Pkg.formatRequire(*item) + self._unexpanded_macros(pkg, f'{tagname} {value}', value) + + # Check if a package contains forbidden-controlchar in Requires: tag. + for pkg_token in (pkg.requires): + dep = Pkg.has_forbidden_controlchars(pkg_token) + if dep: + self.output.add_info('E', + pkg, + 'forbidden-controlchar-found', + f'Requires: {dep}') + + def _check_self_obsoletion(self, pkg): + """Trigger check self-obsoletion + + Check if a package does not obsoletes itself + example: + Name: lib-devel and Obsoletes: lib-devel in its spec file + + Returns: + Output info to STDOUT + """ + obss = pkg.obsoletes + if obss: + provs = pkg.provides + for prov in provs: + for obs in obss: + if Pkg.rangeCompare(obs, prov): + self.output.add_info('W', pkg, + 'self-obsoletion', + '{} obsoletes {}'.format(Pkg.formatRequire(*obs), + Pkg.formatRequire(*prov))) + + def _check_non_coherent_filename(self, pkg): + """Trigger check in non-coherent-filename + + Check if a package has a + named <NAME>-<VERSION>-<RELEASE>.<ARCH>.rpm in this order + + Returns: + Output info STDOUT + """ + expfmt = rpm.expandMacro('%{_build_name_fmt}') + if pkg.is_source: + # _build_name_fmt often (always?) ends up not outputting src/nosrc + # as arch for source packages, do it ourselves + expfmt = re.sub(r'(?i)%\{?ARCH\b\}?', pkg.arch, expfmt) + expected = pkg.header.sprintf(expfmt).split('/')[-1] + basename = Path(pkg.filename).name + if basename != expected: + self.output.add_info('W', pkg, 'non-coherent-filename', basename, expected) diff --git a/rpmlint/checks/TmpFilesCheck.py b/rpmlint/checks/TmpFilesCheck.py new file mode 100644 index 0000000..ff3bc3f --- /dev/null +++ b/rpmlint/checks/TmpFilesCheck.py @@ -0,0 +1,102 @@ +from pathlib import Path +import re +import stat + +import rpm +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class TmpFilesCheck(AbstractCheck): + """ + Validate that temporary files meet tmpfiles.d packaging rules. + """ + + # interesting types in tmpfiles.d configuration file (see tmpfiles.d(5)) + interesting_types = ('f', 'F', 'w', 'd', 'D', 'p', 'L', 'c', 'b') + + def check(self, pkg): + if pkg.is_source: + return + + for fname, pkgfile in pkg.files.items(): + if not fname.startswith('/usr/lib/tmpfiles.d/'): + continue + if not stat.S_ISREG(pkgfile.mode): + self.output.add_info('W', pkg, 'tmpfile-not-regular-file', + fname) + continue + + if pkgfile.is_ghost: + continue + + self._check_pre_tmpfile(fname, pkg) + self._check_post_tmpfile(fname, pkg) + self._check_tmpfile_in_filelist(pkgfile, pkg) + + def _check_pre_tmpfile(self, fname, pkg): + """ + Check if the %pre section doesn't contain 'systemd-tmpfiles --create' + call. + + Print a warning if there is systemd-tmpfiles call in the %pre section. + """ + pre = pkg[rpm.RPMTAG_PREIN] + + basename = Path(fname).name + tmpfiles_regex = re.compile(r'systemd-tmpfiles --create .*%s' + % re.escape(basename)) + + if pre and tmpfiles_regex.search(pre): + self.output.add_info('W', pkg, 'pre-with-tmpfile-creation', fname) + + def _check_post_tmpfile(self, fname, pkg): + """ + Check if the %post section contains 'systemd-tmpfiles --create' call. + + Print a warning if there is no such call in the %post section. + """ + post = pkg[rpm.RPMTAG_POSTIN] + + basename = Path(fname).name + tmpfiles_regex = re.compile(r'systemd-tmpfiles --create .*%s' + % re.escape(basename)) + + if post and tmpfiles_regex.search(post): + return + self.output.add_info('W', pkg, 'post-without-tmpfile-creation', fname) + + def _check_tmpfile_in_filelist(self, pkgfile, pkg): + """ + Check if the tmpfile is listed in the filelist and marked as %ghost. + + Please note that a tmpfile that doesn't exist during the build can't + be in the filelist without %ghost directive otherwise rpm wouldn't + build it. + + Print a 'tmpfile-not-in-filelist' warning while it's not in the + filelist (and therefore not marked as %ghost). + """ + with open(pkgfile.path) as inputf: + for line in inputf: + # skip comments + line = line.split('#')[0].split('\n')[0] + line = line.lstrip() + if not len(line): + continue + + # the format is: + # Type Path Mode UID GID Age Argument + line = re.split(r'\s+', line) + if len(line) < 3: + continue + # we only need Type and Path + tmpfiles_type = line[0] + tmpfiles_path = line[1] + if tmpfiles_type.endswith('!'): + tmpfiles_type = tmpfiles_type[:-1] + if tmpfiles_type not in self.interesting_types: + continue + + if tmpfiles_path not in pkg.files: + self.output.add_info('W', pkg, 'tmpfile-not-in-filelist', + tmpfiles_path) diff --git a/rpmlint/checks/XinetdDepCheck.py b/rpmlint/checks/XinetdDepCheck.py new file mode 100644 index 0000000..b910751 --- /dev/null +++ b/rpmlint/checks/XinetdDepCheck.py @@ -0,0 +1,11 @@ +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class XinetdDepCheck(AbstractCheck): + def check(self, pkg): + if pkg.is_source: + return + + for req in pkg.requires + pkg.prereq: + if req[0] == 'xinetd': + self.output.add_info('E', pkg, 'obsolete-xinetd-requirement') diff --git a/rpmlint/checks/ZipCheck.py b/rpmlint/checks/ZipCheck.py new file mode 100644 index 0000000..3ac48a1 --- /dev/null +++ b/rpmlint/checks/ZipCheck.py @@ -0,0 +1,94 @@ +from pathlib import Path +import re +from zipfile import BadZipFile, is_zipfile, ZipFile + +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class ZipCheck(AbstractCheck): + """ + Validate zip and jar files correctness. + """ + zip_regex = re.compile(r'\.(zip|[ewj]ar)$') + jar_regex = re.compile(r'\.[ewj]ar$') + + def check(self, pkg): + for fname, pkgfile in pkg.files.items(): + path = pkgfile.path + if self.zip_regex.search(fname) and Path(path).exists() and \ + Path(path).is_file() and is_zipfile(path): + try: + with ZipFile(path, 'r') as z: + # zip checks + self._check_bad_crc(pkg, fname, z) + self._check_compression(pkg, fname, z) + + # jar checks + if self.jar_regex.search(fname): + self._check_classpath(pkg, fname, z) + self._check_jarindex(pkg, fname, z) + except BadZipFile as err: + self.output.add_info('E', pkg, 'unable-to-read-zip', f'{fname}: {err}') + except RuntimeError as err: + self.output.add_info('W', pkg, 'unable-to-read-zip', f'{fname}: {err}') + + def _check_bad_crc(self, pkg, fname, zipfile): + """ + Check CRC issues for the files in the zipfile. + + Print an error if there is a file in the archive that fails CRC check. + """ + badcrc = zipfile.testzip() + if badcrc: + self.output.add_info('E', pkg, 'bad-crc-in-zip', badcrc, fname) + + def _check_compression(self, pkg, fname, zipfile): + """ + Check if zip is actually compressed or not. + + One file with smaller size is enough. Print an error if the zipfile + is not compressed. + """ + # check for empty archives which are valid + filecount = len(zipfile.namelist()) + nullcount = 0 + for zinfo in zipfile.infolist(): + if zinfo.file_size == 0: + nullcount += 1 + if zinfo.compress_size != zinfo.file_size: + return + + # empty files only + if filecount == nullcount: + return + self.output.add_info('E', pkg, 'uncompressed-zip', fname) + + def _check_classpath(self, pkg, fname, jarfile): + """ + Check if META-INF/MANIFEST.MF file in the jar contains a hardcoded + Class-Path. + + Print a warning if the path is hardcoded. + """ + classpath_regex = re.compile(r'^\s*Class-Path\s*:', re.MULTILINE | re.IGNORECASE) + + # the META-INF is optional so skip if it is not present + mf = 'META-INF/MANIFEST.MF' + if mf not in jarfile.namelist(): + return + + # otherwise check for the hardcoded classpath + manifest = jarfile.read(mf).decode() + if classpath_regex.search(manifest): + self.output.add_info('W', pkg, 'class-path-in-manifest', fname) + + def _check_jarindex(self, pkg, fname, jarfile): + """ + Check if the .jar file is indexed. + + Print a warning if 'META-INF/INDEX.LIST' file is not present in the + jarfile. + """ + index = 'META-INF/INDEX.LIST' + if index not in jarfile.namelist(): + self.output.add_info('W', pkg, 'jar-not-indexed', fname) diff --git a/rpmlint/checks/ZyppSyntaxCheck.py b/rpmlint/checks/ZyppSyntaxCheck.py new file mode 100644 index 0000000..60a4f55 --- /dev/null +++ b/rpmlint/checks/ZyppSyntaxCheck.py @@ -0,0 +1,18 @@ +from rpmlint.checks.AbstractCheck import AbstractCheck + + +class ZyppSyntaxCheck(AbstractCheck): + def check(self, pkg): + # We care only about the names, versions are pointless here + pkg_supplements = [x.name for x in pkg.supplements] + pkg_enhances = [x.name for x in pkg.enhances] + pkg_recommends = [x.name for x in pkg.recommends] + pkg_suggests = [x.name for x in pkg.suggests] + pkg_requires = [x.name for x in pkg.requires] + pkg_conflicts = [x.name for x in pkg.conflicts] + keywords = pkg_supplements + pkg_enhances + pkg_recommends + pkg_suggests + pkg_requires + pkg_conflicts + for keyword in keywords: + if keyword.startswith('packageand('): + self.output.add_info('E', pkg, 'suse-zypp-packageand', keyword) + if keyword.startswith('otherproviders('): + self.output.add_info('E', pkg, 'suse-zypp-otherproviders', keyword) diff --git a/rpmlint/cli.py b/rpmlint/cli.py new file mode 100644 index 0000000..18dc401 --- /dev/null +++ b/rpmlint/cli.py @@ -0,0 +1,189 @@ +import argparse +from pathlib import Path +import sys + +from rpmlint.helpers import print_warning +from rpmlint.lint import Lint +from rpmlint.rpmdiff import Rpmdiff +from rpmlint.version import __version__ + + +__copyright__ = """ + Copyright (C) 2006 Mandriva + Copyright (C) 2009 Red Hat, Inc. + Copyright (C) 2009 Ville Skyttä + Copyright (C) 2017 SUSE LINUX GmbH + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" + + +def process_diff_args(argv): + """ + Process the passed arguments and return the result + :param argv: passed arguments + """ + + parser = argparse.ArgumentParser(prog='rpmdiff', + description='Shows basic differences between two rpm packages', + epilog="""When using the -i or -e options, + separate values from package arguments with '--', + e.g.: 'rpmdiff -i 5 T -- old.rpm new.rpm' or place + the options _after_ the package arguments.""") + parser.add_argument('old_package', metavar='RPM_ORIG', type=Path, help='the old package') + parser.add_argument('new_package', metavar='RPM_NEW', type=Path, help='the new package') + parser.add_argument('-V', '--version', action='version', version=__version__, help='show package version and exit') + parser.add_argument('-i', '--ignore', nargs='+', default=None, choices=['S', 'M', '5', 'D', 'N', 'L', 'V', 'U', 'G', 'F', 'T'], + help="""file property to ignore when calculating differences. + Valid values are: S (size), M (mode), 5 (checksum), D (device), + N (inode), L (number of links), V (vflags), U (user), G (group), + F (digest), T (time)""") + parser.add_argument('-e', '--exclude', metavar='GLOB', nargs='+', default=None, + help="""Paths to exclude when showing differences. + Takes a glob. When absolute (starting with /) + all files in a matching directory are excluded as well. + When relative, files matching the pattern anywhere + are excluded but not directory contents.""") + + options = parser.parse_args(args=argv) + + # convert options to dict + options_dict = vars(options) + return options_dict + + +def process_lint_args(argv): + """ + Process the passed arguments and return the result + :param argv: passed arguments + """ + + parser = argparse.ArgumentParser(prog='rpmlint', + description='Check for common problems in rpm packages') + parser.add_argument('rpmfile', nargs='*', type=Path, help='files to be validated by rpmlint') + parser.add_argument('-V', '--version', action='version', version=__version__, help='show package version and exit') + parser.add_argument('-c', '--config', type=_validate_conf_location, help='load up additional configuration data from specified path (file or directory with *.toml files)') + parser.add_argument('-e', '--explain', nargs='+', default='', help='provide detailed explanation for one specific message id') + parser.add_argument('-r', '--rpmlintrc', '--file', type=_is_file_path, help='load up specified rpmlintrc file') + parser.add_argument('-v', '--verbose', '--info', action='store_true', help='provide detailed explanations where available') + parser.add_argument('-p', '--print-config', action='store_true', help='print the settings that are in effect when using the rpmlint') + parser.add_argument('-i', '--installed', nargs='+', default='', help='installed packages to be validated by rpmlint') + parser.add_argument('-t', '--time-report', action='store_true', help='print time report for run checks') + parser.add_argument('-T', '--profile', action='store_true', help='print cProfile report') + parser.add_argument('--ignore-unused-rpmlintrc', action='store_true', + help='Do not report "unused-rpmlintrc-filter" errors') + parser.add_argument('--checks', + help='Debugging option that enables only selected checks (separated by comma)') + lint_modes_parser = parser.add_mutually_exclusive_group() + lint_modes_parser.add_argument('-s', '--strict', action='store_true', help='treat all messages as errors') + lint_modes_parser.add_argument('-P', '--permissive', action='store_true', help='treat individual errors as non-fatal') + + # print help if there is no argument + if len(argv) < 1: + parser.print_help() + sys.exit(0) + + options = parser.parse_args(args=argv) + + # make sure rpmlintrc exists + if options.rpmlintrc and not options.rpmlintrc.exists(): + print_warning(f"User specified rpmlintrc '{options.rpmlintrc}' does not exist") + exit(2) + # validate all the rpmlfile options to be either file or folder + f_path = set() + invalid_path = False + for item in options.rpmfile: + p_path = Path() + pattern = None + for pos, component in enumerate(item.parts): + if ('*' in component) or ('?' in component): + pattern = '/'.join(item.parts[pos:]) + break + p_path = p_path / component + p_path = list(p_path.glob(pattern)) if pattern else [p_path] + + for path in p_path: + if not path.exists(): + print_warning(f"The file or directory '{path}' does not exist") + invalid_path = True + f_path.update(p_path) + + if invalid_path: + exit(2) + # convert options to dict + options_dict = vars(options) + # use computed rpmfile + options_dict['rpmfile'] = list(f_path) + return options_dict + + +def _validate_conf_location(string): + """ + Help validate configuration location during argument parsing. + + We accept either one configuration file or a directory (then it processes + all *.toml files in this directory). It exits the program if location + doesn't exist. + + Args: + string: A string representing configuration path (file or directory). + + Returns: + A list with individual paths for each configuration file found. + """ + config_paths = [] + path = Path(string) + + # Exit if file or dir doesn't exist + if not path.exists(): + print_warning( + f"File or dir with user specified configuration '{string}' does not exist") + exit(2) + + if path.is_dir(): + config_paths.extend(path.glob('*.toml')) + elif path.is_file(): + config_paths.append(path) + + return config_paths + + +def _is_file_path(path): + p = Path(path) + if not p.is_file(): + raise argparse.ArgumentTypeError(f'{path} is not a valid file path') + return p + + +def lint(): + """ + Main wrapper for lint command processing + """ + options = process_lint_args(sys.argv[1:]) + + lint = Lint(options) + sys.exit(lint.run()) + + +def diff(): + """ + Main wrapper for diff command processing + """ + options = process_diff_args(sys.argv[1:]) + d = Rpmdiff(options['old_package'], options['new_package'], + ignore=options['ignore'], exclude=options['exclude']) + textdiff = d.textdiff() + if textdiff: + print(textdiff) + sys.exit(int(d.differs())) diff --git a/rpmlint/color.py b/rpmlint/color.py new file mode 100644 index 0000000..0b720d4 --- /dev/null +++ b/rpmlint/color.py @@ -0,0 +1,20 @@ +import sys + +if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty(): + class Color: + """ + Colors used when doing printouts with rpmlint + """ + Bold = '\x1b[1m' + Red = '\x1b[31m' + Yellow = '\x1b[33m' + Reset = '\x1b[0m' +else: + class Color: + """ + Colors used when doing printouts with rpmlint + """ + Bold = '' + Red = '' + Yellow = '' + Reset = '' diff --git a/rpmlint/config.py b/rpmlint/config.py new file mode 100644 index 0000000..136e5a0 --- /dev/null +++ b/rpmlint/config.py @@ -0,0 +1,183 @@ +import os +from pathlib import Path +import re +import sys + +from rpmlint.helpers import print_warning +try: + import tomllib +except ImportError: + import tomli as tomllib +import tomli_w +from xdg.BaseDirectory import xdg_config_dirs + + +class Config: + """ + Load and parse rpmlint configuration. + + Configuration files are written in toml and should be placed in one of + the XDG_CONFIG_DIRS directory or passed as "config" argument directly. + + By default it loads configdefaults.toml and all default locations and + initializes basic testing layout for the rpmlint binary. Based on the + opening order 'newer' configuration takes precedence over already + existing one. + """ + + re_filter = re.compile(r'^\s*addFilter\s*\(\s*r?[\"\'](.*)[\"\']\s*\)') + re_badness = re.compile(r'\s*setBadness\s*\([\'\"](.*)[\'\"],\s*[\'\"]?(\d+)[\'\"]?\)') + config_defaults = Path(__file__).parent / 'configdefaults.toml' + + def __init__(self, config=None): + """ + Initialize basic options and load rpmlint configuration. + + Args: + config: A list of paths of configuration file(s) passed by user in + command line. + """ + # ordered list of configuration files we loaded + # useful when debugging where from we got all the config options + self.conf_files = [] + # Configuration content parsed from the toml configuration file + self.configuration = None + # List of rpmlintrc filters + self.rpmlintrc_filters = [] + # whether to print more information or not + self.info = False + # whether to treat all messages as errors or not + self.strict = False + # whether to treat individual errors as non-fatal + self.permissive = False + + # find configuration files and load them + self.find_configs(config) + self.load_config() + # if loading of the configuration failed -> fall back only to defaults + if not self.configuration: + # reset the configs only to defaults + self.conf_files = [self.config_defaults] + self.load_config() + + def find_configs(self, config=None): + """ + Find and store paths to all config files. + + It searches for default configuration, files in XDG_CONFIG_DIRS and + user defined configuration (argument "config"). All configuration + file paths found are then stored in self.conf_files variable. + XDG_CONFIG_DIRS contains preference-ordered set of base directories + to search for configuration files. Users can override it by their + own configuration file (config parameter) and then that is + added too. + """ + # first load up the file that contains defaults + self.conf_files.append(self.config_defaults) + + # Skip auto-loading when running under PYTEST + if not os.environ.get('PYTEST_XDIST_TESTRUNUID') and not os.environ.get('CONFIG_DISABLE_AUTOLOADING'): + # Then load up config directories on system + for directory in reversed(xdg_config_dirs): + confdir = Path(directory) / 'rpmlint' + if confdir.is_dir(): + # load all configs in the folders + confopts = sorted(confdir.glob('*toml')) + self.conf_files += confopts + + # As a last item load up the user configuration + if config: + for path in config: + if path.exists(): + # load this only if it really exist + self.conf_files.append(path) + else: + print_warning(f'(none): W: error locating user requested configuration: {path}') + + def _merge_dictionaries(self, dest, source, override): + """ + Merge in place dest dictionary for values in source in recursive way. + If override is set to True, override instead of merging. + """ + for k, v in source.items(): + vdest = dest.get(k) + if isinstance(vdest, dict) and isinstance(v, dict): + self._merge_dictionaries(vdest, v, override) + else: + if isinstance(vdest, list) and not override: + for item in v: + if item not in vdest: + vdest.append(item) + else: + dest[k] = v + + def _is_override_config(self, config_file): + return '.override.' in config_file.name + + def _sort_config_files(self, config_file): + """ + Sort config files in the following order: + configdefaults.toml, normal configs, *.override.* configs + """ + if config_file == self.config_defaults: + return 0 + elif not self._is_override_config(config_file): + return 1 + else: + return 2 + + def load_config(self, config=None): + """ + Load the configuration files and append it to local dictionary. + + It's stored in self.configuration with the content of already loaded + options. + """ + if config: + # just add the new config at the end of the list, someone injected + # config file to us + for path in config: + if path not in self.conf_files and path.exists(): + self.conf_files.append(path) + + cfg = {} + # sort self.conf_files as we print list of loaded configuration files + self.conf_files = sorted(self.conf_files, key=self._sort_config_files) + for cf in self.conf_files: + try: + with open(cf, 'rb') as f: + toml_config = tomllib.load(f) + self._merge_dictionaries(cfg, toml_config, self._is_override_config(cf)) + except tomllib.TOMLDecodeError as terr: + print_warning(f'(none): E: fatal error while parsing configuration file {cf}: {terr}') + sys.exit(4) + self.configuration = cfg + + def load_rpmlintrc(self, rpmlintrc_file): + """ + Load existing rpmlintrc files. + + Only setBadness and addFilter are processed. + """ + + rpmlintrc_lines = rpmlintrc_file.read_text().splitlines() + filters = [] + for line in rpmlintrc_lines: + m = self.re_filter.match(line) + if m: + filters.append(m.group(1)) + m = self.re_badness.match(line) + if m: + self.configuration['Scoring'].update({m.group(1): m.group(2)}) + + self.configuration['Filters'] += filters + self.rpmlintrc_filters = filters + + def print_config(self): + """Print the current state of the configuration.""" + if self.configuration: + print(tomli_w.dumps(self.configuration)) + + def set_badness(self, result, badness): + """Set specific badness for some result.""" + self.configuration['Scoring'][result] = badness diff --git a/rpmlint/configdefaults.toml b/rpmlint/configdefaults.toml new file mode 100644 index 0000000..a24295d --- /dev/null +++ b/rpmlint/configdefaults.toml @@ -0,0 +1,342 @@ +# Enabled checks for the rpmlint to be run +Checks = [ + "AlternativesCheck", + "AppDataCheck", + "BinariesCheck", + 'BuildRootAndDateCheck', + "ConfigFilesCheck", + "DBusPolicyCheck", + 'DuplicatesCheck', + "DocCheck", + "ErlangCheck", + "FHSCheck", + "FilesCheck", + "IconSizesCheck", + "I18NCheck", + "LibraryDependencyCheck", + "LogrotateCheck", + "MenuCheck", + "MenuXDGCheck", + "MixedOwnershipCheck", + "PkgConfigCheck", + "PostCheck", + "PythonCheck", + "SignatureCheck", + "SourceCheck", + "SpecCheck", + "TagsCheck", + "ZipCheck", + "ZyppSyntaxCheck", +] +# List of error titles that should be filtered +FilterErrorTitles = [] +# Various output filters, list of regexp strings eg. "E: .* no-signature" +Filters = [] +# List of errors that can't be filtered +BlockedFilters = [] +# Treshold where we should error out, by default single error is enough +BadnessThreshold = -1 +# When checking that various files that should be compressed are +# indeed compressed, look for this filename extension +CompressExtension = "bz2" +# Base directory where to extract uninstalled packages while checking +# Default is to use mktemp from python to provide one +ExtractDir = "" +# Regexp string for words that must never exist in preamble tag values +ForbiddenWords = "" +# Accepted non-XDG legacy icon filenames, string regexp format +IconFilename = '.*\.png$' +# Regexp string to disallow in various URL tags +InvalidURL = "" +# Whether to allow packaging kernel modules in non-kernel packages. +KernelModuleRPMsOK = true +# Maximum allowed line length for Summary and Description tags +MaxLineLength = 79 +# Regexp string with names of packages to treat as "meta" ones. +MetaPackageRegexp = '^(bundle|task)-' +# String regexp validating value for the Packager tag. +Packager = "" +# Assumed default version of Python if one cannot be determined from files +PythonDefaultVersion = "" +# Trick in perl version handling +PerlVersionTrick = true +# Regexp string with expected suffix in Release tags. +ReleaseExtension = "" +# Regexp string with allowed Group tag for games +RpmGamesGroup = "Games" +# Doc files to which end of line and UTF-8 checks should not be applied +SkipDocsRegexp = '\.(?:rtf|x?html?|svg|ml[ily]?)$' +# Whether to use the Enchant spell checker for spell checking +UseEnchant = true +# Whether debug sources are expected to be in separate packages from +# -debuginfo, typically -debugsource. +UseDebugSource = true +# Whether an explicit Epoch should always be specified in preamble +UseEpoch = false +# Whether to want default start/stop runlevels specified in init scripts +UseDefaultRunlevels = true +# Whether symlinks between directories should be relative. +UseRelativeSymlinks = true +# Whether %changelog entries should contain a version +UseVersionInChangelog = true +# Whether init scripts must use /var/lock/subsys +UseVarLockSubsys = true +# Regexp string with value for the BuildHost preamble tag +ValidBuildHost = '' +# Standard "needs" values for non-XDG legacy menu items +ExtraMenuNeeds = [ + "gnome", + "icewm", + "kde", + "wmaker", +] +# Regexp string with exceptions for hardcoded library paths. +HardcodedLibPathExceptions = '/lib/(modules|cpp|perl5|rpm|hotplug|firmware|systemd)($|[\s/,])' +# Values for non-XDG legacy menu item sections +ValidMenuSections = [ + "Office/Accessories", + "Office/Address Books", + "Office/Communications/Fax", + "Office/Communications/PDA", + "Office/Communications/Phone", + "Office/Communications/Other", + "Office/Drawing", + "Office/Graphs", + "Office/Presentations", + "Office/Publishing", + "Office/Spreadsheets", + "Office/Tasks Management", + "Office/Time Management", + "Office/Wordprocessors", + "Office/Other", + "Internet/Chat", + "Internet/File Transfer", + "Internet/Instant Messaging", + "Internet/Mail", + "Internet/News", + "Internet/Remote Access", + "Internet/Video Conference", + "Internet/Web Browsers", + "Internet/Web Editors", + "Internet/Other", + "Multimedia/Graphics", + "Multimedia/Sound", + "Multimedia/Video", + "Multimedia/Other", + "System/Archiving/Backup", + "System/Archiving/CD Burning", + "System/Archiving/Compression", + "System/Archiving/Other", + "System/Configuration/Boot and Init", + "System/Configuration/GNOME", + "System/Configuration/Hardware", + "System/Configuration/KDE", + "System/Configuration/Networking", + "System/Configuration/Packaging", + "System/Configuration/Printing", + "System/Configuration/Users", + "System/Configuration/Other", + "System/File Tools", + "System/Monitoring", + "System/Session/Windowmanagers", + "System/Terminals", + "System/Text Tools", + "System/Other", + "More Applications/Accessibility", + "More Applications/Communications", + "More Applications/Databases", + "More Applications/Development/Code Generators", + "More Applications/Development/Development Environments", + "More Applications/Development/Interpreters", + "More Applications/Development/Tools", + "More Applications/Development/Other", + "More Applications/Documentation", + "More Applications/Editors", + "More Applications/Education/Economy", + "More Applications/Education/Geography", + "More Applications/Education/History", + "More Applications/Education/Languages", + "More Applications/Education/Literature", + "More Applications/Education/Sciences", + "More Applications/Education/Sports", + "More Applications/Education/Other", + "More Applications/Emulators", + "More Applications/Finances", + "More Applications/Games/Adventure", + "More Applications/Games/Arcade", + "More Applications/Games/Boards", + "More Applications/Games/Cards", + "More Applications/Games/Puzzles", + "More Applications/Games/Sports", + "More Applications/Games/Strategy", + "More Applications/Games/Toys", + "More Applications/Games/Other", + "More Applications/Sciences/Artificial Intelligence", + "More Applications/Sciences/Astronomy", + "More Applications/Sciences/Biology", + "More Applications/Sciences/Chemistry", + "More Applications/Sciences/Computer Science", + "More Applications/Sciences/Data visualization", + "More Applications/Sciences/Electricity", + "More Applications/Sciences/Geosciences", + "More Applications/Sciences/Image Processing", + "More Applications/Sciences/Mathematics", + "More Applications/Sciences/Numerical Analysis", + "More Applications/Sciences/Parallel Computing", + "More Applications/Sciences/Physics", + "More Applications/Sciences/Robotics", + "More Applications/Sciences/Other", + "More Applications/Other", +] +# Disallowed Runtime dependencies +InvalidRequires = [ + '^is$', + '^not$', + '^owned$', + '^by$', + '^any$', + '^package$', + '^libsafe\.so\.', +] +# List of directory prefixes that are not allowed in packages +# In addition rpmlint will warn about non-ghost files in "/run/" +DisallowedDirs = [ + "/home", + "/mnt", + "/opt", + "/proc", + "/tmp", + "/usr/local", + "/usr/tmp", + "/var/local", + "/var/lock", + "/var/run", + "/var/tmp", +] +# Standard OS groups +StandardGroups = [ + "root", + "bin", + "daemon", + "adm", + "lp", + "sync", + "shutdown", + "halt", + "mail", + "news", + "uucp", + "man", + "nobody", +] +# Standard OS users +StandardUsers = [ + "root", + "bin", + "daemon", + "adm", + "lp", + "sync", + "shutdown", + "halt", + "mail", + "news", + "uucp", + "operator", + "man", + "nobody", +] +# List of directories considered to be system default library search paths. +SystemLibPaths = [ + "/lib", + "/usr/lib", + "/usr/X11R6/lib", + "/lib64", + "/usr/lib64", + "/usr/X11R6/lib64", +] +# List of regexp strings with executables that must be compiled as position independent +PieExecutables = [] +# Architecture dependent paths in which packages are allowed to install files +# even if they are all non-binary +UsrLibBinaryException = '^/usr/lib(64)?/(perl|python|ruby|menu|pkgconfig|ocaml|lib[^/]+\.(so|l?a)$|bonobo/servers/|\.build-id|firmware|systemd)' +# List of compilation flags that are mandatory +MandatoryOptflags = [] +# List of forbidden compilation flags +ForbiddenOptflags = [] +# Interpreters whose scriptlets are allowed to be empty +ValidEmptyShells = [ + "/sbin/ldconfig", +] +# Package scriptlet interpreters +ValidShells = [ + "<lua>", + "/bin/sh", + "/bin/bash", + "/sbin/sash", + "/usr/bin/perl", + "/sbin/ldconfig", +] +# Values for the Group tag, if not specified pulled from RPM +ValidGroups = [] +# Permissions for files in source packages +ValidSrcPerms = [ + "0o644", + "0o755", +] +# Valid values for the License tag +ValidLicenses = [] + +# Default valid license exceptions +ValidLicenseExceptions = [] + +# Default list of authorized PAM modules +PAMAuthorizedModules = [] + +# Minimum size of files to check duplicates, in bytes +DuplicatesMinSize = 2 + +# Additional warnings on specific function calls +[WarnOnFunction] +#[WarnOnFunction.testname] +#f_name = "" +#good_param = "" +#description = "" +# Set badness tweaking for various options +[Scoring] +#function=0 + +# Paths in which non-XDG legacy icons should be installed +[IconPath] +[IconPath."/usr/share/icons/"] +path = "/usr/share/icons/" +type = "normal" +[IconPath."/usr/share/icons/mini"] +path = "/usr/share/icons/mini" +type = "mini" +[IconPath."/usr/share/icons/large"] +path = "/usr/share/icons/large" +type = "large" + +# Default known application launchers for desktop entries +[MenuLaunchers] +[MenuLaunchers.kdesu] +regexp = '(?:/usr/bin/)?kdesu' +binaries = ['/usr/bin/kdesu', 'kdesu'] +[MenuLaunchers.x11_clanapp] +regexp = '(?:/usr/bin/)?launch_x11_clanapp' +binaries = ['/usr/bin/launch_x11_clanapp', 'clanlib', 'libclanlib0'] +[MenuLaunchers.soundwrapper] +regexp = '(?:/usr/bin/)?soundwrapper' +binaries = false + +# Exception list for dangling symlink checks. The first in each pair ("path") +# is a regexp, and the second ("name") the package in which the target of the +# dangling symlink is shipped +[DanglingSymlinkExceptions] + +# A table with key/value pairs representing new descriptions for rpmlint errors +[Descriptions] +# rpmlint-error-name = """ +# A new description for this rpmlint error +# """" diff --git a/rpmlint/descriptions/AlternativesCheck.toml b/rpmlint/descriptions/AlternativesCheck.toml new file mode 100644 index 0000000..82f920b --- /dev/null +++ b/rpmlint/descriptions/AlternativesCheck.toml @@ -0,0 +1,61 @@ +alternative-generic-name-not-symlink=""" +The update-alternative generic-name is not a symlink pointing to +%{_sysconfdir}/alternatives/$(basename generic-name). +""" +alternative-link-not-ghost=""" +The %{_sysconfdir}/alternatives/$(basename generic-name) link exists but is +not marked as ghost. Mark it as %ghost. +""" +alternative-link-missing=""" +The file %{_sysconfdir}/alternatives/$(basename generic-name) is missing +in the file list. Mark it as %ghost and add it to the file list. +""" +alternative-generic-name-missing=""" +The update-alternatives generic name is not in the filelist. Create it as +a symlink to %{_sysconfdir}/alternatives/$(basename generic-name) and add it +to the file list. +""" +update-alternatives-requirement-missing=""" +The package does not have update-alternatives in Requires(post) or +Requires(postun). This is needed for the proper scriptlet execution. +""" +update-alternatives-post-call-missing=""" +The package does not call update-alternatives --install in post phase to +install all the configuration. +""" +update-alternatives-postun-call-missing=""" +The package does not call update-alternatives --remove in postun phase to +remove all the configuration for each individual --install binary that +was done in postun. +""" +alts-requirement-missed=""" +The package does not require package alts, needed for libalternatives. +""" +empty-libalternatives-directory=""" +The directory /usr/share/libalternatives/<call> has no configuration file. +""" +libalternatives-directory-not-exists=""" +The directory /usr/share/libalternatives/<call> has not been defined. +""" +libalternatives-conf-not-found=""" +Not found libalternatives configuration file, defined in the file package section. This does not have to be an error if the file has been tagged as a ghost file. +""" +multiple-entries=""" +Libalternatives configuration file has multiple entries for that key. +""" +wrong-entry-format=""" +The libalternatives configuration file has a wrong entry format (key=value). +""" +binary-entry-value-not-found=""" +The libalternatives configuration file has wrong value in binary entry. +""" +man-entry-value-not-found=""" +The value of the man entry in libalternatives configuration file has no corresponding package file entry. +""" +wrong-or-missed-binary-entry=""" +Binary entry in libalternatives configuration file has no corresponding package file entry. +""" +wrong-tag-found=""" +Unknown tag in libalternatives configuration file. +""" + diff --git a/rpmlint/descriptions/AppDataCheck.toml b/rpmlint/descriptions/AppDataCheck.toml new file mode 100644 index 0000000..af5e392 --- /dev/null +++ b/rpmlint/descriptions/AppDataCheck.toml @@ -0,0 +1,3 @@ +invalid-appdata-file = """ +Appdata file is not valid. Check the validity with appstream-util. +""" diff --git a/rpmlint/descriptions/BashismsCheck.toml b/rpmlint/descriptions/BashismsCheck.toml new file mode 100644 index 0000000..7c8acfc --- /dev/null +++ b/rpmlint/descriptions/BashismsCheck.toml @@ -0,0 +1,7 @@ +bin-sh-syntax-error=""" +A /bin/sh shell script contains a POSIX shell syntax error. +This might indicate a potential bash-specific feature being used, +try dash -n <file> for more detailed error message.""" +potential-bashisms=""" +checkbashisms reported potential bashisms in a /bin/sh shell +script, you might want to manually check this script for bashisms.""" diff --git a/rpmlint/descriptions/BinariesCheck.toml b/rpmlint/descriptions/BinariesCheck.toml new file mode 100644 index 0000000..6fb084d --- /dev/null +++ b/rpmlint/descriptions/BinariesCheck.toml @@ -0,0 +1,185 @@ +arch-independent-package-contains-binary-or-object = """ +The package is marked as noarch and contains a binary or object file. +""" +arch-dependent-file-in-usr-share=""" +This package installs an ELF binary in the /usr/share hierarchy, which is +reserved for architecture-independent files only. +""" +binary-in-etc=""" +This package installs an ELF binary in /etc. +""" +noarch-with-lib64=""" +This package is marked as noarch but installs files into lib64. +Not all architectures have this in path, so the package can't be noarch. +""" +no-soname=""" +The library has no soname. +""" +invalid-soname=""" +The soname of the library is neither of the form lib<libname>.so.<major> or +lib<libname>-<major>.so. +""" +invalid-ldconfig-symlink=""" +The symbolic link references the wrong file. It should reference +the shared library. +""" +no-ldconfig-symlink=""" +The package should not only include the shared library itself, but +also the symbolic link which ldconfig would produce. (This is +necessary, so that the link gets removed by rpm automatically when +the package gets removed). +""" +shlib-with-non-pic-code=""" +The listed shared libraries contain object code that was compiled +without -fPIC. All object code in shared libraries should be +recompiled separately from the static libraries with the -fPIC option. +Use the ``eu-findtextrel'' command on a library with debugging symbols +to list code compiled without -fPIC. + +Another common mistake that causes this problem is linking with +``gcc -Wl,-shared'' instead of ``gcc -shared''. +""" +libtool-wrapper-in-package=""" +The package contains a libtool wrapper shell script. Instead of installing +the libtool wrapper file run +``libtool --mode=install install -m perm <file> <dest>'' in order +to install the relinked file. +""" +binary-or-shlib-defines-rpath=""" +The binary or shared library defines `RPATH' (or `RUNPATH') that points +to a non-system library path. +""" +statically-linked-binary=""" +The package installs a statically linked binary or object file. +""" +executable-in-library-package=""" +The package mixes up libraries and executables. Mixing up these +both types of files makes upgrades quite impossible. +""" +non-versioned-file-in-library-package=""" +The package contains files in non versioned directories. This makes it +impossible to have multiple major versions of the libraries installed. +One solution can be to change the directories which contain the files +to subdirs of /usr/lib/<name>-<version> or /usr/share/<name>-<version>. +Another solution can be to include a version number in the file names +themselves. +""" +shlib-policy-name-error=""" +The package contains shared library but is not named after its SONAME. +""" +invalid-la-file=""" +This .la file contains a reference to /tmp or /home. +""" +no-binary=""" +The package should be of the noarch architecture because it doesn't contain +any binaries. +""" +undefined-non-weak-symbol=""" +The binary contains undefined non-weak symbols. +""" +unused-direct-shlib-dependency=""" +The binary contains unused direct shared library dependencies. This may +indicate gratuitously bloated linkage; check that the binary has been linked +with the intended shared libraries only. +""" +only-non-binary-in-usr-lib=""" +There are only non binary files in /usr/lib so they should be in +/usr/share. +""" +readelf-failed=""" +Executing readelf on this file failed, all checks could not be run. +""" +ldd-failed=""" +Executing ldd on this file failed, all checks could not be run. +""" +strings-failed=""" +Executing strings on this file failed, all checks could not be run. +""" +objdump-failed=""" +Executing objdump on this file failed, all checks could not be run. +""" +executable-stack=""" +The binary declares the stack as executable. Executable stack is usually an +error as it is only needed if the code contains GCC trampolines or similar +constructs which uses code on the stack. One common source for needlessly +executable stack cases are object files built from assembler files which +don't define a proper .note.GNU-stack section. +""" +missing-PT_GNU_STACK-section=""" +The binary lacks a PT_GNU_STACK section. This forces the dynamic linker to +make the stack executable. +""" +non-position-independent-executable=""" +This executable must be position independent. Check that it is built with +-fPIE/-fpie in compiler flags and -pie in linker flags. +""" +position-independent-executable-suggested=""" +This executable should be position independent (all binaries should). Check +that it is built with -fPIE/-fpie in compiler flags and -pie in linker flags. +""" +missing-call-to-setgroups-before-setuid=""" +This executable is calling setuid and setgid without setgroups or initgroups. +This means it didn't relinquish all groups, and this would be a potential +security issue. +""" +call-to-mktemp=""" +This executable calls mktemp. As advised by the manpage (mktemp(3)), this +function should be avoided. +""" +unstripped-binary-or-object=""" +This executable should be stripped from debugging symbols, in order to take +less space and be loaded faster. This is usually done automatically at +buildtime by rpm. +""" +lto-bytecode=""" +This executable contains a LTO section. LTO bytecode is not portable +and should not be distributed in static libraries or e.g. Python modules. +""" +lto-no-text-in-archive=""" +This archive does not contain a non-empty .text section. The archive +was not created with -ffat-lto-objects option. +""" +linked-against-opt-library=""" +This executable is linked against a shared library in /opt folder. +""" +linked-against-usr-library=""" +Libraries and executables under /bin, /sbin, /lib and /lib64 may not link +against a shared library in /usr folder. +""" +static-library-without-symtab=""" +The static library doesn't contain any symbols and therefore can't be linked +against. This may indicate a stripped archive. +""" +binary-or-shlib-calls-gethostbyname=""" +The binary calls gethostbyname. Please port the code to use getaddrinfo. +""" +static-library-without-debuginfo=""" +The static library doesn't contain any debuginfo. Binaries linking against +this static library can't be properly debugged. +""" +shared-library-not-executable=""" +This library doesn't have the executable bit set. Without this bit set, +rpm for instance won't be able identify the file as a library and not +generate dependencies or strip debug symbols from it. +""" +program-not-linked-against-libc=""" +The binary is not dynamically linked against libc. +""" +missing-mandatory-optflags=""" +This executable was not compiled with expected flags. +""" +forbidden-optflags=""" +This executable was compiled with an unexpected flag. +""" +missing-hash-section=""" +SystemV requires each shared library must provide .hash section that +is used for efficient symbol resolution. +""" +missing-gnu-hash-section=""" +The .gnu.hash section is missing and leads to a slower symbol resolution +during dynamic linking. +""" +patchable-function-entry-in-archive=""" +This archive contains a __patchable_function_entries section and can +accidentally make a shared library or an executable live-patchable. +""" diff --git a/rpmlint/descriptions/BuildDateCheck.toml b/rpmlint/descriptions/BuildDateCheck.toml new file mode 100644 index 0000000..26fc97e --- /dev/null +++ b/rpmlint/descriptions/BuildDateCheck.toml @@ -0,0 +1,8 @@ +file-contains-current-date=""" +Your file contains the current date, this may cause the package +to rebuild in excess. +""" +file-contains-date-and-time=""" +Your file uses __DATE__ and __TIME__ which causes the package to +rebuild when not needed. +""" diff --git a/rpmlint/descriptions/BuildRootCheck.toml b/rpmlint/descriptions/BuildRootCheck.toml new file mode 100644 index 0000000..c74d4c6 --- /dev/null +++ b/rpmlint/descriptions/BuildRootCheck.toml @@ -0,0 +1,2 @@ +file-contains-buildroot=""" +Your file contains traces of %{buildroot}.""" diff --git a/rpmlint/descriptions/CheckForXinetd.toml b/rpmlint/descriptions/CheckForXinetd.toml new file mode 100644 index 0000000..0175e62 --- /dev/null +++ b/rpmlint/descriptions/CheckForXinetd.toml @@ -0,0 +1,5 @@ +obsolete-xinetd-requirement=""" +Xinetd is obsolete by systemd socket activated services. +Please stop using xinetd and switch to socket activation +from systemd. +""" diff --git a/rpmlint/descriptions/ConfigFilesCheck.toml b/rpmlint/descriptions/ConfigFilesCheck.toml new file mode 100644 index 0000000..76e1659 --- /dev/null +++ b/rpmlint/descriptions/ConfigFilesCheck.toml @@ -0,0 +1,11 @@ +non-etc-or-var-file-marked-as-conffile=""" +A file not in /etc or /var is marked as being a configuration file (%config). +Please put your configuration files in /etc or /var. +""" +conffile-without-noreplace-flag=""" +A configuration file is stored in your package without the noreplace flag. +This flag tells RPM not to overwrite or replace a configuration file to protect +local modifications. +A way to resolve this is to put the following in your SPEC file: +%config(noreplace) /etc/your_config_file_here +""" diff --git a/rpmlint/descriptions/DBusPolicyCheck.toml b/rpmlint/descriptions/DBusPolicyCheck.toml new file mode 100644 index 0000000..cfb94a1 --- /dev/null +++ b/rpmlint/descriptions/DBusPolicyCheck.toml @@ -0,0 +1,15 @@ +dbus-policy-allow-without-destination=""" +'allow' directives must always specify a 'send_destination'.""" +dbus-policy-allow-receive=""" +allow receive_* is normally not needed as that is the default.""" +dbus-policy-deny-without-destination=""" +'deny' directives must always specify a 'send_destination' +otherwise messages to other services could be blocked.""" +dbus-policy-missing-allow=""" +Every dbus config normally needs a line of the form +<allow send_destination="org.foo.bar"/> +or similar. If that is missing the service +will not work with a dbus that uses deny as default policy""" +dbus-parsing-exception=""" +A python exception was raised which prevents further analysis +of the DBus rule file.""" diff --git a/rpmlint/descriptions/DocCheck.toml b/rpmlint/descriptions/DocCheck.toml new file mode 100644 index 0000000..eb5612d --- /dev/null +++ b/rpmlint/descriptions/DocCheck.toml @@ -0,0 +1,17 @@ +executable-docs=""" +Documentation should not be executable. +""" +doc-file-dependency=""" +A file marked as %doc creates a possible additional dependency in the package. +This is not wanted and may be caused by example scripts with executable bits +set included in the package's documentation. +""" +install-file-in-docs=""" +A file whose name suggests that it contains installation instructions is +included in the package. Such instructions are often not relevant for already +installed packages. +""" +package-with-huge-docs=""" +More than half the size of your package is documentation. +Consider splitting it into a -doc subpackage. +""" diff --git a/rpmlint/descriptions/DuplicatesCheck.toml b/rpmlint/descriptions/DuplicatesCheck.toml new file mode 100644 index 0000000..aea8dd1 --- /dev/null +++ b/rpmlint/descriptions/DuplicatesCheck.toml @@ -0,0 +1,20 @@ +files-duplicate=""" +Your package contains duplicated files that are not hard- or symlinks. +You should use the %fdupes macro to link the files to one. +""" +files-duplicated-waste=""" +Your package contains duplicated files that are not hard- or symlinks. +You should use the %fdupes macro to link the files to one. +""" +hardlink-across-partition=""" +Your package contains two files that are apparently hardlinked and +that are likely on different partitions. Installation of such an RPM will fail +due to RPM being unable to unpack the hardlink. Do not hardlink across +the first two levels of a path, e.g. between /var/ftp and /var/www or +/etc and /usr. +""" +hardlink-across-config-files=""" +Your package contains two config files that are apparently hardlinked. +Hardlinking a config file is probably not what you want. Please double +check and report false positives. +""" diff --git a/rpmlint/descriptions/ErlangCheck.toml b/rpmlint/descriptions/ErlangCheck.toml new file mode 100644 index 0000000..f3b1a0e --- /dev/null +++ b/rpmlint/descriptions/ErlangCheck.toml @@ -0,0 +1,15 @@ +beam-compile-info-missed=""" +Your beam file has missed compile info chunk. +""" +beam-compiled-without-debuginfo=""" +Your beam file indicates that it doesn't contain debug_info. +Please, make sure that you compile with +debug_info. +""" +beam-was-not-recompiled=""" +It seems that your beam file was not compiled by you, but was +just copied in binary form to destination. Please, make sure +that you really compile it from the sources. +""" +pybeam-failed=""" +Invocation of the Python pybeam library failed. +""" diff --git a/rpmlint/descriptions/FilesCheck.toml b/rpmlint/descriptions/FilesCheck.toml new file mode 100644 index 0000000..30a59a3 --- /dev/null +++ b/rpmlint/descriptions/FilesCheck.toml @@ -0,0 +1,411 @@ +no-documentation=""" +The package contains no documentation (README, doc, etc). +You have to include documentation files. +""" +not-listed-as-documentation=""" +The documentation files of this package are not listed with +the standard %doc tag. +""" +library-without-ldconfig-postin=""" +This package contains a library and provides no %post scriptlet containing +a call to ldconfig. +""" +postin-without-ldconfig=""" +This package contains a library and its %post scriptlet doesn't call +ldconfig. +""" +library-without-ldconfig-postun=""" +This package contains a library and provides no %postun scriptlet containing +a call to ldconfig. +""" +postun-without-ldconfig=""" +This package contains a library and its %postun doesn't call ldconfig. +""" +info-files-without-install-info-postin=""" +This package contains info files and provides no %post scriptlet containing +a call to install-info. +""" +postin-without-install-info=""" +This package contains info files and its %post doesn't call install-info. +""" +info-files-without-install-info-postun=""" +This package contains info files and provides no %postun scriptlet +containing a call to install-info. +""" +postun-without-install-info=""" +This package contains info files and its %postun doesn't call +install-info. +""" +perl-temp-file=""" +You have a perl temporary file in your package. Usually, this +file is beginning with a dot (.) and contain 'perl' in its name. +""" +non-ghost-in-run=""" +A file or directory in the package is located in /run. Files installed +in this directory should be marked as %ghost and created at runtime to work +properly in tmpfs /run setups. +""" +systemd-unit-in-etc=""" +A systemd unit has been packaged in /etc/systemd/system. These units should +be installed in the system unit dir instead. +""" +udev-rule-in-etc=""" +A udev rule has been packaged in /etc/udev/rules.d. These rules should be +installed in the system rules dir instead. +""" +tmpfiles-conf-in-etc=""" +A tmpfiles config has been packaged in /etc/tmpfiles.d. These rules should be +installed in the system tmpfiles dir instead. +""" +subdir-in-bin=""" +The package contains a subdirectory in /usr/bin. It's not permitted to +create a subdir there. Create it in /usr/lib/ instead. +""" +backup-file-in-package=""" +You have a file whose name looks like one for backup files, usually created +by an editor or resulting from applying unclean (fuzzy, or ones with line +offsets) patches. +""" +version-control-internal-file=""" +You have included file(s) internally used by a version control system +in the package. Move these files out of the package and rebuild it. +""" +htaccess-file=""" +You have individual apache configuration .htaccess file(s) in your package. +Replace them by a central configuration file in /etc/, according to the web +application packaging policy for your distribution. +""" +info-dir-file=""" +You have /usr/info/dir or /usr/share/info/dir in your package. It will cause +conflicts with other packages and thus is not allowed. Please remove it and +rebuild your package. +""" +non-conffile-in-etc=""" +A non-executable file in your package is being installed in /etc, but is not +a configuration file. All non-executable files in /etc should be configuration +files. Mark the file as %config in the spec file. +""" +compressed-symlink-with-wrong-ext=""" +The symlink points to a compressed file but doesn't use the same +extension. +""" +setuid-binary=""" +The file is setuid; this may be dangerous, especially if this +file is setuid root. Sometimes file capabilities can be used instead of +setuid bits. +""" +setgid-binary=""" +The file is setgid. Usually this is a packaging bug. If this is a game, +then, you should use the proper rpm group, or location. +""" +non-standard-executable-perm=""" +A standard executable should have permission set to 0755. If you get this +message, it means that you have a wrong executable permissions in some files +included in your package. +""" +non-executable-in-bin=""" +A file is being installed in /usr/bin, but is not an executable. Be sure +that the file is an executable or that it has executable permissions. +""" +devel-file-in-non-devel-package=""" +A file that is needed only e.g. when developing or building software is +included in a non-devel package. These files should go in devel packages. +""" +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 +included in your package. +""" +spurious-executable-perm=""" +The file is installed with executable permissions, but was identified as one +that probably should not be executable. Verify if the executable bits are +desired, and remove if not. +""" +world-writable=""" +A file or directory in the package is installed with world writable +permissions, which is most likely a security issue. +""" +standard-dir-owned-by-package=""" +This package owns a directory that is part of the standard hierarchy, which +can lead to default directory permissions or ownerships being changed to +something non-standard. +""" +cross-directory-hard-link=""" +File is hard linked across directories. This can cause problems in +installations where the directories are located on different devices. +""" +dangling-symlink=""" +The target of the symbolic link does not exist within this package or its +file based dependencies. Verify spelling of the link target and that the +target is included in a package in this package's dependency chain. +""" +symlink-should-be-relative=""" +Absolute symlinks are problematic eg. when working with chroot environments. +symlinks(8) is a tool that can be useful for creating/dealing with relative +symlinks at package build time. +""" +dangling-relative-symlink=""" +The target of the symbolic link does not exist within this package or its +file based dependencies. Verify spelling of the link target and that the +target is included in a package in this package's dependency chain. +""" +non-readable=""" +The file can't be read by everybody. Review if this is expected. +""" +incoherent-logrotate-file=""" +Your logrotate file should be named /etc/logrotate.d/<package name>. +""" +non-root-user-log-file=""" +If you need log files owned by a non-root user, just create a subdir in +/var/log and put your log files in it. +""" +non-root-group-log-file=""" +If you need log files owned by a non-root group, just create a subdir in +/var/log and put your log files in it. +""" +non-ghost-file=""" +File should be tagged %ghost. +""" +outside-libdir-files=""" +This library package must not contain non library files to allow 64 +and 32 bits versions of the package to coexist. +""" +hidden-file-or-dir=""" +The file or directory is hidden. You should see if this is normal, +and delete it from the package if not. +""" +module-without-depmod-postin=""" +This package contains a kernel module but provides no call to depmod in the +%post scriptlet. +""" +postin-with-wrong-depmod=""" +This package contains a kernel module but its %post scriptlet calls depmod +for the wrong kernel. +""" +module-without-depmod-postun=""" +This package contains a kernel module but provides no call to depmod in the +%postun scriptlet. +""" +postun-with-wrong-depmod=""" +This package contains a kernel module but its %postun scriptlet calls depmod +for the wrong kernel. +""" +log-files-without-logrotate=""" +This package contains files in /var/log/ without adding logrotate +configuration for them. +""" +unexpanded-macro=""" +This package contains a file whose path contains something that looks like +an unexpanded macro; this is often the sign of a misspelling. Please check your +specfile. +""" +manifest-in-perl-module=""" +This perl module package contains a MANIFEST or a MANIFEST.SKIP file +in the documentation directory. +""" +siteperl-in-perl-module=""" +This perl module package installs files under the subdirectory site_perl, +while they must appear under vendor_perl. +""" +executable-marked-as-config-file=""" +Executables must not be marked as config files because that may +prevent upgrades from working correctly. If you need to be able to +customize an executable, make it for example read a config file in +/etc/sysconfig. +""" +sourced-script-with-shebang=""" +This text file contains a shebang, but is meant to be sourced, not +executed. +""" +executable-sourced-script=""" +This text file has executable bit set, but is meant to be sourced, not +executed. +""" +wrong-script-interpreter=""" +This script uses an interpreter which is either an inappropriate one +or located in an inappropriate directory for packaged system software. + +Alternatively, if the file should not be executed, then ensure that +it is not marked as executable. +""" +env-script-interpreter=""" +This script uses 'env' as an interpreter. +For the rpm runtime dependency detection to work, the shebang +#!/usr/bin/env <interpreter> + +needs to be patched into +#!/usr/bin/<interpreter> + +otherwise the package dependency generator merely adds a dependency +on /usr/bin/env rather than the actual interpreter /usr/bin/<interpreter>. + +Alternatively, if the file should not be executed, then ensure that +it is not marked as executable or don't install it in a path that +is reserved for executables. +""" +non-executable-script=""" +This text file contains a shebang or is located in a path dedicated for +executables, but lacks the executable bits and cannot thus be executed. If +the file is meant to be an executable script, add the executable bits, +otherwise remove the shebang or move the file elsewhere. +""" +script-without-shebang=""" +This text file has executable bits set or is located in a path dedicated +for executables, but lacks a shebang and cannot thus be executed. If the file +is meant to be an executable script, add the shebang, otherwise remove the +executable bits or move the file elsewhere. +""" +wrong-script-end-of-line-encoding=""" +This script has wrong end-of-line encoding, usually caused by creation or +modification on a non-Unix system. It will prevent its execution. +""" +wrong-file-end-of-line-encoding=""" +This file has wrong end-of-line encoding, usually caused by creation or +modification on a non-Unix system. It could prevent it from being displayed +correctly in some circumstances. +""" +file-not-utf8=""" +The character encoding of this file is not UTF-8. Consider converting it +in the specfile's %prep section for example using iconv(1). +""" +filename-not-utf8=""" +The character encoding of the name of this file is not UTF-8. +Rename it. +""" +file-in-meta-package=""" +This package seems to be a meta-package (an empty package used to require +other packages), but it is not empty. You should remove or rename it, see the +option MetaPackageRegexp. +""" +empty-debuginfo-package=""" +This debuginfo package contains no files. This is often a sign of binaries +being unexpectedly stripped too early during the build, rpmbuild not being able +to strip the binaries, the package actually being a noarch one but erratically +packaged as arch dependent, or something else. Verify what the case is, and +if there's no way to produce useful debuginfo out of it, disable creation of +the debuginfo package. +""" +debuginfo-without-sources=""" +This debuginfo package appears to contain debug symbols but no source files. +This is often a sign of binaries being unexpectedly stripped too early during +the build, or being compiled without compiler debug flags (which again often +is a sign of distro's default compiler flags ignored which might have security +consequences), or other compiler flags which result in rpmbuild's debuginfo +extraction not working as expected. Verify that the binaries are not +unexpectedly stripped and that the intended compiler flags are used. +""" +missing-dependency-to-crontabs=""" +This package installs a file in /etc/cron.*/ but +doesn't require crontabs to be installed. As crontabs is not part of the essential packages, +your package should explicitely require crontabs to make sure that your cron job is +executed. If it is an optional feature of your package, recommend or suggest crontabs. +""" +missing-dependency-to-logrotate=""" +This package installs a file in /etc/logrotate.d/ but +doesn't require logrotate to be installed. Because logrotate is not part of the essential packages, +your package should explicitely depend on logrotate to make sure that your logrotate +job is executed. If it is an optional feature of your package, recommend or suggest logrotate. +""" +missing-dependency-to-xinetd=""" +This package installs a file in /etc/xinetd.d/ but +doesn't require xinetd to be installed. Because xinetd is not part of the essential packages, +your package should explicitely depend on logrotate to make sure that your xinetd +job is executed. If it is an optional feature of your package, recommend or suggest xinetd. +""" +read-error=""" +This file could not be read. A reason for this could be that the info about +it in the rpm header indicates that it is supposed to be a readable normal file +but it actually is not in the filesystem. Because of this, some checks will +be skipped. +""" +inaccessible-filename=""" +An error occurred while trying to access this file due to some characters +in its name. Because of this, some checks will be skipped. Access could work +with some other locale settings. +""" +executable-crontab-file=""" +This crontab file has executable bit set, which is refused by newer version +of cron +""" +non-owner-writeable-only-crontab-file=""" +This crontab file is writeable by other users as its owner, which is refused +by newer version of cron and insecure +""" +symlink-crontab-file=""" +This crontab file is a symbolic link, which is insecure and refused by newer +version of cron +""" +rpath-in-buildconfig=""" +This build configuration file contains rpaths which will be introduced into +dependent packages. +""" +python-bytecode-wrong-magic-value=""" +The 'magic' ABI version embedded in this python bytecode file isn't equal +to that of the corresponding runtime, which will force the interpreter to +recompile the .py source every time, ignoring the saved bytecode. +""" +python-bytecode-inconsistent-mtime=""" +The timestamp embedded in this python bytecode file isn't equal to the mtime +of the original source file, which will force the interpreter to recompile the +.py source every time, ignoring the saved bytecode. +""" +python-bytecode-without-source=""" +This python bytecode file (.pyo/.pyc) is not accompanied by its original +source file (.py) +""" +duplicate-executable=""" +This executable file exists in more than one standard binary directories. +It can cause problems when dirs in $PATH are reordered. +""" +no-manual-page-for-binary=""" +Each executable in standard binary directories should have a man page. +""" +incorrect-fsf-address=""" +The Free Software Foundation address in this file seems to be outdated or +misspelled. Ask upstream to update the address, or if this is a license file, +possibly the entire file with a new copy available from the FSF. +""" +gzipped-svg-icon=""" +Not all desktop environments that support SVG icons support them gzipped +(.svgz). Install the icon as plain uncompressed SVG. +""" +pem-certificate=""" +Shipping a PEM certificate is likely wrong. If used for the default +configuration, this is insecure ( since the certificate is public ). If this +is used for validation, ie a CA certificate store, then this must be kept up +to date due to CA compromise. The only valid reason is for testing purpose, +so ignore this warning if this is the case. +""" +pem-private-key=""" +Private key in a .pem file should not be shipped in a rpm, unless +this is for testing purpose ( ie, run by the test suite ). Shipping it +as part of the example documentation mean that someone will sooner or later +use it and setup a insecure configuration. +""" +tcl-extension-file=""" +Script libraries for Tcl extensions should be in a package-specific +subdir of /usr/share/tcl. +""" +makefile-junk=""" +Your package contains makefiles that only make sense in a +source package. Did you package a complete directory from the +tarball by using %doc? Consider removing Makefile* from this +directory at the end of your %install section to reduce bloat. +""" +bad-manual-page-folder=""" +Manual page with section name X (e.g. man.1) should be placed +corresponding manual folder manX (man1). +""" +manual-page-in-subfolder=""" +Manual page should not be placed in a subfolder of a manual section +directory. +""" + +symlink-to-binary-with-shebang=""" +A file in /usr/bin is a link to a script in a different place with a shebang. +rpm won't be able to inject the needed interpreter as dependency, so it should +be done manually. +""" diff --git a/rpmlint/descriptions/I18NCheck.toml b/rpmlint/descriptions/I18NCheck.toml new file mode 100644 index 0000000..1437b6c --- /dev/null +++ b/rpmlint/descriptions/I18NCheck.toml @@ -0,0 +1,16 @@ +"subfile-not-in-%lang"=""" +If /foo/bar is not tagged %lang(XX) whereas /foo is, the package won't be +installable if XX is not in %_install_langs. +""" + +"invalid-lc-messages-dir"=""" +The package contains a languages file for a language code that's not recognized +by rpmlint, in a path like /usr/share/locale/LANGCODE/LC_MESSAGES/FILE.mo. +please report a bug if the LANGCODE is correct. +""" + +"invalid-locale-man-dir"=""" +The package contains a man page file for a language code that's not recognized +by rpmlint, in a path like /usr/share/man/LANGCODE/man1/FILE.1.gz. +please report a bug if the LANGCODE is correct. +""" diff --git a/rpmlint/descriptions/IconSizesCheck.toml b/rpmlint/descriptions/IconSizesCheck.toml new file mode 100644 index 0000000..08b6319 --- /dev/null +++ b/rpmlint/descriptions/IconSizesCheck.toml @@ -0,0 +1,4 @@ +wrong-icon-size=""" +Your icon file is installed in a fixed-size directory, but has a +largely incorrect size. Some desktop environments (e.g. GNOME) +display them incorrectly.""" diff --git a/rpmlint/descriptions/InitScriptCheck.toml b/rpmlint/descriptions/InitScriptCheck.toml new file mode 100644 index 0000000..40a80df --- /dev/null +++ b/rpmlint/descriptions/InitScriptCheck.toml @@ -0,0 +1,75 @@ +init-script-without-chkconfig-postin=""" +The package contains an init script but doesn't contain a %post with +a call to chkconfig. +""" +postin-without-chkconfig=""" +The package contains an init script but doesn't call chkconfig in its +%post script. +""" +init-script-without-chkconfig-preun=""" +The package contains an init script but doesn't contain a %preun with +a call to chkconfig. +""" +preun-without-chkconfig=""" +The package contains an init script but doesn't call chkconfig in its +%preun script. +""" +missing-lsb-keyword=""" +The package contains an init script that does not contain one of the LSB +init script comment block convention keywords that are recommendable for all +init scripts. If there is nothing to add to a keyword's value, include the +keyword in the script with an empty value. Note that as of version 3.2, the +LSB specification does not mandate presence of any keywords. +""" +no-status-entry=""" +In your init script (/etc/rc.d/init.d/your_file), you don't +have a 'status' entry, which is necessary for good functionality. +""" +no-reload-entry=""" +In your init script (/etc/rc.d/init.d/your_file), you don't +have a 'reload' entry, which is necessary for good functionality. +""" +no-chkconfig-line=""" +The init script doesn't contain a chkconfig line to specify the runlevels +at which to start and stop it. +""" +no-default-runlevel=""" +The default runlevel isn't specified in the init script. +""" +service-default-enabled=""" +The service is enabled by default after 'chkconfig --add'; for security +reasons, most services should not be. Use '-' as the default runlevel in the +init script's 'chkconfig:' line and/or remove the 'Default-Start:' LSB keyword +to fix this if appropriate for this service. +""" +subsys-unsupported=""" +The init script uses /var/lock/subsys which is not supported by +this distribution. +""" +subsys-not-used=""" +While your daemon is running, you have to put a lock file in +/var/lock/subsys/. To see an example, look at this directory on your +machine and examine the corresponding init scripts. +""" +incoherent-subsys=""" +The filename of your lock file in /var/lock/subsys/ is incoherent +with your actual init script name. For example, if your script name +is httpd, you have to use 'httpd' as the filename in your subsys directory. +It is also possible that rpmlint gets this wrong, especially if the init +script contains nontrivial shell variables and/or assignments. These +cases usually manifest themselves when rpmlint reports that the subsys name +starts a with '$'; in these cases a warning instead of an error is reported +and you should check the script manually. +""" +incoherent-init-script-name=""" +The init script name should be the same as the package name in lower case, +or one with 'd' appended if it invokes a process by that name. +""" +init-script-name-with-dot=""" +The init script name should not contain a dot in its name. Some versions +of chkconfig don't work as expected with init script names like that. +""" +init-script-non-executable=""" +The init script should have at least the execution bit set for root +in order for it to run at boot time. +""" diff --git a/rpmlint/descriptions/LSBCheck.toml b/rpmlint/descriptions/LSBCheck.toml new file mode 100644 index 0000000..de66b73 --- /dev/null +++ b/rpmlint/descriptions/LSBCheck.toml @@ -0,0 +1,15 @@ +non-lsb-compliant-package-name=""" +Your package name contains an illegal character that is not +LSB-compliant. Use only lowercase letters, numbers, '.', '+' +or '-' characters. +""" +non-lsb-compliant-version=""" +Your version number contains an illegal character that is not +LSB-compliant. Use only alphanumeric symbols, '.' or '+' +characters. +""" +non-lsb-compliant-release=""" +Your release number contains an illegal character that is not +LSB-compliant. Use only alphanumeric symbols, '.' or '+' +characters. +""" diff --git a/rpmlint/descriptions/LibraryDependencyCheck.toml b/rpmlint/descriptions/LibraryDependencyCheck.toml new file mode 100644 index 0000000..6cc7a9c --- /dev/null +++ b/rpmlint/descriptions/LibraryDependencyCheck.toml @@ -0,0 +1,7 @@ +no-library-dependency-for=""" +The package misses dependency on a library package that provides the shared library. +""" + +no-library-dependency-on=""" +The package misses dependency on a package which file it links to. +""" diff --git a/rpmlint/descriptions/LogrotateCheck.toml b/rpmlint/descriptions/LogrotateCheck.toml new file mode 100644 index 0000000..1cdb2a3 --- /dev/null +++ b/rpmlint/descriptions/LogrotateCheck.toml @@ -0,0 +1,10 @@ +logrotate-duplicate=""" +There are dupliated logrotate entries with different settings for +the specified file.""" +logrotate-user-writable-log-dir=""" +The log directory is writable by unprivileged users. Please fix +the permissions so only root can write there or add the 'su' option +to your logrotate config.""" +logrotate-log-dir-not-packaged=""" +Please add the specified directory to the file list to be able to +check permissions.""" diff --git a/rpmlint/descriptions/MenuCheck.toml b/rpmlint/descriptions/MenuCheck.toml new file mode 100644 index 0000000..ed0149e --- /dev/null +++ b/rpmlint/descriptions/MenuCheck.toml @@ -0,0 +1,92 @@ +non-file-in-menu-dir=""" +The directory /usr/lib/menu must not contain anything else than normal files. +""" +non-coherent-menu-filename=""" +The menu file name should be /usr/lib/menu/<package>. +""" +non-readable-menu-file=""" +The menu file isn't readable. Check the permissions. +""" +non-transparent-xpm=""" +xpm icon should be transparent for use in menus. +""" +menu-without-postin=""" +A menu file exists in the package but no %post scriptlet is present to call +update-menus. +""" +postin-without-update-menus=""" +A menu file exists in the package but its %post scriptlet doesn't call +update-menus. +""" +menu-without-postun=""" +A menu file exists in the package but no %postun scriptlet is present to +call update-menus. +""" +postun-without-update-menus=""" +A menu file exists in the package but its %postun scriptlet doesn't call +update-menus. +""" +incoherent-package-value-in-menu=""" +The package field of the menu entry isn't the same as the package name. +""" +use-of-launcher-in-menu-but-no-requires-on=""" +The menu command uses a launcher but there is no dependency in the package +that contains it. +""" +menu-command-not-in-package=""" +The command used in the menu isn't included in the package. +""" +menu-longtitle-not-capitalized=""" +The longtitle field of the menu doesn't start with a capital letter. +""" +version-in-menu-longtitle=""" +The longtitle filed of the menu entry contains a version. This is bad +because it will be prone to error when the version of the package changes. +""" +no-longtitle-in-menu=""" +The longtitle field isn't present in the menu entry. +""" +menu-title-not-capitalized=""" +The title field of the menu entry doesn't start with a capital letter. +""" +version-in-menu-title=""" +The title filed of the menu entry contains a version. This is bad +because it will be prone to error when the version of the package changes. +""" +no-title-in-menu=""" +The title field isn't present in the menu entry. +""" +invalid-menu-section=""" +The section field of the menu entry isn't standard. +""" +unable-to-parse-menu-section=""" +rpmlint wasn't able to parse the menu section. Please report a bug. +""" +hardcoded-path-in-menu-icon=""" +The path of the icon is hardcoded in the menu entry. This prevents multiple +sizes of the icon from being found. +""" +normal-icon-not-in-package=""" +The normal icon isn't present in the package. +""" +mini-icon-not-in-package=""" +The mini icon isn't present in the package. +""" +large-icon-not-in-package=""" +The large icon isn't present in the package. +""" +no-icon-in-menu=""" +The menu entry doesn't contain an icon field. +""" +invalid-title=""" +The menu title contains invalid characters like /. +""" +missing-menu-command=""" +The menu file doesn't contain a command. +""" +menu-in-wrong-directory=""" +The menu files must be under /usr/lib/menu. +""" +non-xdg-migrated-menu=""" +The menu file has not been migrated to new XDG menu system. +""" diff --git a/rpmlint/descriptions/MenuXDGCheck.toml b/rpmlint/descriptions/MenuXDGCheck.toml new file mode 100644 index 0000000..1d59deb --- /dev/null +++ b/rpmlint/descriptions/MenuXDGCheck.toml @@ -0,0 +1,21 @@ +invalid-desktopfile=""" +The .desktop file is not valid, check with desktop-file-validate +""" +non-utf8-desktopfile=""" +The .desktop file is not encoded in UTF-8. +""" +desktopfile-without-binary=""" +The .desktop file is for a file not present in the package. You +should check the requires or see if this is not a error. +""" +desktopfile-duplicate-section=""" +The .desktop file contains the mentioned section name twice, which +can trigger parsing ambiguities. Remove the duplicate. +""" +desktopfile-duplicate-option=""" +The .desktop file contains the mentioned option key twice, +which can trigger parsing ambiguities. Remove the duplicate. +""" +desktopfile-missing-header=""" +The .desktop file should start with a section header. +""" diff --git a/rpmlint/descriptions/MixedOwnershipCheck.toml b/rpmlint/descriptions/MixedOwnershipCheck.toml new file mode 100644 index 0000000..5690d3d --- /dev/null +++ b/rpmlint/descriptions/MixedOwnershipCheck.toml @@ -0,0 +1,5 @@ +file-parent-ownership-mismatch=""" +A file or directory is stored in a directory owned by another unprivileged user. +This is a security issue since the owner of the parent directory can replace this +file/directory with a different one. +""" diff --git a/rpmlint/descriptions/PAMModulesCheck.toml b/rpmlint/descriptions/PAMModulesCheck.toml new file mode 100644 index 0000000..8798782 --- /dev/null +++ b/rpmlint/descriptions/PAMModulesCheck.toml @@ -0,0 +1,5 @@ +pam-unauthorized-module=""" +The package installs a PAM module. If the package +is intended for inclusion the PAM module name must +be included in the white list. +""" diff --git a/rpmlint/descriptions/PkgConfigCheck.toml b/rpmlint/descriptions/PkgConfigCheck.toml new file mode 100644 index 0000000..180a5fb --- /dev/null +++ b/rpmlint/descriptions/PkgConfigCheck.toml @@ -0,0 +1,20 @@ +invalid-pkgconfig-file=""" +Your .pc file appears to be invalid. Possible causes are: +- it contains traces of $RPM_BUILD_ROOT or $RPM_BUILD_DIR. +- it contains unreplaced macros (@have_foo@) +- it references invalid paths (e.g. /home or /tmp) +""" +pkgconfig-invalid-libs-dir=""" +Your .pc file contains -L/usr/lib or -L/lib and is +built on a lib64 target, or contains references to -L/usr/lib64 or +-L/lib64 and is built for a lib target. +""" +double-slash-in-pkgconfig-path=""" +This pkg-config file contains a path with a double slash ('//') in it. This +will break debugedit when stripping debug symbols during package building if +these paths have been passed to gcc, and fail with the following error: +canonicalization unexpectedly shrank by one character. +""" +pkgconfig-exception=""" +An exception during parsing of .pc file has occurred. +""" diff --git a/rpmlint/descriptions/PythonCheck.toml b/rpmlint/descriptions/PythonCheck.toml new file mode 100644 index 0000000..aa5df50 --- /dev/null +++ b/rpmlint/descriptions/PythonCheck.toml @@ -0,0 +1,40 @@ +python-doc-in-package=""" +doc/ or docs/ directory in Python package directory. Documentation should go into %{docdir}, not %{python_sitelib}/<pkgname>""" +python-egg-info-distutils-style=""" +The Python package's egg-info is a distutils style file. +Please update to dist-info standardized core metadata. +""" +python-tests-in-site-packages=""" +test/ or tests/ directory in %{python_sitelib}. This should never happen since +this is a global name space not owned by any particular package. +""" +python-doc-in-site-packages=""" +doc/ or docs directory installed to %{python_sitelib}. This should never happen +since this is a global name space not owned by any particular package. +""" +python-src-in-site-packages=""" +src/ directory installed to %{python_sitelib}. This should never happen +since this is a global name space not owned by any particular package. +""" + +python-missing-require=""" +The python package declare some requirement that's not detected in the +rpm package. Please, verify that all dependencies are added as +Requires. +""" + +python-leftover-require=""" +Some python module Requires can't be found in the python package +requirements declaration. Please verify that all dependencies are +really needed. +""" + +python-pyc-multiple-versions=""" +There are .pyc files in the rpm that are from the different Python +interperters. Please, verify that all files are needed for this package. +""" + +python-sphinx-doctrees-leftover=""" +Cache Sphinx build folder found in the package ".doctrees". Please, make sure +to do not include any build files in the final package. +""" diff --git a/rpmlint/descriptions/SharedLibraryPolicyCheck.toml b/rpmlint/descriptions/SharedLibraryPolicyCheck.toml new file mode 100644 index 0000000..04388b2 --- /dev/null +++ b/rpmlint/descriptions/SharedLibraryPolicyCheck.toml @@ -0,0 +1,22 @@ +shlib-policy-excessive-dependency=""" +Your package starts with 'lib' as part of its name, but also contains binaries +that have more dependencies than those that already required by the libraries. +Those binaries should probably not be part of the library package, but split into +a seperate one to reduce the additional dependencies for other users of this library. +""" +shlib-policy-missing-lib=""" +Your package name looks its based on soname, but does not provide any libraries. +""" +shlib-fixed-dependency=""" +Your shared library package requires a fixed version of another package. The +intention of the Shared Library Policy is to allow parallel installation of +multiple versions of the same shared library, hard dependencies likely make that +impossible. Please remove this dependency and instead move it to the runtime uses +of your library. +""" +shlib-unversioned-lib=""" +Your package matches the Shared Library Policy Naming Scheme but contains an +unversioned library. Therefore it is very unlikely that your package can be installed +in parallel to another version of this library package. Consider moving unversioned +parts into another package. +""" diff --git a/rpmlint/descriptions/SignatureCheck.toml b/rpmlint/descriptions/SignatureCheck.toml new file mode 100644 index 0000000..3beb74c --- /dev/null +++ b/rpmlint/descriptions/SignatureCheck.toml @@ -0,0 +1,10 @@ +no-signature=""" +You have to include your pgp or gpg signature in your package. +""" +unknown-key=""" +The package was signed, but with an unknown key. See the rpm --import option +for more information. +""" +invalid-signature=""" +The package was signed, but the signature is corrupted. +""" diff --git a/rpmlint/descriptions/SourceCheck.toml b/rpmlint/descriptions/SourceCheck.toml new file mode 100644 index 0000000..b371a9b --- /dev/null +++ b/rpmlint/descriptions/SourceCheck.toml @@ -0,0 +1,12 @@ +multiple-specfiles=""" +Your package contains multiple spec files. To build a correct package, you need +to have only one spec file containing all your RPM information. +""" +strange-permission=""" +A file that you listed to include in your package has strange +permissions. Usually, a file should have 0644 permissions. +""" +inconsistent-file-extension=""" +The file name extension indicates a different compression format than +what is actually used (as checked by file(1)). +""" diff --git a/rpmlint/descriptions/SpecCheck.toml b/rpmlint/descriptions/SpecCheck.toml new file mode 100644 index 0000000..204d62a --- /dev/null +++ b/rpmlint/descriptions/SpecCheck.toml @@ -0,0 +1,203 @@ +no-spec-file=""" +No spec file was specified in your RPM metadata. Please specify a valid +SPEC file to build a valid RPM package. +""" +invalid-spec-name=""" +The spec file name (without the .spec suffix) must match the package name +('Name:' tag). +""" +non-utf8-spec-file=""" +The character encoding of the spec file is not UTF-8. +""" +use-of-RPM_SOURCE_DIR=""" +You use $RPM_SOURCE_DIR or %{_sourcedir} in your spec file. If you have to +use a directory for building, use $RPM_BUILD_ROOT instead. +""" +patch-not-applied=""" +A patch is included in your package but was not applied. +""" +obsolete-tag=""" +The following tags are obsolete: Copyright and Serial. They must +be replaced by License and Epoch respectively. +""" +deprecated-grep=""" +Direct use of grep as egrep or fgrep is deprecated in GNU grep and +historical in POSIX, use grep -E and grep -F instead. +""" +no-buildroot-tag=""" +The BuildRoot tag isn't used in your spec. It must be used in order to +allow building the package as non root on some systems. For some rpm versions +(e.g. rpm.org >= 4.6) the BuildRoot tag is not necessary in specfiles and is +ignored by rpmbuild; if your package is only going to be built with such rpm +versions you can ignore this warning. +""" +hardcoded-path-in-buildroot-tag=""" +A path is hardcoded in your Buildroot tag. It should be replaced +by something like %{_tmppath}/%{name}-%{version}-build. +""" +hardcoded-packager-tag=""" +The Packager tag is hardcoded in your spec file. It should be removed, so +as to use rebuilder's own defaults. +""" +buildarch-instead-of-exclusivearch-tag=""" +Use ExclusiveArch instead of BuildArch (or BuildArchitectures) +to restrict build on some specific architectures. +Only use BuildArch with noarch +""" +hardcoded-prefix-tag=""" +The Prefix tag is hardcoded in your spec file. It should be removed, so as +to allow package relocation. +""" +hardcoded-library-path=""" +A library path is hardcoded to one of the following paths: /lib, +/usr/lib. It should be replaced by something like /%{_lib} or %{_libdir}. +""" +configure-without-libdir-spec=""" +A configure script is run without specifying the libdir. configure +options must be augmented with something like --libdir=%{_libdir} whenever +the script supports it. +""" +"no-%prep-section"=""" +The spec file does not contain a %prep section. Even if some packages don't +directly need it, section markers may be overridden in rpm's configuration +to provide additional 'under the hood' functionality. Add the section, even +if empty. +""" +"no-%build-section"=""" +The spec file does not contain a %build section. Even if some packages +don't directly need it, section markers may be overridden in rpm's +configuration to provide additional 'under the hood' functionality, such as +injection of automatic -debuginfo subpackages. Add the section, even if +empty. +""" +"no-%install-section"=""" +The spec file does not contain an %install section. Even if some packages +don't directly need it, section markers may be overridden in rpm's +configuration to provide additional 'under the hood' functionality. Add the +section, even if empty. +""" +"more-than-one-%changelog-section"=""" +The spec file unnecessarily contains more than one %changelog section. +""" +"superfluous-%clean-section"=""" +The spec section %clean should not be used any longer. +RPM provides its own clean logic. +""" +"lib-package-without-%mklibname"=""" +The package name must be built using %mklibname to allow lib64 and lib32 +coexistence. +""" +"%ifarch-applied-patch"=""" +A patch is applied inside an %ifarch block. Patches must be applied +on all architectures and may contain necessary configure and/or code +patch to be effective only on a given arch. +""" +prereq-use=""" +The use of PreReq is deprecated. In the majority of cases, a plain Requires +is enough and the right thing to do. Sometimes Requires(pre), Requires(post), +Requires(preun) and/or Requires(postun) can also be used instead of PreReq. +""" +buildprereq-use=""" +The use of BuildPreReq is deprecated, build dependencies are always required +before a package can be built. Use plain BuildRequires instead. +""" +setup-not-in-prep=""" +The %setup macro should only be used within the %prep section because it may +not expand to anything outside of it and can break the build in unpredictable +ways. +""" +setup-not-quiet=""" +Use the -q option to the %setup macro to avoid useless build output from +unpacking the sources. +""" +rpm-buildroot-usage=""" +$RPM_BUILD_ROOT or %{buildroot} must not be touched during %build or %prep +stage, as it will break short circuit builds and will not persist to %install +stage in a normal build, leading to unexpected package build behavior. +""" +make-check-outside-check-section=""" +Make check or other automated regression test should be run in %check, as +they can be disabled with a rpm macro for short circuiting purposes. +""" +"macro-in-%changelog"=""" +Macros are expanded in %changelog too, which can in unfortunate cases lead +to the package not building at all, or other subtle unexpected conditions that +affect the build. Even when that doesn't happen, the expansion results in +possibly 'rewriting history' on subsequent package revisions and generally +odd entries eg. in source rpms, which is rarely wanted. Avoid use of macros +in %changelog altogether, or use two '%'s to escape them, like '%%foo'. +""" +depscript-without-disabling-depgen=""" +In some common rpm configurations/versions, defining __find_provides and/or +__find_requires has no effect if rpm's internal dependency generator has not +been disabled for the build. %define _use_internal_dependency_generator to 0 +to disable it in the specfile, or don't define __find_provides/requires. +""" +mixed-use-of-spaces-and-tabs=""" +The specfile mixes use of spaces and tabs for indentation, which is a +cosmetic annoyance. +""" +unversioned-explicit-provides=""" +The specfile contains an unversioned Provides: token, which will match all +older, equal, and newer versions of the provided thing. This may cause +update problems and will make versioned dependencies, obsoletions and conflicts +on the provided thing useless -- make the Provides versioned if possible. +""" +unversioned-explicit-obsoletes=""" +The specfile contains an unversioned Obsoletes: token, which will match all +older, equal and newer versions of the obsoleted thing. This may cause update +problems, restrict future package/provides naming, and may match something it +was originally not inteded to match -- make the Obsoletes versioned if +possible. +""" +libdir-macro-in-noarch-package=""" +The %{_libdir} or %{_lib} macro was found in a noarch package in a section +that gets included in binary packages. This is most likely an error because +these macros are expanded on the build host and their values vary between +architectures, probably resulting in a package that does not work properly +on all architectures at runtime. Investigate whether the package is really +architecture independent or if some other dir/macro should be instead. +""" +non-break-space=""" +The spec file contains a non-break space, which looks like a regular space +in some editors but can lead to obscure errors. It should be replaced by a +regular space. +""" +specfile-error=""" +This error occurred when rpmlint used rpm to query the specfile. The error +is output by rpm and the message should contain more information. +""" +comparison-operator-in-deptoken=""" +This dependency token contains a comparison operator (<, > or =). This is +usually not intended and may be caused by missing whitespace between the +token's name, the comparison operator and the version string. +""" +macro-in-comment=""" +There is a unescaped macro after a shell style comment in the specfile. +Macros are expanded everywhere, so check if it can cause a problem in this +case and escape the macro with another leading % if appropriate. +""" +patch-fuzz-is-changed=""" +The internal patch fuzz value was changed, and could hide patchs issues, or +could lead to applying a patch at the wrong location. Usually, this is often +the sign that someone didn't check if a patch is still needed and do not want +to rediff it. It is usually better to rediff the patch and try to send it +upstream. +""" +python-setup-test=""" +The python setup.py test subcommand is deprecated and should be replaced with a +modern testing tool like %pytest or %pyunittest discover -v. +""" +python-module-def=""" +The spec file contains a conditional definition of python_module macro, this +macro is present in recent versions of python-rpm-macros. +The following conditional python_module macro definition can be removed: +%{?!python_module:%define python_module() python-%{**} python3-%{**}} +""" +python-sitelib-glob-in-files=""" +The %files section contains "%{python_sitelib}/*" or "%{python_sitearch}/*" +that can get something not wanted in the package. Please use a more specific +file path like: +%{python_sitelib}/packagename +%{python_sitelib}/packagename-%{version}*-info +""" diff --git a/rpmlint/descriptions/SysVInitOnSystemdCheck.toml b/rpmlint/descriptions/SysVInitOnSystemdCheck.toml new file mode 100644 index 0000000..e37450e --- /dev/null +++ b/rpmlint/descriptions/SysVInitOnSystemdCheck.toml @@ -0,0 +1,12 @@ +obsolete-insserv-requirement=""" +In systemd based distributions insserv is obsolete. +Please remove dependencies on insserv.""" +deprecated-init-script=""" +SysV init scripts are deprecated. Please migrate to +systemd service files.""" +deprecated-boot-script=""" +SysV boot scripts are deprecated. Please migrate to +systemd service files.""" +systemd-shadowed-initscript=""" +The package contains both an init script and a systemd service +file for the same activity.""" diff --git a/rpmlint/descriptions/TagsCheck.toml b/rpmlint/descriptions/TagsCheck.toml new file mode 100644 index 0000000..59e5f53 --- /dev/null +++ b/rpmlint/descriptions/TagsCheck.toml @@ -0,0 +1,188 @@ +invalid-version=""" +The version string must not contain the pre, alpha, beta or rc suffixes +because when the final version will be out, you will have to use an Epoch tag +to make the package upgradable. Instead put it in the release tag, prefixed +with something you have control over. +""" +spelling-error=""" +The value of this tag appears to be misspelled. Please double-check. +""" +no-packager-tag=""" +There is no Packager tag in your package. You have to specify a packager +using the Packager tag. Ex: Packager: John Doe <john.doe@example.com>. +""" +invalid-packager=""" +The packager email must end with an email compatible with the Packager +option of rpmlint. Please change it and rebuild your package. +""" +no-version-tag=""" +There is no Version tag in your package. You have to specify a version using +the Version tag. +""" +no-release-tag=""" +There is no Release tag in your package. You have to specify a release using +the Release tag. +""" +no-name-tag=""" +There is no Name tag in your package. You have to specify a name using the +Name tag. +""" +non-coherent-filename=""" +The file which contains the package should be named <NAME>-<VERSION>-<RELEASE>.<ARCH>.rpm. +""" +no-major-in-name=""" +The major number of the library isn't included in the package's name. +""" +description-shorter-than-summary=""" +The package description should be longer than the summary. +""" +no-provides=""" +Your library package doesn't provide the -devel name without the major +version included. +""" +no-summary-tag=""" +There is no Summary tag in your package. You have to describe your package +using this tag. +""" +summary-on-multiple-lines=""" +Your summary must fit on one line. +""" +summary-not-capitalized=""" +Summary doesn't begin with a capital letter. +""" +summary-ended-with-dot=""" +Summary ends with a dot. +""" +summary-has-leading-spaces=""" +Summary begins with whitespace which will waste space when displayed. +""" +no-description-tag=""" +The description of the package is empty or missing. +""" +tag-in-description=""" +Something that looks like a tag was found in the package's description. +This may indicate a problem where the tag was not actually parsed as a tag +but just textual description content, thus being a no-op. Verify if this is +the case, and move the tag to a place in the specfile where %description +won't fool the specfile parser, and rebuild the package. +""" +no-group-tag=""" +There is no Group tag in your package. You have to specify a valid group +in your spec file using the Group tag. +""" +no-changelogname-tag=""" +There is no %changelog tag in your spec file(or it's empty). To fix it, +please insert a '%changelog' section in your spec file and add a entry change below it. +""" +no-version-in-last-changelog=""" +The latest changelog entry doesn't contain a version. Please insert the +version that is coherent with the version of the package and rebuild it. +""" +incoherent-version-in-changelog=""" +The latest entry in %changelog contains a version identifier that is not +coherent with the epoch:version-release tuple of the package. +""" +changelog-time-overflow=""" +The timestamp of the latest entry in %changelog is suspiciously far away in +the past. +""" +changelog-time-in-future=""" +The timestamp of the latest entry in %changelog is in the future. +""" +no-license=""" +There is no License tag in your spec file. You have to specify one license +for your program (eg. GPL-3.0-only). +""" +obsolete-not-provided=""" +If a package is obsoleted by a compatible replacement, the obsoleted package +should also be provided in order to not cause unnecessary dependency breakage. +If the obsoleting package is not a compatible replacement for the old one, +leave out the Provides. +""" +invalid-dependency=""" +An invalid dependency has been detected. It usually means that the build of +the package was buggy. +""" +no-epoch-tag=""" +There is no Epoch tag in your package. +""" +unreasonable-epoch=""" +The value of your Epoch tag is unreasonably large (> 99). +""" +no-epoch-in-dependency=""" +Your package contains a versioned dependency without an Epoch. +""" +devel-dependency=""" +Your package has a dependency on a devel package but it's not a devel +package itself. +""" +invalid-build-requires=""" +Your source package contains a dependency not compliant with the lib64 +naming. This BuildRequires dependency will not be resolved on lib64 platforms +(eg. amd64). +""" +explicit-lib-dependency=""" +You must let rpm find the library dependencies by itself. Do not put +unneeded explicit Requires: tags. +""" +useless-provides=""" +This package provides multiple times the same capacity. +This means versioned and unversioned symbols are provided at once +thus one overshadowing the other. I.e. 'foo' and 'foo = 1.0'. +""" +requires-on-release=""" +This rpm requires a specific release of another package. +""" +no-url-tag=""" +The URL tag is missing. +""" +name-repeated-in-summary=""" +The name of the package is repeated in its summary. Make the summary +brief and to the point without including redundant information in it. +""" +enchant-dictionary-not-found=""" +A dictionary for the Enchant spell checking library is not available for +the language given in the info message. +""" +self-obsoletion=""" +The package obsoletes itself. This is known to cause errors in various +tools and should thus be avoided, usually by using appropriately versioned +Obsoletes and/or Provides and avoiding unversioned ones. +""" +unexpanded-macro=""" +This tag contains something that looks like an unexpanded macro; this is +often the sign of a misspelling. Please check your specfile. +""" +private-shared-object-provides=""" +A shared object soname provides is provided by a file in a path from which +other packages should not directly load shared objects from. Such shared +objects should thus not be depended on and they should not result in provides +in the containing package. Get rid of the provides if appropriate, for example +by filtering it out during build. Note that in some cases this may require +disabling rpmbuild's internal dependency generator. +""" +devel-package-with-non-devel-group=""" +The package ends with -devel but does not have a RPM group starting with +Development/. +""" +no-pkg-config-provides=""" +The package installs a .pc file but does not provide pkgconfig(..) provides. +The most likely reason for that is that it was built without BuildRequires: pkgconfig. +Please double check your build dependencies.""" +invalid-license-exception=""" +The ' with <x> ' license exception of the License tag was not recognized. +""" +forbidden-controlchar-found=""" +This package contains tags which contain forbidden control characters. +These are all ASCII characters with a decimal value below 32, except TAB(9), +LF(10) and CR(13) +""" +summary-too-long=""" +This package has a summary line greater than 79 characters. Keep the summary below the character limit. +""" +description-line-too-long=""" +This package has a description line of length greater than 79 characters. Break the line into multiple lines to remove the warning. +""" +non-standard-group=""" +This package contains a Group: tag value different from the one defined in ValidGroups inside configdefaults.toml. Make sure both the values match to remove the warning. +""" diff --git a/rpmlint/descriptions/TmpFilesCheck.toml b/rpmlint/descriptions/TmpFilesCheck.toml new file mode 100644 index 0000000..3b7d433 --- /dev/null +++ b/rpmlint/descriptions/TmpFilesCheck.toml @@ -0,0 +1,17 @@ +pre-with-tmpfile-creation=""" +%pre section contains %tmpfiles_create macro that should be in the +%post section instead. +""" +post-without-tmpfile-creation=""" +Please use the %tmpfiles_create macro in %post for each of your +tmpfiles.d files if you expect this file or directory to be +available after package installation (and before reboot). +""" +tmpfile-not-regular-file=""" +Files in tmpfiles.d need to be regular files. +""" +tmpfile-not-in-filelist=""" +Please add the specified file to your %files section as %ghost so +users can easily query who created the file, it gets uninstalled on +package removal and finally other rpmlint checks see it. +""" diff --git a/rpmlint/descriptions/ZipCheck.toml b/rpmlint/descriptions/ZipCheck.toml new file mode 100644 index 0000000..2d5e008 --- /dev/null +++ b/rpmlint/descriptions/ZipCheck.toml @@ -0,0 +1,17 @@ +bad-crc-in-zip = """ +The reported file in the zip fails the CRC check. +Usually this is a sign of a corrupt zip file. +""" +uncompressed-zip = """ +The zip file is not compressed. +""" +class-path-in-manifest = """ +The META-INF/MANIFEST.MF file in the jar contains a hardcoded Class-Path. +These entries do not work with older Java versions and even if they do work, +they are inflexible and usually cause nasty surprises. +""" +jar-not-indexed = """ +The jar file is not indexed, i.e. it does not contain the META-INF/INDEX.LIST +file. Indexed jars speed up the class searching process of classloaders +in some situations. +""" diff --git a/rpmlint/descriptions/ZyppSyntaxCheck.toml b/rpmlint/descriptions/ZyppSyntaxCheck.toml new file mode 100644 index 0000000..147b13b --- /dev/null +++ b/rpmlint/descriptions/ZyppSyntaxCheck.toml @@ -0,0 +1,11 @@ +suse-zypp-packageand=""" +The 'packageand(package1:package2)' syntax is obsolete, please use boolean +dependencies like: +'Supplements: (package1 and package2)' +""" +suse-zypp-otherproviders=""" +The 'otherproviders(symbol)' syntax is obsolete, it is not needed and you +can use the 'symbol' directly: +'Conflicts: symbol' +""" + diff --git a/rpmlint/filter.py b/rpmlint/filter.py new file mode 100644 index 0000000..65b8830 --- /dev/null +++ b/rpmlint/filter.py @@ -0,0 +1,227 @@ +from pathlib import Path +import re +import textwrap + +from rpmlint.color import Color +from rpmlint.helpers import print_warning + +try: + import tomllib +except ImportError: + import tomli as tomllib + + +class Filter: + """ + Handle all printing/formatting/filtering of the rpmlint output. + + Nothing gets printed out until the end of all runs and all errors are + sorted and formatted based on the rules specified by the user/config + """ + + def __init__(self, config): + """ + Initialize options from configuration and load rpmlint descriptions. + + Args: + config: Config object with parsed rpmlint configuration. + """ + # badness stuff + self.badness_threshold = config.configuration['BadnessThreshold'] + self.badness = config.configuration['Scoring'] + self.strict = config.strict + # list of filter regexes + self.filters_regexes = [re.compile(f) for f in config.configuration['Filters']] + self.filter_titles = set(config.configuration['FilterErrorTitles']) + # list of blocked filters + self.blocked_filters = set(config.configuration['BlockedFilters']) + # set of filters that are actually used in add_info + self.used_filters = set() + self.rpmlintrc_filters = config.rpmlintrc_filters + # informative or quiet + self.info = config.info + # How many bad hits we already collected while collecting issues + self.score = 0 + # Dictionary containing mapped values of descriptions for the errors. + self.error_details = {} + # Load it up with the toml descriptions + self.error_details.update(self._load_descriptions()) + # Counter of how many issues we encountered + self.printed_messages = {'I': 0, 'W': 0, 'E': 0} + # Number of promoted warnings and infos to errors + self.promoted_to_error = 0 + # Number of messaged that are filtered out + self.filtered_out = 0 + # Messages + self.results = [] + + @staticmethod + def _load_descriptions(): + """ + Load rpmlint error/warning description texts from toml files. + + Detailed description for every rpmlint error/warning is stored in + descriptions/<check_name>.toml file. + + Returns: + A dictionary mapping error/warning/info names to their + descriptions. + """ + descriptions = {} + descr_folder = Path(__file__).parent / 'descriptions' + try: + for description_file in sorted(descr_folder.glob('*.toml')): + with open(description_file, 'rb') as f: + descriptions.update(tomllib.load(f)) + except tomllib.TOMLDecodeError as terr: + print_warning(f'(none): W: unable to parse description files: {terr}') + return descriptions + + def add_info(self, level, package, rpmlint_issue, *details): + """ + Format rpmlint issue output and add it to self.results. + + It creates formatted and colored output consisting of all information + about rpmlint issue given by the arguments. + + Args: + level: A string with level of the rpmlint issue ('E' - Error, + 'W' - Warning, 'I' - Info + package: Pkg object representing processed package + rpmlint_issue: A string representing the name of the rpmlint + issue + *details: Details of the rpmlint issue + """ + + if ' ' in rpmlint_issue: + raise ValueError(f'Space cannot be part of an issue name: "{rpmlint_issue}"') + + # filename in some cases can contain tmp paths and we don't need it + # for the printout + filename = Path(package.name).name + # we can get badness treshold + badness = None + if rpmlint_issue in self.badness: + badness = int(self.badness[rpmlint_issue]) + # If we have any badness configured then we 'stricten' and call the + # result Error. Otherwise we downgrade the error to Warn. + if badness > 0: + level = 'E' + elif level == 'E': + level = 'W' + # allow strict reporting where we override levels and treat everything + # as an error + if self.strict: + if level != 'E': + self.promoted_to_error += 1 + level = 'E' + + if badness is None: + badness = 1 if level == 'E' else 0 + # set coloring + if level == 'E': + lvl_color = Color.Red + elif level == 'W': + lvl_color = Color.Yellow + else: + lvl_color = Color.Bold + # compile the message + line = f'{package.current_linenum}:' if package.current_linenum else '' + arch = f'.{package.arch}' if package.arch else '' + bad_output = f' (Badness: {badness})' if badness > 1 else '' + detail_output = '' + for detail in details: + if detail: + detail_output += f' {detail}' + result = f'{Color.Bold}{filename}{arch}:{line}{Color.Reset} {lvl_color}{level}: {rpmlint_issue}{Color.Reset}{bad_output}{detail_output}' + + # filter by the result message + result_no_color = f'{filename}{arch}:{line} {level}: {rpmlint_issue}{detail_output}' + # unused-rpmlintrc-filter warnings should be skipped + if rpmlint_issue != 'unused-rpmlintrc-filter' and rpmlint_issue not in self.blocked_filters: + if rpmlint_issue in self.filter_titles: + self.filtered_out += 1 + return + for f in self.filters_regexes: + if f.search(result_no_color): + self.used_filters.add(f.pattern) + self.filtered_out += 1 + return + + # raise the counters + self.score += badness + self.printed_messages[level] += 1 + + self.results.append(result) + + def print_results(self, results, config=None): + """ + Provide all the information about the specified package. + + If there is description to be provided it needs to be provided only + once per rpmlint_issue. + + Args: + results: A list with rpmlint messages. + config: parsed configuration file that is used as a source for + new description strings + + Returns: + A string with final rpmlint output. + """ + output = '' + results.sort(key=self.__diag_sortkey, reverse=True) + last_issue = '' + for diag in results: + if self.info: + rpmlint_issue = diag.split()[2].rstrip(Color.Reset) + # print out details for each rpmlint_issue we had + if rpmlint_issue != last_issue: + if last_issue: + output += self.get_description(last_issue, config) + last_issue = rpmlint_issue + output += diag + '\n' + if self.info and last_issue: + output += self.get_description(last_issue, config) + # normalize the output as rpm 4.15 uses surrogates + output = output.encode('utf-8', errors='surrogateescape').decode('utf-8', errors='replace') + + return output + + def get_description(self, rpmlint_issue, config=None): + """ + Get description for specified rpmlint issue (error, warning or info). + + Args: + rpmlint_issue: A string with the rpmlint error/warning/info name + config: parsed configuration file that is used as a source for + custom description strings ([Descriptions] table in toml + syntax) + + Returns: + A string with description for specified rpmlint issue. Empty + content does not cause an issue and we just return empty content + """ + description = '' + if rpmlint_issue in self.error_details: + + # Update rpmlint error descriptions from configuration file + if config and config.configuration.get('Descriptions').get(rpmlint_issue): + self.error_details[rpmlint_issue] = config.configuration['Descriptions'][rpmlint_issue] + + # we need 2 enters at the end for whitespace purposes + description = textwrap.fill(self.error_details[rpmlint_issue], 78, break_on_hyphens=False) + '\n\n' + return description + + def __diag_sortkey(self, x): + """ + Sorting helper, xs[1] is packagename line architecture + xs[2] is the reason of the error + """ + xs = x.split() + return (xs[2], xs[1]) + + def validate_filters(self, pkg): + for f in self.rpmlintrc_filters: + if f not in self.used_filters: + self.add_info('E', pkg, 'unused-rpmlintrc-filter', f'"{f}"') diff --git a/rpmlint/helpers.py b/rpmlint/helpers.py new file mode 100644 index 0000000..e9a1d21 --- /dev/null +++ b/rpmlint/helpers.py @@ -0,0 +1,55 @@ +# File containing various helper functions used across rpmlint + +import os +from shutil import get_terminal_size +import sys + +from rpmlint.color import Color + + +ENGLISH_ENVIROMENT = dict(os.environ, LC_ALL='en_US.UTF-8', + LANGUAGE='en_US') + + +def string_center(message, filler=' '): + """ + Create string centered of the terminal + """ + cols, rows = get_terminal_size() + return (f' {message} ').center(cols, filler) + + +def print_centered(message, filler=' '): + """ + Print message in the center of a terminal + """ + print(string_center(message, filler)) + + +def print_warning(message): + """ + Print warning message to stderr. + """ + print(f'{Color.Red}{message}{Color.Reset}', file=sys.stderr) + + +def byte_to_string(item): + """ + Convert byte items to strings + """ + # empty stuff or already existing string stays + if item is None or isinstance(item, str): + return item + + # if we have a list/tuple we have to recurse + if isinstance(item, (list, tuple)): + return [byte_to_string(i) for i in item] + + # everything else shall be decoded and fails replaced + return item.decode(encoding='UTF-8', errors='replace') + + +def readlines(path): + with open(path, 'rb') as fobj: + for line in fobj: + yield byte_to_string(line) diff --git a/rpmlint/lddparser.py b/rpmlint/lddparser.py new file mode 100644 index 0000000..04974c2 --- /dev/null +++ b/rpmlint/lddparser.py @@ -0,0 +1,92 @@ +import re +import subprocess + +from rpmlint.helpers import ENGLISH_ENVIROMENT + + +class LddParser: + """ + Class contains all information obtained by ldd command + about undefined symbols and unused direct dependencies. + + Parse these 2 outputs: + + $ ldd -u libnss-unused-dependency.so + Unused direct dependencies: + /lib/libnss_files.so.2 + + $ ldd -r libthread-undefined-symbol.so + linux-gate.so.1 (0xf7fce000) + libc.so.6 => /lib/libc.so.6 (0xf7d9a000) + /lib/ld-linux.so.2 (0xf7fcf000) + undefined symbol: ps_pdwrite (./libthread-undefined-symbol.so) + undefined symbol: ps_pglobal_lookup (./libthread-undefined-symbol.so) + undefined symbol: ps_lsetregs (./libthread-undefined-symbol.so) + undefined symbol: ps_getpid (./libthread-undefined-symbol.so) + undefined symbol: ps_lgetfpregs (./libthread-undefined-symbol.so) + undefined symbol: ps_lsetfpregs (./libthread-undefined-symbol.so) + undefined symbol: ps_lgetregs (./libthread-undefined-symbol.so) + undefined symbol: ps_pdread (./libthread-undefined-symbol.so) + undefined symbol: gss_release_cred, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) + undefined symbol: gss_canonicalize_name, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) + undefined symbol: gss_pname_to_uid, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) + undefined symbol: gss_accept_sec_context, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) + undefined symbol: gss_verify_mic, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) + undefined symbol: gss_get_mic, version gssapi_krb5_2_MIT (./test/ldd/libtirpc.so.3.0.0) + """ + + unused_regex = re.compile(r'^\s+(?P<lib>\S+)') + undef_regex = re.compile(r'^undefined symbol:\s+(?P<symbol>[^, ]+)') + + def __init__(self, pkgfile_path, path, is_installed_pkg): + self.pkgfile_path = pkgfile_path + self.dependencies = [] + self.unused_dependencies = [] + self.undefined_symbols = [] + self.parsing_failed_reason = None + if is_installed_pkg: + self.parse_dependencies() + self.parse_undefined_symbols() + + def parse_dependencies(self): + r = subprocess.run(['ldd', '-u', self.pkgfile_path], encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + if r.returncode == 0: + return + + lines = r.stdout.splitlines() + is_unused = False + for line in lines: + if line.startswith('Unused direct dependencies:'): + is_unused = True + elif is_unused: + unused = self.unused_regex.search(line) + if unused: + self.unused_dependencies.append(unused.group('lib')) + else: + is_unused = False + + def parse_undefined_symbols(self): + r = subprocess.run(['ldd', '-r', self.pkgfile_path], encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + # here ldd should always return 0 + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + return + + lines = r.stdout.splitlines() + for line in lines: + r = self.undef_regex.search(line) + if r: + self.undefined_symbols.append(r.group('symbol')) + else: + self.dependencies.append(line.strip()) + + # run c++filt demangler for all collected symbols + if self.undefined_symbols: + r = subprocess.run(['c++filt'] + self.undefined_symbols, encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + else: + self.undefined_symbols = r.stdout.splitlines() diff --git a/rpmlint/lint.py b/rpmlint/lint.py new file mode 100644 index 0000000..5372e74 --- /dev/null +++ b/rpmlint/lint.py @@ -0,0 +1,339 @@ +from collections import defaultdict +import cProfile +import importlib +import operator +from pstats import Stats +import sys +from tempfile import gettempdir +import time + +from rpmlint.color import Color +from rpmlint.config import Config +from rpmlint.filter import Filter +from rpmlint.helpers import print_warning, string_center +from rpmlint.pkg import FakePkg, get_installed_pkgs, Pkg +from rpmlint.version import __version__ + + +class Lint: + """ + Generic object handling the basic rpmlint operations + """ + + def __init__(self, options): + # initialize configuration + self.checks = {} + self.options = options + self.packages_checked = 0 + self.specfiles_checked = 0 + self.check_duration = defaultdict(int) + if options['config']: + self.config = Config(options['config']) + else: + self.config = Config() + if options['profile']: + self.profile = cProfile.Profile() + self.profile.enable() + else: + self.profile = None + + if options['rpmlintrc']: + options['rpmlintrc'] = [options['rpmlintrc']] + self._load_rpmlintrc() + if options['verbose']: + self.config.info = options['verbose'] + if options['strict']: + self.config.strict = options['strict'] + if options['permissive']: + self.config.permissive = options['permissive'] + if not self.config.configuration['ExtractDir']: + self.config.configuration['ExtractDir'] = gettempdir() + # initialize output buffer + self.output = Filter(self.config) + # preload the check list if we not print config + # some of the config values are transformed e.g. to regular + # expressions + if not self.options['print_config']: + self.load_checks() + + def _run(self): + start = time.monotonic() + retcode = 0 + # if we just want to print config, do so and leave + if self.options['print_config']: + self.print_config() + return retcode + # just explain the error and abort too + if self.options['explain']: + self.print_explanation(self.options['explain'], self.config) + return retcode + # if there are installed arguments just load them up as extra + # items to the rpmfile option + if self.options['installed']: + self.validate_installed_packages(self._load_installed_rpms(self.options['installed'])) + # if no exclusive option is passed then just loop over all the + # arguments that are supposed to be either rpm or spec files + self.validate_files(self.options['rpmfile']) + self._print_header() + print(self.output.print_results(self.output.results, self.config), + end='') + quit_color = Color.Bold + if self.output.printed_messages['W'] > 0: + quit_color = Color.Yellow + if self.output.badness_threshold > 0 and self.output.score > self.output.badness_threshold: + msg = string_center(f'Badness {self.output.score} exceeds threshold {self.output.badness_threshold}, aborting.', '-') + print(f'{Color.Red}{msg}{Color.Reset}') + quit_color = Color.Red + retcode = 66 + elif self.output.printed_messages['E'] > 0 and not self.config.permissive: + quit_color = Color.Red + all_promoted = self.output.printed_messages['E'] == self.output.promoted_to_error + retcode = 65 if all_promoted else 64 + + self._maybe_print_reports() + + duration = time.monotonic() - start + error_messages = self.output.printed_messages['E'] + warning_messages = self.output.printed_messages['W'] + msg = string_center(f'{self.packages_checked} packages and {self.specfiles_checked} specfiles checked; ' + f'{error_messages} errors, {warning_messages} warnings' + f', {self.output.filtered_out} filtered, ' + f'{self.output.score} badness; has taken {duration:.1f} s', '=') + print(f'{quit_color}{msg}{Color.Reset}') + + return retcode + + def run(self): + try: + return self._run() + except KeyboardInterrupt as e: + self._maybe_print_reports() + raise e + + def _maybe_print_reports(self): + if self.options['time_report']: + self._print_time_report() + if self.profile: + self._print_cprofile() + + def _get_color_time_report_value(self, fraction): + if fraction > 25: + color = Color.Red + elif fraction > 5: + color = Color.Yellow + else: + color = '' + return f'{color}{fraction:17.1f}{Color.Reset}' + + def _print_time_report(self): + PERCENT_THRESHOLD = 1 + TIME_THRESHOLD = 0.1 + total = sum(self.check_duration.values()) + checked_files = [check.checked_files for check in self.checks.values() if check.checked_files] + total_checked_files = max(checked_files) if checked_files else '' + print(f'{Color.Bold}Check time report{Color.Reset} (>{PERCENT_THRESHOLD}% & >{TIME_THRESHOLD}s):') + + check = format('Check', '32s') + duration = format('Duration (in s)', '>12') + fraction = format('Fraction (in %)', '>17') + print(f'{Color.Bold} {check} {duration} {fraction} Checked files{Color.Reset}') + + for check, duration in sorted(self.check_duration.items(), key=operator.itemgetter(1), reverse=True): + fraction = 100.0 * duration / total + if fraction < PERCENT_THRESHOLD or duration < TIME_THRESHOLD: + continue + + checked_files = '' + if check in self.checks: + checked = self.checks[check].checked_files + if checked: + checked_files = checked + print(f' {check:32s} {duration:15.1f} {self._get_color_time_report_value(fraction)} {checked_files:>14}') + + print(f' {"TOTAL":32s} {total:15.1f} {100:17.1f} {total_checked_files:>14}\n') # noqa Q000 + + def _print_cprofile(self): + N = 30 + print(f'{Color.Bold}cProfile report:{Color.Reset}') + self.profile.disable() + stats = Stats(self.profile) + stats.sort_stats('cumulative').print_stats(N) + print('========================================================') + stats.sort_stats('ncalls').print_stats(N) + print('========================================================') + stats.sort_stats('tottime').print_stats(N) + + def _load_installed_rpms(self, packages): + existing_packages = [] + for name in packages: + pkg = get_installed_pkgs(name) + if pkg: + existing_packages.extend(pkg) + else: + print_warning(f'(none): E: there is no installed rpm "{name}".') + return existing_packages + + def _load_rpmlintrc(self): + """ + Load rpmlintrc from argument or load up from folder + """ + if self.options['rpmlintrc']: + # Right now, we allow loading of just a single file, but the 'opensuse' + # branch contains auto-loading mechanism that can eventually load + # multiple files. + for rcfile in self.options['rpmlintrc']: + self.config.load_rpmlintrc(rcfile) + else: + # load only from the same folder specname.rpmlintrc or specname-rpmlintrc + # do this only in a case where there is one folder parameter or one file + # to avoid multiple folders handling + rpmlintrc = [] + if len(self.options['rpmfile']) != 1: + return + pkg = self.options['rpmfile'][0] + if pkg.is_file(): + pkg = pkg.parent + rpmlintrc += sorted(pkg.glob('*.rpmlintrc')) + rpmlintrc += sorted(pkg.glob('*-rpmlintrc')) + if len(rpmlintrc) > 1: + # multiple rpmlintrcs are highly undesirable + print_warning('There are multiple items to be loaded for rpmlintrc, ignoring them: {}.'.format(' '.join(map(str, rpmlintrc)))) + elif len(rpmlintrc) == 1: + self.options['rpmlintrc'] = rpmlintrc[0] + self.config.load_rpmlintrc(rpmlintrc[0]) + + def _print_header(self): + """ + Print out header information about the state of the + rpmlint prior printing out the check report. + """ + intro = string_center('rpmlint session starts', '=') + print(f'{Color.Bold}{intro}{Color.Reset}') + print(f'rpmlint: {__version__}') + print('configuration:') + for config in self.config.conf_files: + print(f' {config}') + if self.options['rpmlintrc']: + rpmlintrc = self.options['rpmlintrc'] + print(f'rpmlintrc: {rpmlintrc}') + no_checks = len(self.config.configuration['Checks']) + no_pkgs = len(self.options['installed']) + len(self.options['rpmfile']) + print(f'{Color.Bold}checks: {no_checks}, packages: {no_pkgs}{Color.Reset}') + print('') + + def validate_installed_packages(self, packages): + for pkg in packages: + self.run_checks(pkg, pkg == packages[-1]) + + def validate_files(self, files): + """ + Run all the check for passed file list + """ + if not files: + if self.packages_checked == 0: + # print warning only if we didn't process even installed files + print_warning('There are no files to process nor additional arguments.') + print_warning('Nothing to do, aborting.') + return + # check all elements if they are a folder or a file with proper suffix + # and expand everything + packages = self._expand_filelist(files) + + # Sort the files so that the output is stable + packages = sorted(packages) + for pkg in packages: + self.validate_file(pkg, pkg == packages[-1]) + + def _expand_filelist(self, files): + packages = [] + for pkg in files: + if pkg.is_file() and pkg.suffix in ('.rpm', '.spm', '.spec'): + packages.append(pkg) + elif pkg.is_dir(): + packages.extend(self._expand_filelist(pkg.iterdir())) + return packages + + def validate_file(self, pname, is_last): + try: + if pname.suffix == '.rpm' or pname.suffix == '.spm': + with Pkg(pname, self.config.configuration['ExtractDir'], + verbose=self.config.info) as pkg: + for k, v in pkg.timers.items(): + self.check_duration[k] += v + self.run_checks(pkg, is_last) + elif pname.suffix == '.spec': + with FakePkg(pname) as pkg: + self.run_checks(pkg, is_last) + except Exception as e: + print_warning(f'(none): E: fatal error while reading {pname}: {e}') + if self.config.info: + raise e + else: + sys.exit(3) + + def run_checks(self, pkg, is_last): + spec_checks = isinstance(pkg, FakePkg) + for checker in self.checks: + start = time.monotonic() + fn = self.checks[checker].check_spec if spec_checks else self.checks[checker].check + fn(pkg) + self.check_duration[checker] += time.monotonic() - start + + # run post check function and validate used filters in rpmlintrc + if is_last: + for checker in self.checks.values(): + checker.after_checks() + + if not self.options['ignore_unused_rpmlintrc']: + self.output.validate_filters(pkg) + + if spec_checks: + self.specfiles_checked += 1 + else: + self.packages_checked += 1 + + def print_config(self): + """ + Just output the current configuration + """ + self.config.print_config() + + def print_explanation(self, messages, config): + """ + Print out detailed explanation for the specified messages + """ + for message in messages: + explanation = self.output.get_description(message, config) + if not explanation: + # check if it's a WarnOnFunction warning configuration + forbidden_functions = config.configuration['WarnOnFunction'] + if message in forbidden_functions: + explanation = forbidden_functions[message].get('description') + + if not explanation: + explanation = 'Unknown message, please report a bug if the description should be present.\n\n' + + print(f'{message}:\n{explanation}') + + def load_checks(self): + """ + Load all checks based on the config, skipping those already loaded + SingletonTM + """ + + selected_checks = self.options['checks'] + if selected_checks: + selected_checks = selected_checks.split(',') + + for check in self.config.configuration['Checks']: + if check in self.checks: + continue + if not selected_checks or check in selected_checks: + self.checks[check] = self.load_check(check) + + def load_check(self, name): + """Load a (check) module by its name, unless it is already loaded.""" + module = importlib.import_module(f'.{name}', package='rpmlint.checks') + klass = getattr(module, name) + obj = klass(self.config, self.output) + return obj diff --git a/rpmlint/objdumpparser.py b/rpmlint/objdumpparser.py new file mode 100644 index 0000000..028f0a0 --- /dev/null +++ b/rpmlint/objdumpparser.py @@ -0,0 +1,53 @@ +import subprocess + +from rpmlint.helpers import ENGLISH_ENVIROMENT + + +class ObjdumpParser: + """ + Class contains all information obtained by objdump command. Right now, we + are interested in DW_TAG_compile_unit of debug info. + + Example output of objdump: + + <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit) + <c> DW_AT_stmt_list : 0x0 + <10> DW_AT_low_pc : 0x927840 + <18> DW_AT_high_pc : 0x92786b + <20> DW_AT_name : (indirect string, offset: 0x0): ../sysdeps/x86_64/start.S + <24> DW_AT_comp_dir : (indirect string, offset: 0x1a): /home/abuild/rpmbuild/BUILD/glibc-2.31/csu + <28> DW_AT_producer : (indirect string, offset: 0x45): GNU AS 2.33.1 + <2c> DW_AT_language : 32769 (MIPS assembler) + Compilation Unit @ offset 0x2e: + Length: 0x3c (32-bit) + """ + + dw_at_prefix = 'DW_AT_' + + def __init__(self, pkgfile_path, path): + self.pkgfile_path = pkgfile_path + self.compile_units = [] + self.parsing_failed_reason = None + self.parse_dwarf_compilation_units() + + def parse_dwarf_compilation_units(self): + r = subprocess.run(['objdump', '--dwarf=info', '--dwarf-depth=1', self.pkgfile_path], encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + # here ldd should always return 0 + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + return + + lines = r.stdout.splitlines() + for i, line in enumerate(lines): + if 'DW_TAG_compile_unit' in line: + # We parse all DW_at_ flags that follow the DW_TAG_compile_unit + i += 1 + cu_data = {} + while self.dw_at_prefix in lines[i]: + current_line = lines[i] + current_line = current_line[current_line.find(self.dw_at_prefix) + len(self.dw_at_prefix):] + parts = [t.strip() for t in current_line.split(':')] + cu_data[parts[0]] = parts[-1] + i += 1 + self.compile_units.append(cu_data) diff --git a/rpmlint/pkg.py b/rpmlint/pkg.py new file mode 100644 index 0000000..948c757 --- /dev/null +++ b/rpmlint/pkg.py @@ -0,0 +1,952 @@ +import bz2 +from collections import namedtuple +import contextlib +import gzip +import hashlib +import io +import lzma +import mmap +import os +from pathlib import Path, PurePath +import re +from shlex import quote +import shutil +import stat +import subprocess +import tempfile +import time +from urllib.parse import urljoin + +try: + import magic + has_magic = True +except ImportError: + has_magic = False +import rpm +from rpmlint.helpers import byte_to_string, ENGLISH_ENVIROMENT, print_warning +from rpmlint.pkgfile import PkgFile +import zstandard as zstd + + +DepInfo = namedtuple('DepInfo', ('name', 'flags', 'version')) + +# 64: RPMSENSE_PREREQ is 0 with rpm 4.4..4.7, we want 64 here in order +# to do the right thing with those versions and packages built with other +# rpm versions +PREREQ_FLAG = (rpm.RPMSENSE_PREREQ or 64) | rpm.RPMSENSE_SCRIPT_PRE | \ + rpm.RPMSENSE_SCRIPT_POST | 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'), + # file triggers: rpm >= 4.12.90 + (getattr(rpm, 'RPMTAG_FILETRIGGERSCRIPTS', 5066), + getattr(rpm, 'RPMTAG_FILETRIGGERSCRIPTPROG', 5067), + '%filetrigger'), + (getattr(rpm, 'RPMTAG_TRANSFILETRIGGERSCRIPTS', 5076), + getattr(rpm, 'RPMTAG_TRANSFILETRIGGERSCRIPTPROG', 5077), + '%transfiletrigger'), +] + +RPM_SCRIPTLETS = ('pre', 'post', 'preun', 'postun', 'pretrans', 'posttrans', + 'trigger', 'triggerin', 'triggerprein', 'triggerun', + 'triggerpostun', 'verifyscript', 'filetriggerin', + 'filetrigger', 'filetriggerun', 'filetriggerpostun', + 'transfiletriggerin', 'transfiletrigger', + 'transfiletriggerun', 'transfiletriggerun', + 'transfiletriggerpostun') + + +gzip_regex = re.compile(r'\.t?gz?$') +bz2_regex = re.compile(r'\.t?bz2?$') +xz_regex = re.compile(r'\.(t[xl]z|xz|lzma)$') +zst_regex = re.compile(r'\.zst$') + + +def catcmd(fname): + """Get a 'cat' command that handles possibly compressed files.""" + fname = str(fname) + cat = 'gzip -dcf' + if bz2_regex.search(fname): + cat = 'bzip2 -dcf' + elif xz_regex.search(fname): + cat = 'xz -dc' + elif zst_regex.search(fname): + cat = 'zstd -dc' + return cat + + +def compression_algorithm(fname): + """Return compression algorithm based on filename if known, None otherwise.""" + fname = str(fname) + if gzip_regex.search(fname): + return gzip + elif bz2_regex.search(fname): + return bz2 + elif xz_regex.search(fname): + return lzma + elif zst_regex.search(fname): + return zstd + else: + return None + + +def is_utf8(fname): + compression = compression_algorithm(fname) + if compression is None: + with open(fname, 'rb') as f: + return is_utf8_bytestr(f.read()) + + with compression.open(fname, 'rb') as f: + try: + return is_utf8_bytestr(f.read()) + except OSError: + return True + + +def is_utf8_bytestr(s): + """Returns True whether the given text is UTF-8. + Due to changes in rpm, needs to handle both bytes and unicode.""" + try: + if hasattr(s, 'decode'): + s.decode('utf-8') + elif hasattr(s, 'encode'): + s.encode('utf-8') + else: + unexpected = type(s).__name__ + raise TypeError( + f'Expected str/unicode/bytes, not {unexpected}') + except UnicodeError: + return False + return True + + +def has_forbidden_controlchars(val): + if isinstance(val, (str, bytes)): + string = val + if isinstance(val, bytes): + val = memoryview(val) + for c in val: + if isinstance(c, str): + c = ord(c) + if c < 32 and (c not in (9, 10, 13)): + return string + if isinstance(val, (tuple, list)): + for item in val: + return has_forbidden_controlchars(item) + return False + + +# from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications +def compareEVR(evr1, evr2): + (e1, v1, r1) = evr1 + (e2, v2, r2) = evr2 + # return 1: a is newer than b + # 0: a and b are the same version + # -1: b is newer than a + # rpmlint mod: don't stringify None epochs to 'None' strings + if e1 is not None: + e1 = str(e1) + v1 = str(v1) + r1 = str(r1) + if e2 is not None: + e2 = str(e2) + v2 = str(v2) + r2 = str(r2) + rc = rpm.labelCompare((e1, v1, r1), (e2, v2, r2)) + return rc + + +# from yum 3.2.27, rpmUtils.miscutils, with rpmlint modifications +def rangeCompare(reqtuple, provtuple): + """returns true if provtuple satisfies reqtuple""" + (reqn, reqf, (reqe, reqv, reqr)) = reqtuple + (n, f, (e, v, r)) = provtuple + if reqn != n: + return 0 + + # unversioned satisfies everything + if not f or not reqf: + return 1 + + # and you thought we were done having fun + # if the requested release is left out then we have + # to remove release from the package prco to make sure the match + # is a success - ie: if the request is EQ foo 1:3.0.0 and we have + # foo 1:3.0.0-15 then we have to drop the 15 so we can match + if reqr is None: + r = None + # rpmlint mod: don't mess with provided Epoch, doing so breaks e.g. + # 'Requires: foo < 1.0' should not be satisfied by 'Provides: foo = 1:0.5' + # if reqe is None: + # e = None + if reqv is None: # just for the record if ver is None then we're going to segfault + v = None + + # if we just require foo-version, then foo-version-* will match + if r is None: + reqr = None + + rc = compareEVR((e, v, r), (reqe, reqv, reqr)) + + # does not match unless + if rc >= 1: + if reqf in ['GT', 'GE', 4, 12]: + return 1 + if reqf in ['EQ', 8] and f in ['LE', 10, 'LT', 2]: + return 1 + if reqf in ['LE', 'LT', 'EQ', 10, 2, 8] and f in ['LE', 'LT', 10, 2]: + return 1 + + if rc == 0: + if reqf in ['GT', 4] and f in ['GT', 'GE', 4, 12]: + return 1 + if reqf in ['GE', 12] and f in ['GT', 'GE', 'EQ', 'LE', 4, 12, 8, 10]: + return 1 + if reqf in ['EQ', 8] and f in ['EQ', 'GE', 'LE', 8, 12, 10]: + return 1 + if reqf in ['LE', 10] and f in ['EQ', 'LE', 'LT', 'GE', 8, 10, 2, 12]: + return 1 + if reqf in ['LT', 2] and f in ['LE', 'LT', 10, 2]: + return 1 + if rc <= -1: + if reqf in ['GT', 'GE', 'EQ', 4, 12, 8] and f in ['GT', 'GE', 4, 12]: + return 1 + if reqf in ['LE', 'LT', 10, 2]: + return 1 +# if rc >= 1: +# if reqf in ['GT', 'GE', 4, 12]: +# return 1 +# if rc == 0: +# if reqf in ['GE', 'LE', 'EQ', 8, 10, 12]: +# return 1 +# if rc <= -1: +# if reqf in ['LT', 'LE', 2, 10]: +# return 1 + + return 0 + + +# from yum 3.2.23, rpmUtils.miscutils, with rpmlint modifications +def formatRequire(name, flags, evr): + s = name + + if flags and flags & (rpm.RPMSENSE_LESS | rpm.RPMSENSE_GREATER | + rpm.RPMSENSE_EQUAL): + s = s + ' ' + if flags & rpm.RPMSENSE_LESS: + s = s + '<' + if flags & rpm.RPMSENSE_GREATER: + s = s + '>' + if flags & rpm.RPMSENSE_EQUAL: + s = s + '=' + s = f'{s} {versionToString(evr)}' + return s + + +def versionToString(evr): + if not isinstance(evr, (list, tuple)): + # assume string + return evr + ret = '' + if evr[0] is not None and evr[0] != '': + ret += str(evr[0]) + ':' + if evr[1] is not None: + ret += evr[1] + if evr[2] is not None and evr[2] != '': + ret += '-' + evr[2] + return ret + + +# from yum 3.2.23, rpmUtils.miscutils, with some rpmlint modifications +def stringToVersion(verstring): + if verstring in (None, ''): + return (None, None, None) + epoch = None + i = verstring.find(':') + if i != -1: + with contextlib.suppress(ValueError): + # garbage in epoch, ignore it + epoch = int(verstring[:i]) + i += 1 + j = verstring.find('-', i) + if j != -1: + if verstring[i:j] == '': + version = None + else: + version = verstring[i:j] + release = verstring[j + 1:] + else: + if verstring[i:] == '': + version = None + else: + version = verstring[i:] + release = None + return (epoch, version, release) + + +def parse_deps(line): + """ + Parse provides/requires/conflicts/obsoletes line to list of + (name, flags, (epoch, version, release)) tuples. + """ + + prcos = [] + tokens = re.split(r'[\s,]+', line.strip()) + + # Drop line continuation backslash in multiline macro definition (for + # spec file parsing), e.g. + # [...] \ + # Obsoletes: foo-%1 <= 1.0.0 \ + # [...] \ + # (yes, this is an ugly hack and we probably have other problems with + # multiline macro definitions elsewhere...) + if tokens[-1] == '\\': + del tokens[-1] + + prco = [] + while tokens: + token = tokens.pop(0) + if not token: + # skip empty tokens + continue + + plen = len(prco) + + if plen == 0: + prco.append(token) + + elif plen == 1: + flags = 0 + if token[0] in ('=', '<', '<=', '>', '>='): + # versioned, flags + if '=' in token: + flags |= rpm.RPMSENSE_EQUAL + if '<' in token: + flags |= rpm.RPMSENSE_LESS + if '>' in token: + flags |= rpm.RPMSENSE_GREATER + prco.append(flags) + else: + # no flags following name, treat as unversioned, add and reset + prco.extend((flags, (None, None, None))) + prcos.append(tuple(prco)) + prco = [token] + + elif plen == 2: + # last token of versioned one, add and reset + prco.append(stringToVersion(token)) + prcos.append(tuple(prco)) + prco = [] + + plen = len(prco) + if plen: + if plen == 1: + prco.extend((0, (None, None, None))) + elif plen == 2: + prco.append((None, None, None)) + prcos.append(tuple(prco)) + + return prcos + + +def _get_magic_libmagic(path): + return magic.detect_from_filename(path).name + + +def _get_magic_python_magic(path): + return magic.from_file(path) + + +def get_magic(path): + # python-magic & libmagic compatibility code + # https://github.com/ahupp/python-magic/blob/master/COMPAT.md + detect_magic = _get_magic_python_magic + if not hasattr(magic, 'from_file'): + # libmagic python bindings + detect_magic = _get_magic_libmagic + + try: + return detect_magic(path) + except (ValueError, FileNotFoundError): + return '' + + +# classes representing package + +class AbstractPkg: + def cleanup(self): + pass + + # internal function to gather dependency info used by the above ones + def _gather_aux(self, header, xs, nametag, flagstag, versiontag, + prereq=None): + names = header[nametag] + flags = header[flagstag] + versions = header[versiontag] + + if versions: + for loop in range(len(versions)): + name = byte_to_string(names[loop]) + evr = stringToVersion(byte_to_string(versions[loop])) + if prereq is not None and flags[loop] & PREREQ_FLAG: + prereq.append((name, flags[loop] & (~PREREQ_FLAG), evr)) + else: + xs.append(DepInfo(name, flags[loop], evr)) + return xs, prereq + + def _gather_dep_info(self): + _requires = [] + _prereq = [] + _provides = [] + _conflicts = [] + _obsoletes = [] + _recommends = [] + _suggests = [] + _enhances = [] + _supplements = [] + + _requires, _prereq = self._gather_aux(self.header, _requires, + rpm.RPMTAG_REQUIRENAME, + rpm.RPMTAG_REQUIREFLAGS, + rpm.RPMTAG_REQUIREVERSION, + _prereq) + _conflits, _ = self._gather_aux(self.header, _conflicts, + rpm.RPMTAG_CONFLICTNAME, + rpm.RPMTAG_CONFLICTFLAGS, + rpm.RPMTAG_CONFLICTVERSION) + _provides, _ = self._gather_aux(self.header, _provides, + rpm.RPMTAG_PROVIDENAME, + rpm.RPMTAG_PROVIDEFLAGS, + rpm.RPMTAG_PROVIDEVERSION) + _obsoletes, _ = self._gather_aux(self.header, _obsoletes, + rpm.RPMTAG_OBSOLETENAME, + rpm.RPMTAG_OBSOLETEFLAGS, + rpm.RPMTAG_OBSOLETEVERSION) + _recommends, _ = self._gather_aux(self.header, _recommends, + rpm.RPMTAG_RECOMMENDNAME, + rpm.RPMTAG_RECOMMENDFLAGS, + rpm.RPMTAG_RECOMMENDVERSION) + _suggests, _ = self._gather_aux(self.header, _suggests, + rpm.RPMTAG_SUGGESTNAME, + rpm.RPMTAG_SUGGESTFLAGS, + rpm.RPMTAG_SUGGESTVERSION) + _enhances, _ = self._gather_aux(self.header, _enhances, + rpm.RPMTAG_ENHANCENAME, + rpm.RPMTAG_ENHANCEFLAGS, + rpm.RPMTAG_ENHANCEVERSION) + _supplements, _ = self._gather_aux(self.header, _supplements, + rpm.RPMTAG_SUPPLEMENTNAME, + rpm.RPMTAG_SUPPLEMENTFLAGS, + rpm.RPMTAG_SUPPLEMENTVERSION) + + return (_requires, _prereq, _provides, _conflicts, _obsoletes, _recommends, + _suggests, _enhances, _supplements) + + def scriptprog(self, which): + """ + Get the specified script interpreter as a string. + Depending on rpm-python version, the string may or may not include + interpreter arguments, if any. + """ + if which is None: + return '' + prog = self[which] + if prog is None: + prog = '' + elif isinstance(prog, (list, tuple)): + # http://rpm.org/ticket/847#comment:2 + prog = ''.join(prog) + return prog + + 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(r'\([^)]+\s+compressed\s+data\b') + + def __init__(self, filename, dirname, header=None, is_source=False, extracted=False, verbose=False): + self.filename = filename + self.extracted = extracted + + # record decompression and extraction time + start = time.monotonic() + self.dirname = self._extract_rpm(dirname, verbose) + self.timers = {'rpm2cpio': time.monotonic() - start, 'libmagic': 0} + self.current_linenum = None + + self._req_names = -1 + + if header: + self.header = header + self.is_source = is_source + else: + # Create a package object from the file name + ts = rpm.TransactionSet() + # Don't check signatures here... + ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES) + fd = os.open(filename, os.O_RDONLY) + try: + self.header = ts.hdrFromFdno(fd) + finally: + os.close(fd) + self.is_source = not self.header[rpm.RPMTAG_SOURCERPM] + + self.name = self[rpm.RPMTAG_NAME] + + (self.requires, self.prereq, self.provides, self.conflicts, + self.obsoletes, self.recommends, self.suggests, self.enhances, + self.supplements) = self._gather_dep_info() + + self.req_names = [x[0] for x in self.requires + self.prereq] + + self.files = self._gather_files_info() + self.config_files = [x.name for x in self.files.values() if x.is_config] + self.doc_files = [x.name for x in self.files.values() if x.is_doc] + self.ghost_files = [x.name for x in self.files.values() if x.is_ghost] + self.noreplace_files = [x.name for x in self.files.values() if x.is_noreplace] + self.missingok_files = [x.name for x in self.files.values() if x.is_missingok] + + if self.is_no_source: + self.arch = 'nosrc' + elif self.is_source: + self.arch = 'src' + else: + self.arch = self.header.format('%{ARCH}') + + # Return true if the package is a nosource package. + # NoSource files are ghosts in source packages. + @property + def is_no_source(self): + return self.is_source and self.ghost_files + + # access the tags like an array + def __getitem__(self, key): + try: + val = self.header[key] + except KeyError: + val = [] + if val == []: + return None + else: + # 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, + rpm.RPMTAG_DISTRIBUTION, rpm.RPMTAG_VENDOR) \ + or key in (x[0] for x in SCRIPT_TAGS) \ + or key in (x[1] for x in SCRIPT_TAGS): + val = byte_to_string(val) + if key == rpm.RPMTAG_GROUP and val == 'Unspecified': + val = None + return val + + # return the name of the directory where the package is extracted + def dir_name(self): + return self.dirname + + def _extract_rpm(self, dirname, verbose): + if not Path(dirname).is_dir(): + print_warning('Unable to access dir %s' % dirname) + elif dirname == '/': + # it is an InstalledPkg + pass + else: + self.__tmpdir = tempfile.TemporaryDirectory( + prefix='rpmlint.%s.' % Path(self.filename).name, dir=dirname + ) + dirname = self.__tmpdir.name + # TODO: sequence based command invocation + # TODO: warn some way if this fails (e.g. rpm2cpio not installed) + + # BusyBox' cpio does not support '-D' argument and the only safe + # usage is doing chdir before invocation. + filename = Path(self.filename).resolve() + cwd = os.getcwd() + os.chdir(dirname) + command_str = f'rpm2cpio {quote(str(filename))} | cpio -id ; chmod -R +rX .' + stderr = None if verbose else subprocess.DEVNULL + subprocess.check_output(command_str, shell=True, env=ENGLISH_ENVIROMENT, + stderr=stderr) + os.chdir(cwd) + self.extracted = True + return dirname + + def check_signature(self): + ret = subprocess.run(('rpm', '-Kv', self.filename), + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + env=ENGLISH_ENVIROMENT, text=True) + text = ret.stdout + if text.endswith('\n'): + text = text[:-1] + return ret.returncode, text + + # remove the extracted files from the package + def cleanup(self): + if self.extracted and self.dirname: + self.__tmpdir.cleanup() + + def grep(self, regex, filename): + """Grep regex from a file, return first matching line number (starting with 1).""" + data = self.read_with_mmap(filename) + match = regex.search(data) + if match: + return data.count('\n', 0, match.start()) + 1 + else: + return None + + def read_with_mmap(self, filename): + """Mmap a file, return it's content decoded.""" + try: + with open(Path(self.dir_name() or '/', filename.lstrip('/'))) as in_file: + return mmap.mmap(in_file.fileno(), 0, mmap.MAP_SHARED, mmap.PROT_READ).read().decode() + except Exception: + return '' + + def langtag(self, tag, lang): + """Get value of tag in the given language.""" + # LANGUAGE trumps other env vars per GNU gettext docs, see also #166 + orig = os.environ.get('LANGUAGE') + os.environ['LANGUAGE'] = lang + ret = self[tag] + if orig is not None: + os.environ['LANGUAGE'] = orig + return ret + + # extract information about the files + def _gather_files_info(self): + ret = {} + flags = self.header[rpm.RPMTAG_FILEFLAGS] + modes = self.header[rpm.RPMTAG_FILEMODES] + users = self.header[rpm.RPMTAG_FILEUSERNAME] + groups = self.header[rpm.RPMTAG_FILEGROUPNAME] + links = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILELINKTOS]] + sizes = self.header[rpm.RPMTAG_FILESIZES] + md5s = self.header[rpm.RPMTAG_FILEMD5S] + mtimes = self.header[rpm.RPMTAG_FILEMTIMES] + rdevs = self.header[rpm.RPMTAG_FILERDEVS] + langs = self.header[rpm.RPMTAG_FILELANGS] + inodes = self.header[rpm.RPMTAG_FILEINODES] + requires = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILEREQUIRE]] + provides = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILEPROVIDE]] + files = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILENAMES]] + magics = [byte_to_string(x) for x in self.header[rpm.RPMTAG_FILECLASS]] + try: # rpm >= 4.7.0 + filecaps = self.header[rpm.RPMTAG_FILECAPS] + except AttributeError: + filecaps = None + + # rpm-python < 4.6 does not return a list for this (or FILEDEVICES, + # FWIW) for packages containing exactly one file + if not isinstance(inodes, list): + inodes = [inodes] + + if files: + for idx, file in enumerate(files): + pkgfile = PkgFile(file) + pkgfile.path = os.path.normpath(os.path.join( + self.dir_name() or '/', pkgfile.name.lstrip('/'))) + pkgfile.flags = flags[idx] + pkgfile.mode = modes[idx] + pkgfile.user = byte_to_string(users[idx]) + pkgfile.group = byte_to_string(groups[idx]) + pkgfile.linkto = links[idx] and os.path.normpath(links[idx]) + pkgfile.size = sizes[idx] + pkgfile.md5 = md5s[idx] + pkgfile.mtime = mtimes[idx] + pkgfile.rdev = rdevs[idx] + pkgfile.inode = inodes[idx] + pkgfile.requires = parse_deps(requires[idx]) + pkgfile.provides = parse_deps(provides[idx]) + pkgfile.lang = byte_to_string(langs[idx]) + pkgfile.magic = magics[idx] + if not pkgfile.magic: + if stat.S_ISDIR(pkgfile.mode): + pkgfile.magic = 'directory' + elif stat.S_ISLNK(pkgfile.mode): + pkgfile.magic = "symbolic link to `%s'" % pkgfile.linkto + elif not pkgfile.size: + pkgfile.magic = 'empty' + if (not pkgfile.magic and + not pkgfile.is_ghost and has_magic): + start = time.monotonic() + pkgfile.magic = get_magic(pkgfile.path) + self.timers['libmagic'] += time.monotonic() - start + if pkgfile.magic is None or Pkg._magic_from_compressed_re.search(pkgfile.magic): + # Discard magic from inside compressed files ('file -z') + # until PkgFile gets decompression support. We may get + # such magic strings from package headers already now; + # for example Fedora's rpmbuild as of F-11's 4.7.1 is + # patched so it generates them. + pkgfile.magic = '' + if filecaps: + pkgfile.filecaps = byte_to_string(filecaps[idx]) + ret[pkgfile.name] = pkgfile + return ret + + def readlink(self, pkgfile): + """ + Resolve symlinks for the given PkgFile, return the dereferenced + PkgFile if it is found in this package, None if not. + """ + result = pkgfile + while result and result.linkto: + linkpath = urljoin(result.name, result.linkto) + linkpath = os.path.normpath(linkpath) + result = self.files.get(linkpath) + return result + + def check_versioned_dep(self, name, version): + # try to match name%_isa as well (e.g. 'foo(x86-64)', 'foo(x86-32)') + name_re = re.compile(r'^%s(\(\w+-\d+\))?$' % re.escape(name)) + for d in self.requires + self.prereq: + if name_re.match(d[0]): + if d[1] & rpm.RPMSENSE_EQUAL != rpm.RPMSENSE_EQUAL \ + or d[2][1] != version: + return False + return True + return False + + +def get_installed_pkgs(name): + """Get list of installed package objects by name.""" + + pkgs = [] + ts = rpm.TransactionSet() + if re.search(r'[?*]|\[.+\]', name): + mi = ts.dbMatch() + mi.pattern('name', rpm.RPMMIRE_GLOB, name) + else: + mi = ts.dbMatch('name', name) + + for hdr in mi: + pkgs.append(InstalledPkg(name, hdr)) + + return pkgs + + +# Class to provide an API to an installed package +class InstalledPkg(Pkg): + def __init__(self, name, hdr=None): + if not hdr: + ts = rpm.TransactionSet() + mi = ts.dbMatch('name', name) + if not mi: + raise KeyError(name) + try: + hdr = next(mi) + except StopIteration: + raise KeyError(name) + + super().__init__(name, '/', hdr, extracted=True) + # create a fake filename to satisfy some checks on the filename + self.filename = '%s-%s-%s.%s.rpm' % \ + (self.name, self[rpm.RPMTAG_VERSION], self[rpm.RPMTAG_RELEASE], + self[rpm.RPMTAG_ARCH]) + + def cleanup(self): + pass + + def check_signature(self): + return (0, 'fake: pgp md5 OK') + + +# Class to provide an API to a 'fake' package, eg. for specfile-only checks +class FakePkg(AbstractPkg): + _autoheaders = [ + 'requires', + 'conflicts', + 'provides', + 'obsoletes', + 'recommends', + 'suggests', + 'enhances', + 'supplements', + ] + + def __init__(self, name, is_source=False): + self.name = str(name) + self.arch = None + self.current_linenum = None + self.dirname = None + self.is_source = False + + # files are dictionary where key is name of a file + self.files = {} + self.ghost_files = {} + + # header is a dictionary to mock rpm metadata + self.header = {} + for i in self._autoheaders: + # the header name wihtout the ending 's' + tagname = i[:-1].upper() + self.header[getattr(rpm, f'RPMTAG_{tagname}NAME')] = [] + self.header[getattr(rpm, f'RPMTAG_{tagname}FLAGS')] = [] + self.header[getattr(rpm, f'RPMTAG_{tagname}VERSION')] = [] + self.header[rpm.RPMTAG_FILENAMES] = [] + + def add_file(self, path, name): + pkgfile = PkgFile(name) + pkgfile.path = path + self.files[name] = pkgfile + return pkgfile + + def _mock_file(self, path, attrs): + metadata = None + if attrs.get('create_dirs', False): + for i in PurePath(path).parents[:attrs.get('include_dirs', -1)]: + self.add_dir(str(i)) + metadata = attrs.get('metadata', None) + + content = '' + if 'content-path' in attrs: + content = open(attrs['content-path']) + elif 'content' in attrs: + content = attrs['content'] + + if 'linkto' in attrs: + self.add_symlink_to(path, attrs['linkto']) + else: + self.add_file_with_content(path, content, metadata=metadata) + self.header[rpm.RPMTAG_FILENAMES].append(path) + + if 'content-path' in attrs: + content.close() + + def create_files(self, files): + """ + This is a helper method to create files(real files); not PkgFile + objects. + """ + + # files can be just a list + if isinstance(files, list) or isinstance(files, tuple): + for path in files: + self._mock_file(path, {}) + # list of files with attributes and content + elif isinstance(files, dict): + for path, file in files.items(): + self._mock_file(path, file) + + def add_dir(self, path): + pkgdir = PkgFile(path) + pkgdir.magic = 'directory' + pkgdir.path = path + self.files[path] = pkgdir + return pkgdir + + def add_file_with_content(self, name, content, metadata=None, **flags): + """ + Add file to the FakePkg and fill the file with provided + string content. + """ + path = os.path.join(self.dir_name(), name.lstrip('/')) + pkg_file = PkgFile(name) + pkg_file.path = path + pkg_file.mode = stat.S_IFREG | 0o0644 + pkg_file.user = 'root' + pkg_file.group = 'root' + self.files[name] = pkg_file + + # create files in filesystem + os.makedirs(Path(path).parent, exist_ok=True) + + with open(Path(path), 'w') as out: + # file like content + if isinstance(content, io.IOBase): + shutil.copyfileobj(content, out) + # text content + else: + out.write(content) + + # Generating md5 hash values for real files: + pkg_file.md5 = self.md5_checksum(Path(path)) + pkg_file.size = os.path.getsize(Path(path)) + pkg_file.inode = os.stat(Path(path)).st_ino + + if metadata: + for k, v in metadata.items(): + setattr(pkg_file, k, v) + for key, value in flags.items(): + setattr(pkg_file, key, value) + + def initiate_files_base_data(self): + """ This method is called after adding metadata of each file """ + self.config_files = [x.name for x in self.files.values() if x.is_config] + self.doc_files = [x.name for x in self.files.values() if x.is_doc] + self.ghost_files = [x.name for x in self.files.values() if x.is_ghost] + self.noreplace_files = [x.name for x in self.files.values() if x.is_noreplace] + self.missingok_files = [x.name for x in self.files.values() if x.is_missingok] + + def add_header(self, header): + for k, v in header.items(): + if k in self._autoheaders: + # the header name wihtout the ending 's' + tagname = k[:-1].upper() + for i in v: + name, flags, version = parse_deps(i)[0] + version = f'{version[1]}-{version[2]}' + self.header[getattr(rpm, f'RPMTAG_{tagname}NAME')].append(name) + self.header[getattr(rpm, f'RPMTAG_{tagname}FLAGS')].append(flags) + self.header[getattr(rpm, f'RPMTAG_{tagname}VERSION')].append(version) + continue + + key = getattr(rpm, f'RPMTAG_{k}'.upper()) + self.header[key] = v + + (self.requires, self.prereq, self.provides, self.conflicts, + self.obsoletes, self.recommends, self.suggests, self.enhances, + self.supplements) = self._gather_dep_info() + + self.req_names = [x[0] for x in self.requires + self.prereq] + + def add_symlink_to(self, name, target): + """ + Add symlink to name file which path is related to name. + Eg. name == '/etc/foo' and target == '../bar' creates a symlink file + /etc/bar that points to /etc/foo. + """ + pkg_file = PkgFile(name) + pkg_file.mode = stat.S_IFLNK + pkg_file.linkto = target + pkg_file.user = 'root' + pkg_file.group = 'root' + self.files[name] = pkg_file + + def readlink(self, pkgfile): + # HACK: reuse the real Pkg's logic + return Pkg.readlink(self, pkgfile) + + def dir_name(self): + if not self.dirname: + self.__tmpdir = tempfile.TemporaryDirectory(prefix='rpmlint.%s.' % Path(self.name).name) + self.dirname = self.__tmpdir.name + return self.dirname + + def md5_checksum(self, file_name): + md5_hash = hashlib.md5() + with open(file_name, 'rb') as f: + for byte_block in iter(lambda: f.read(4096), b''): + md5_hash.update(byte_block) + return md5_hash.hexdigest() + + def cleanup(self): + if self.dirname: + self.__tmpdir.cleanup() + + # access the tags like an array + def __getitem__(self, key): + return self.header.get(key, None) diff --git a/rpmlint/pkgfile.py b/rpmlint/pkgfile.py new file mode 100644 index 0000000..1c18a01 --- /dev/null +++ b/rpmlint/pkgfile.py @@ -0,0 +1,47 @@ +import rpm + + +class PkgFile: + __slots__ = ['name', 'path', 'flags', 'mode', 'user', 'group', 'linkto', + 'size', 'md5', 'mtime', 'rdev', 'inode', 'requires', 'provides', + 'lang', 'magic', 'filecaps'] + + def __init__(self, name): + self.name = name + # Real path to the file (taking extract dir into account) + self.path = name + self.flags = 0 + self.mode = 0 + self.user = None + self.group = None + self.linkto = '' + self.size = None + self.md5 = None + self.mtime = 0 + self.rdev = '' + self.inode = 0 + self.requires = [] + self.provides = [] + self.lang = '' + self.magic = '' + self.filecaps = None + + @property + def is_config(self): + return self.flags & rpm.RPMFILE_CONFIG + + @property + def is_doc(self): + return self.flags & rpm.RPMFILE_DOC + + @property + def is_noreplace(self): + return self.flags & rpm.RPMFILE_NOREPLACE + + @property + def is_ghost(self): + return self.flags & rpm.RPMFILE_GHOST + + @property + def is_missingok(self): + return self.flags & rpm.RPMFILE_MISSINGOK diff --git a/rpmlint/readelfparser.py b/rpmlint/readelfparser.py new file mode 100644 index 0000000..8cec93f --- /dev/null +++ b/rpmlint/readelfparser.py @@ -0,0 +1,372 @@ +from itertools import dropwhile, takewhile +import re +import subprocess + +from rpmlint.helpers import ENGLISH_ENVIROMENT + + +class ElfSection: + """ + A simple wrapper representing one ELF section. + """ + def __init__(self, name, size): + self.name = name + self.size = int(size, 16) + + +class ElfProgramHeader: + """ + A simple wrapper representing one ELF program header. + """ + def __init__(self, name, flags): + self.name = name + self.flags = flags.replace(' ', '') + + +class ElfDynamicSection: + """ + A simple wrapper representing one ELF dynamic section entry. + """ + def __init__(self, key, value): + self.key = key + self.value = value + + +class ElfSectionInfo: + """ + Class contains information about ELF sections of an ELF file. The information + is get with the following command line: readelf -WS. + + Output example: + + There are 12 section headers, starting at offset 0x268: + + Section Headers: + [Nr] Name Type Address Off Size ES Flg Lk Inf Al + [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 + [ 1] .text PROGBITS 0000000000000000 000040 000015 00 AX 0 0 1 + [ 2] .rela.text RELA 0000000000000000 0001d8 000018 18 I 9 1 8 + [ 3] .data PROGBITS 0000000000000000 000055 000000 00 WA 0 0 1 + [ 4] .bss NOBITS 0000000000000000 000055 000000 00 WA 0 0 1 + [ 5] .comment PROGBITS 0000000000000000 000055 000041 01 MS 0 0 1 + [ 6] .note.GNU-stack PROGBITS 0000000000000000 000096 000000 00 0 0 1 + [ 7] .eh_frame PROGBITS 0000000000000000 000098 000038 00 A 0 0 8 + [ 8] .rela.eh_frame RELA 0000000000000000 0001f0 000018 18 I 9 7 8 + [ 9] .symtab SYMTAB 0000000000000000 0000d0 0000f0 18 10 8 8 + [10] .strtab STRTAB 0000000000000000 0001c0 000011 00 0 0 1 + [11] .shstrtab STRTAB 0000000000000000 000208 000059 00 0 0 1 + Key to Flags: + W (write), A (alloc), X (execute), M (merge), S (strings), I (info), + L (link order), O (extra OS processing required), G (group), T (TLS), + C (compressed), x (unknown), o (OS specific), E (exclude), + l (large), p (processor specific) + """ + + section_regex = re.compile(r'.*\] (?P<section>\S*)\s*\S+\s*\S*\s*\S*\s*(?P<size>\w*)') + pic_regex = re.compile(r'\.rela?\.(data|text)') + + def __init__(self, path, extra_flags): + self.path = path + self.elf_files = [] + self.parsing_failed_reason = None + self.pic = False + self.extra_flags = extra_flags + self.parse() + + def parse(self): + r = subprocess.run(['readelf', '-W', '-S', self.path] + self.extra_flags, encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + return + + lines = r.stdout.splitlines() + needle = 'Section Headers:' + + # archive files can contain multiple files + i = 0 + length = len(lines) + + while i < length: + parsed_sections = [] + while needle not in lines[i]: + i += 1 + if i == length: + return + + # skip header and empty section + i += 3 + + sections = [] + while 'Key to Flags:' not in lines[i]: + sections.append(lines[i]) + i += 1 + + for s in sections: + r = self.section_regex.search(s) + section = ElfSection(r.group('section'), r.group('size')) + parsed_sections.append(section) + + # detect a PIC section + if self.pic_regex.search(section.name) is not None: + self.pic = True + + if len(parsed_sections) > 0: + self.elf_files.append(parsed_sections) + + +class ElfProgramHeaderInfo: + """ + Program Headers: + Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align + PHDR 0x000040 0x0000000000400040 0x0000000000400040 0x000268 0x000268 R 0x8 + INTERP 0x0002a8 0x00000000004002a8 0x00000000004002a8 0x00001c 0x00001c R 0x1 + [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] + LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x000460 0x000460 R 0x1000 + LOAD 0x001000 0x0000000000401000 0x0000000000401000 0x0002ad 0x0002ad R E 0x1000 + LOAD 0x002000 0x0000000000402000 0x0000000000402000 0x0001d0 0x0001d0 R 0x1000 + LOAD 0x002e00 0x0000000000403e00 0x0000000000403e00 0x000230 0x000238 RW 0x1000 + DYNAMIC 0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001e0 0x0001e0 RW 0x8 + NOTE 0x0002c4 0x00000000004002c4 0x00000000004002c4 0x000044 0x000044 R 0x4 + GNU_EH_FRAME 0x002004 0x0000000000402004 0x0000000000402004 0x000054 0x000054 R 0x4 + GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10 + GNU_RELRO 0x002e00 0x0000000000403e00 0x0000000000403e00 0x000200 0x000200 R 0x1 + """ + + header_regex = re.compile('\\s+(?P<header>\\w+)(\\s+\\w+){5}\\s+(?P<flags>[RWE ]{3}).*') + + def __init__(self, path, extra_flags): + self.path = path + self.headers = [] + self.parsing_failed_reason = None + self.extra_flags = extra_flags + self.parse() + + def parse(self): + r = subprocess.run(['readelf', '-W', '-l', self.path] + self.extra_flags, encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + return + + lines = r.stdout.splitlines() + needle = 'Program Headers:' + + while len(lines) > 0: + lines = list(dropwhile(lambda x: needle not in x, lines)) + + # skip header + lines = lines[2:] + + sections = list(takewhile(lambda x: x.strip() != '', lines)) + for s in sections: + r = self.header_regex.search(s) + if r is not None: + self.headers.append(ElfProgramHeader(r.group('header'), r.group('flags'))) + + lines = lines[len(sections):] + + +class ElfDynamicSectionInfo: + """ + 0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2] + 0x000000000000000e (SONAME) Library soname: [libc.so.6] + 0x000000000000000c (INIT) 0x26950 + 0x0000000000000019 (INIT_ARRAY) 0x1ba330 + 0x000000000000001b (INIT_ARRAYSZ) 16 (bytes) + 0x0000000000000004 (HASH) 0x328 + 0x000000006ffffef5 (GNU_HASH) 0x37f8 + 0x0000000000000005 (STRTAB) 0x151e0 + 0x0000000000000006 (SYMTAB) 0x7488 + 0x000000000000000a (STRSZ) 24691 (bytes) + 0x000000000000000b (SYMENT) 24 (bytes) + 0x0000000000000003 (PLTGOT) 0x1bcbd0 + 0x0000000000000002 (PLTRELSZ) 1152 (bytes) + 0x0000000000000014 (PLTREL) RELA + 0x0000000000000017 (JMPREL) 0x24538 + 0x0000000000000007 (RELA) 0x1c948 + 0x0000000000000008 (RELASZ) 31728 (bytes) + 0x0000000000000009 (RELAENT) 24 (bytes) + 0x000000006ffffffc (VERDEF) 0x1c4c8 + 0x000000006ffffffd (VERDEFNUM) 31 + 0x000000000000001e (FLAGS) BIND_NOW STATIC_TLS + 0x000000006ffffffb (FLAGS_1) Flags: NOW + 0x000000006ffffffe (VERNEED) 0x1c918 + 0x000000006fffffff (VERNEEDNUM) 1 + 0x000000006ffffff0 (VERSYM) 0x1b254 + 0x000000006ffffff9 (RELACOUNT) 1232 + 0x0000000000000000 (NULL) 0x0 + + handle also: + + 0x60009990 (Operating System specific: 60009990) 0x24e20 + 0x60009991 (Operating System specific: 60009991) 0x8 + """ + + section_regex = re.compile('\\s+\\w*\\s+\\((?P<key>[^\\)]+)\\)\\s+(?P<value>.*)') + soname_regex = re.compile('Library soname: \\[(?P<soname>[^\\]]+)\\]') + needed_regex = re.compile('Shared library: \\[(?P<library>[^\\]]+)\\]') + runpath_regex = re.compile('Library runpath: \\[(?P<path>[^\\]]+)\\]') + rpath_regex = re.compile('Library rpath: \\[(?P<path>[^\\]]+)\\]') + + def __init__(self, path, extra_flags): + self.path = path + self.sections = [] + self.parsing_failed_reason = None + self.extra_flags = extra_flags + self.parse() + self.parse_meta() + + def parse(self): + r = subprocess.run(['readelf', '-W', '-d', self.path] + self.extra_flags, encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + return + + lines = r.stdout.splitlines() + needle = 'Dynamic section at offset' + + lines = list(dropwhile(lambda x: needle not in x, lines)) + # skip header + lines = lines[2:] + for line in lines: + r = self.section_regex.search(line) + self.sections.append(ElfDynamicSection(r.group('key'), r.group('value'))) + + def parse_meta(self): + self.soname = None + soname = self['SONAME'] + if len(soname) == 1: + r = self.soname_regex.search(soname[0]) + if r: + self.soname = r.group('soname') + + self.needed = [] + for line in self['NEEDED']: + r = self.needed_regex.search(line) + if r: + self.needed.append(r.group('library')) + + self.runpaths = [] + + # Parse both RUNPATH and RPATH + for line in self['RUNPATH']: + r = self.runpath_regex.search(line) + if r: + self.runpaths.append(r.group('path')) + for line in self['RPATH']: + r = self.rpath_regex.search(line) + if r: + self.runpaths.append(r.group('path')) + + def __getitem__(self, key): + return [x.value for x in self.sections if x.key == key] + + +class ElfSymbolTableInfo: + """ + 7: 0000000000000000 0 SECTION LOCAL DEFAULT 7 + 8: 0000000000000000 0 SECTION LOCAL DEFAULT 8 + 9: 0000000000000000 0 SECTION LOCAL DEFAULT 6 + 10: 0000000000000000 18 FUNC GLOBAL DEFAULT 4 main + 11: 0000000000000000 11 FUNC GLOBAL DEFAULT 5 foo + ... + 7: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .comment + 8: 0000000000000000 21 FUNC GLOBAL DEFAULT 1 main + """ + + def __init__(self, path, extra_flags): + self.path = path + self.functions = set() + self.parsing_failed_reason = None + self.extra_flags = extra_flags + self.parse() + + def parse(self): + try: + r = subprocess.run(['readelf', '-W', '-s', self.path] + self.extra_flags, encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + return + + lines = r.stdout.splitlines() + for line in lines: + parts = line.split() + if len(parts) >= 8 and parts[3] == 'FUNC': + self.functions.add(parts[7]) + except UnicodeDecodeError as e: + self.parsing_failed_reason = str(e) + + def get_functions_for_regex(self, regex): + for sym in self.functions: + if regex.search(sym): + yield sym + + +class ElfCommentInfo: + """ + String dump of section '.comment': + [ 1] GHC 8.6.5 + """ + + comment_regex = re.compile('\\s+\\[[\\s[0-9]+\\]\\s+(?P<comment>.*)') + + def __init__(self, path, extra_flags): + self.path = path + self.comments = [] + self.parsing_failed_reason = None + self.extra_flags = extra_flags + self.parse() + + def parse(self): + r = subprocess.run(['readelf', '-p', '.comment', self.path] + self.extra_flags, encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + return + + lines = r.stdout.splitlines() + for line in lines: + r = self.comment_regex.search(line) + if r: + self.comments.append(r.group('comment')) + + +class ReadelfParser: + """ + Class contains all information obtained by readelf command + in a structured format. + """ + + NOT_ELF_ERROR = 'Error: Not an ELF file - it has the wrong magic bytes at the start' + so_regex = re.compile(r'/lib(64)?/[^/]+\.so(\.[0-9]+)*$') + + def __init__(self, pkgfile_path, path): + self.is_archive = path.endswith('.a') + self.is_shlib = self.so_regex.search(path) + self.is_debug = path.endswith('.debug') + + # Do not follow debug info links + output = subprocess.check_output('readelf --help', shell=True, encoding='utf8') + flag = '--debug-dump=no-follow-links' + extra_flags = [flag] if flag in output else [] + + self.section_info = ElfSectionInfo(pkgfile_path, extra_flags) + self.program_header_info = ElfProgramHeaderInfo(pkgfile_path, extra_flags) + self.dynamic_section_info = ElfDynamicSectionInfo(pkgfile_path, extra_flags) + self.symbol_table_info = ElfSymbolTableInfo(pkgfile_path, extra_flags) + self.comment_section_info = ElfCommentInfo(pkgfile_path, extra_flags) + + def parsing_failed_reason(self): + reasons = [self.section_info.parsing_failed_reason, + self.program_header_info.parsing_failed_reason, + self.dynamic_section_info.parsing_failed_reason, + self.symbol_table_info.parsing_failed_reason, + self.comment_section_info.parsing_failed_reason] + reasons = [r for r in reasons if r] + for reason in reasons: + if self.NOT_ELF_ERROR in reason: + return self.NOT_ELF_ERROR + return '\n'.join(reasons) if reasons else None diff --git a/rpmdiff b/rpmlint/rpmdiff.py index 6d91c60..a9e972b 100755..100644 --- a/rpmdiff +++ b/rpmlint/rpmdiff.py @@ -1,40 +1,16 @@ -#!/usr/bin/python -tt -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Mandriva; 2009 Red Hat, Inc.; 2009 Ville Skyttä -# Authors: Frederic Lepied, Florian Festi -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU Library General Public License as published by -# the Free Software Foundation; version 2 only -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Library General Public License for more details. -# -# You should have received a copy of the GNU Library General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import getopt -import itertools -import os.path +import contextlib +from itertools import chain +import pathlib import sys import tempfile import rpm +from rpmlint.helpers import byte_to_string, print_warning +from rpmlint.pkg import get_installed_pkgs, Pkg -if os.path.isdir("/usr/share/rpmlint"): - import site - site.addsitedir("/usr/share/rpmlint") -import Pkg # noqa: E402 - - -class Rpmdiff(object): +class Rpmdiff: # constants - TAGS = (rpm.RPMTAG_NAME, rpm.RPMTAG_SUMMARY, rpm.RPMTAG_DESCRIPTION, rpm.RPMTAG_GROUP, rpm.RPMTAG_LICENSE, rpm.RPMTAG_URL, @@ -45,19 +21,17 @@ class Rpmdiff(object): 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' @@ -65,13 +39,10 @@ class Rpmdiff(object): ADDED = 'added' REMOVED = 'removed' - # code starts here - - def __init__(self, old, new, ignore=None): + def __init__(self, old, new, ignore=None, exclude=None): self.result = [] - self.ignore = ignore - if self.ignore is None: - self.ignore = [] + self.ignore = ignore or [] + self.exclude = exclude or [] FILEIDX = self.__FILEIDX for tag in self.ignore: @@ -84,7 +55,7 @@ class Rpmdiff(object): old = self.__load_pkg(old).header new = self.__load_pkg(new).header except KeyError as e: - Pkg.warn(str(e)) + print_warning(str(e)) sys.exit(2) # Compare single tags @@ -105,14 +76,15 @@ class Rpmdiff(object): self.__comparePRCOs(old, new, tag) # compare the files - - old_files_dict = self.__fileIteratorToDict(old.fiFromHeader()) - new_files_dict = self.__fileIteratorToDict(new.fiFromHeader()) - files = list(set(itertools.chain(iter(old_files_dict), - iter(new_files_dict)))) + 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() for f in files: + if self._excluded(f): + continue + diff = False old_file = old_files_dict.get(f) @@ -123,77 +95,89 @@ class Rpmdiff(object): elif not new_file: self.__add(self.FORMAT, (self.REMOVED, f)) else: - format = '' + fmt = '' for entry in FILEIDX: if entry[1] is not None and \ - old_file[entry[1]] != new_file[entry[1]]: - format = format + entry[0] + getattr(old_file, entry[1]) != getattr(new_file, entry[1]): + fmt += entry[0] diff = True else: - format = format + '.' + fmt += '.' if diff: - self.__add(self.FORMAT, (format, f)) + self.__add(self.FORMAT, (fmt, f)) + + def _excluded(self, f): + f = pathlib.PurePath(f) + for glob in self.exclude: + if f.match(glob): + return True + if glob.startswith('/'): + for parent in f.parents: + if parent.match(glob): + return True + return False # return a report of the differences def textdiff(self): - return '\n'.join((format % data for format, data in self.result)) + return '\n'.join((fmt % data for fmt, data in self.result)) # do the two rpms differ def differs(self): return bool(self.result) # add one differing item - def __add(self, format, data): - self.result.append((format, data)) + def __add(self, fmt, data): + self.result.append((fmt, data)) # load a package from a file or from the installed ones - def __load_pkg(self, name, tmpdir=tempfile.gettempdir()): - try: - if os.path.isfile(name): - return Pkg.Pkg(name, tmpdir) - except TypeError: - pass - inst = Pkg.getInstalledPkgs(name) + def __load_pkg(self, name): + # FIXME: redo to try file/installed and proceed based on that, or pick + # one of the selected first + tmpdir = tempfile.gettempdir() + with contextlib.suppress(TypeError): + if name.is_file(): + return Pkg(name, tmpdir) + inst = get_installed_pkgs(str(name)) if not inst: - raise KeyError("No installed packages by name %s" % name) + raise KeyError(f'No installed packages by name {name}') if len(inst) > 1: - raise KeyError("More than one installed packages by name %s" % name) + raise KeyError(f'More than one installed packages by name {name}') return inst[0] # output the right string according to RPMSENSE_* const def sense2str(self, sense): - s = "" - for tag, char in ((rpm.RPMSENSE_LESS, "<"), - (rpm.RPMSENSE_GREATER, ">"), - (rpm.RPMSENSE_EQUAL, "=")): + s = '' + for tag, char in ((rpm.RPMSENSE_LESS, '<'), + (rpm.RPMSENSE_GREATER, '>'), + (rpm.RPMSENSE_EQUAL, '=')): if sense & tag: s += char return s # output the right requires string according to RPMSENSE_* const def req2str(self, req): - s = "REQUIRES" + s = 'REQUIRES' # we want to use 64 even with rpm versions that define RPMSENSE_PREREQ # as 0 to get sane results when comparing packages built with an old # (64) version and a new (0) one if req & (rpm.RPMSENSE_PREREQ or 64): - s = "PREREQ" + s = 'PREREQ' ss = [] if req & rpm.RPMSENSE_SCRIPT_PRE: - ss.append("pre") + ss.append('pre') if req & rpm.RPMSENSE_SCRIPT_POST: - ss.append("post") + ss.append('post') if req & rpm.RPMSENSE_SCRIPT_PREUN: - ss.append("preun") + ss.append('preun') if req & rpm.RPMSENSE_SCRIPT_POSTUN: - ss.append("postun") - if req & getattr(rpm, "RPMSENSE_PRETRANS", 1 << 7): # rpm >= 4.9.0 - ss.append("pretrans") - if req & getattr(rpm, "RPMSENSE_POSTTRANS", 1 << 5): # rpm >= 4.9.0 - ss.append("posttrans") + ss.append('postun') + if req & getattr(rpm, 'RPMSENSE_PRETRANS', 1 << 7): # rpm >= 4.9.0 + ss.append('pretrans') + if req & getattr(rpm, 'RPMSENSE_POSTTRANS', 1 << 5): # rpm >= 4.9.0 + ss.append('posttrans') if ss: - s += "(%s)" % ",".join(ss) + s += '(%s)' % ','.join(ss) return s @@ -220,11 +204,11 @@ class Rpmdiff(object): # filter self provides, TODO: self %name(%_isa) as well if name == 'PROVIDES': - oldE = old['epoch'] is not None and str(old['epoch']) + ":" or "" - oldV = "%s%s" % (oldE, old.format("%{VERSION}-%{RELEASE}")) + oldE = old['epoch'] is not None and str(old['epoch']) + ':' or '' + oldV = '{}{}'.format(oldE, old.format('%{VERSION}-%{RELEASE}')) oldNV = (old['name'], rpm.RPMSENSE_EQUAL, oldV.encode()) - newE = new['epoch'] is not None and str(new['epoch']) + ":" or "" - newV = "%s%s" % (newE, new.format("%{VERSION}-%{RELEASE}")) + newE = new['epoch'] is not None and str(new['epoch']) + ':' or '' + newV = '{}{}'.format(newE, new.format('%{VERSION}-%{RELEASE}')) newNV = (new['name'], rpm.RPMSENSE_EQUAL, newV.encode()) o = [entry for entry in o if entry != oldNV] n = [entry for entry in n if entry != newNV] @@ -235,66 +219,19 @@ class Rpmdiff(object): if namestr == 'REQUIRES': namestr = self.req2str(oldentry[1]) self.__add(self.DEPFORMAT, - (self.REMOVED, namestr, Pkg.b2s(oldentry[0]), - self.sense2str(oldentry[1]), Pkg.b2s(oldentry[2]))) + (self.REMOVED, namestr, byte_to_string(oldentry[0]), + self.sense2str(oldentry[1]), byte_to_string(oldentry[2]))) for newentry in n: if newentry not in o: namestr = name if namestr == 'REQUIRES': namestr = self.req2str(newentry[1]) self.__add(self.DEPFORMAT, - (self.ADDED, namestr, Pkg.b2s(newentry[0]), - self.sense2str(newentry[1]), Pkg.b2s(newentry[2]))) + (self.ADDED, namestr, byte_to_string(newentry[0]), + self.sense2str(newentry[1]), byte_to_string(newentry[2]))) def __fileIteratorToDict(self, fi): result = {} for filedata in fi: - result[filedata[0]] = filedata[1:] + result[filedata.name] = filedata return result - - -def _usage(exit=1): - print('''Usage: %s [<options>] <old package> <new package> -Options: - -h, --help Output this message and exit - -i, --ignore File property to ignore when calculating differences (may be - used multiple times); valid values are: S (size), M (mode), - 5 (checksum), D (device), N (inode), L (number of links), - V (vflags), U (user), G (group), F (digest), T (time)''' - % sys.argv[0]) - sys.exit(exit) - - -def main(): - - ignore_tags = [] - try: - opts, args = getopt.getopt(sys.argv[1:], - "hti:", ["help", "ignore-times", "ignore="]) - except getopt.GetoptError as e: - Pkg.warn("Error: %s" % e) - _usage() - - for option, argument in opts: - if option in ("-h", "--help"): - _usage(0) - if option in ("-t", "--ignore-times"): - # deprecated; --ignore=T should be used instead - ignore_tags.append("T") - if option in ("-i", "--ignore"): - ignore_tags.append(argument) - - if len(args) != 2: - _usage() - - d = Rpmdiff(args[0], args[1], ignore=ignore_tags) - textdiff = d.textdiff() - if textdiff: - print(textdiff) - sys.exit(int(d.differs())) - - -if __name__ == '__main__': - main() - -# rpmdiff ends here diff --git a/rpmlint/spellcheck.py b/rpmlint/spellcheck.py new file mode 100644 index 0000000..8088454 --- /dev/null +++ b/rpmlint/spellcheck.py @@ -0,0 +1,127 @@ +import re + +from rpmlint.helpers import print_warning + +try: + from enchant import Broker + from enchant.checker import SpellChecker + from enchant.tokenize import EmailFilter, URLFilter, WikiWordFilter + ENCHANT = True +except ImportError: + # if the enchant is not present we simply continue but without + # spellchecking work being done + ENCHANT = None + + +class Spellcheck: + """ + The object containing current state of spellchecking used within rpmlint + """ + + sentence_break_regex = re.compile(r'(^|[.:;!?])\s*$') + _enchant_checkers = {} + + def __init__(self): + pass + + def _init_checker(self, lang='en_US'): + """ + Initialize a checker of selected language if it is not yet present + lang: language to initialize the dictionary + """ + + # C language means English + if lang == 'C': + lang = 'en_US' + + # test if we actually have working enchant + if not ENCHANT: + print_warning('(none): W: unable to init enchant, spellchecking disabled.') + return + + # there might not be myspell/aspell/etc dicts present + broker = Broker() + if not broker.dict_exists(lang): + print_warning(f'(none): W: unable to load spellchecking dictionary for {lang}.') + return + + if lang not in self._enchant_checkers: + checker = SpellChecker(lang, filters=[EmailFilter, URLFilter, WikiWordFilter]) + self._enchant_checkers[lang] = checker + + def spell_check(self, text, fmt, lang='en_US', pkgname='', ignored_words=None): + """ + Spell check string and return list of warnings if we found out any typos. + text: the checked text + fmt: format of the result ie 'Description({})' + lang: language code ie en_US, default en_US + pkgname: name of the checked package - for specific ignore finegraining + ignored_words: words to be ignored by the spellchecker + """ + 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) + # If the init failed, just return + if lang not in self._enchant_checkers: + return suggestions + checker = self._enchant_checkers[lang] + + if checker: + checker.set_text(re.sub(r'\s+', ' ', text)) + # Uppercase packagename to be case insensitive + uppername = pkgname.upper() + # Allow partial matches for just part of the name + upperparts = uppername.split('-') + # In english we can have ie. django's (so ignore such words) + if lang.startswith('en'): + ups = [x + "'S" for x in upperparts] + upperparts.extend(ups) + # uppercase all ignorewords + if ignored_words: + ignored_words = [x.upper() for x in ignored_words] + + # for each error found skip some parts + for err in checker: + # Skip already warned + if err.word in warned: + continue + warned.add(err.word) + + # Skip all capitalized words that do not start a sentence + if err.word[0].isupper() and not \ + self.sentence_break_regex.search(checker.leading_context(3)): + continue + + # Skip all uppercase words + upperword = err.word.upper() + if err.word == upperword: + continue + + # skip all ignored words + if ignored_words and upperword in ignored_words: + continue + + # Skip errors containing package name or equal to a + # 'component' of it, case insensitively + if upperword in uppername or upperword in upperparts: + continue + + # Work around enchant's digit tokenizing behavior where + # we split on numbers, just ommit everything thats in there + if checker.leading_context(1).isdigit() or \ + checker.trailing_context(1).isdigit(): + continue + + # Warn and suggest + sug = ', '.join(checker.suggest()[:3]) + if sug: + sug = f'-> {sug}' + suggestions[err.word] = fmt.format(lang) + f' {err.word} {sug}' + return suggestions diff --git a/rpmlint/stringsparser.py b/rpmlint/stringsparser.py new file mode 100644 index 0000000..e27d8e9 --- /dev/null +++ b/rpmlint/stringsparser.py @@ -0,0 +1,24 @@ +import subprocess + +from rpmlint.helpers import ENGLISH_ENVIROMENT + + +class StringsParser: + """ + Class contains all information obtained by strings command. + """ + + def __init__(self, pkgfile_path): + self.pkgfile_path = pkgfile_path + self.strings = [] + self.parsing_failed_reason = None + self.parse() + + def parse(self): + r = subprocess.run(['strings', self.pkgfile_path], encoding='utf8', + capture_output=True, env=ENGLISH_ENVIROMENT) + if r.returncode != 0: + self.parsing_failed_reason = r.stderr + return + + self.strings = r.stdout.splitlines() diff --git a/rpmlint/version.py b/rpmlint/version.py new file mode 100644 index 0000000..2de5bee --- /dev/null +++ b/rpmlint/version.py @@ -0,0 +1,9 @@ +try: + from importlib.metadata import version, PackageNotFoundError +except ImportError: + from importlib_metadata import version, PackageNotFoundError + +try: + __version__ = version('rpmlint') +except PackageNotFoundError: + __version__ = '0.0.0' @@ -1,4 +1,10 @@ +[aliases] +test=pytest + [flake8] -ignore = E122,E501,H101,H105,H201,H301,H404,H405,B001,W504 +# FIXME: enable once https://github.com/MartinThoma/flake8-simplify/issues/148 +# is resolved +ignore = E122,E501,W504 import-order-style = google -application-import-names = __isocodes__,__version__,AbstractCheck,AppDataCheck,BinariesCheck,Config,ConfigCheck,DistributionCheck,DocFilesCheck,FHSCheck,FilesCheck,Filter,I18NCheck,InitScriptCheck,LSBCheck,MenuCheck,MenuXDGCheck,NamingPolicyCheck,PamCheck,Pkg,PostCheck,RpmFileCheck,SCLCheck,SignatureCheck,SourceCheck,SpecCheck,TagsCheck,ZipCheck,Testing +application-import-names = Testing + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..b024da8 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + + +setup() diff --git a/test.sh b/test.sh deleted file mode 100755 index ad29398..0000000 --- a/test.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/sh - -export PYTHONPATH=$(pwd)/tools:$(pwd) -export TESTPATH="$(pwd)/test/" -: ${PYTHON:=python} ${PYTEST:=py.test} ${FLAKE8:=flake8} -: ${PYTHONWARNINGS:=all} -export PYTHONWARNINGS - -echo -echo "Please ignore the possibly occurring output like this:" -echo " .../Patch*.patch: No such file or directory" -echo - -for i in $TESTPATH/test.*.py; do - $PYTHON $i - RET=$? - if [ $RET -ne 0 ]; then - exit $RET - fi -done - -run_rpmlint="$PYTHON ./rpmlint -C $(pwd)" - -echo "Check that rpmlint executes with no unexpected errors" -echo "...in default locale" -$run_rpmlint test/*/*.rpm test/spec/*.spec >/dev/null -rc=$? -test $rc -eq 0 -o $rc -eq 64 || exit $rc -echo "...in the C locale" -LC_ALL=C $run_rpmlint test/*/*.rpm test/spec/*.spec >/dev/null -rc=$? -test $rc -eq 0 -o $rc -eq 64 || exit $rc -echo "...with specfile from stdin" -$run_rpmlint - < $TESTPATH/spec/SpecCheck.spec >/dev/null -rc=$? -test $rc -eq 0 || exit $rc - -echo "$PYTEST tests" -$PYTEST -v || exit $? - -unset PYTHONWARNINGS - -echo "$FLAKE8 tests" -$FLAKE8 --version -$FLAKE8 . ./rpmdiff ./rpmlint || exit $? - -echo "man page tests" -if man --help 2>&1 | grep -q -- --warnings; then - tmpfile=$(mktemp) || exit 1 - for manpage in ./rpmdiff.1 ./rpmlint.1; do - man --warnings $manpage >/dev/null 2>$tmpfile - if [ -s $tmpfile ]; then - echo $manpage: - cat $tmpfile - rm -f $tmpfile - exit 1 - else - >$tmpfile - fi - done - rm -f $tmpfile -else - echo "Skipped, man does not seem to recognize the --warnings switch" -fi diff --git a/test/Dockerfile-centos7 b/test/Dockerfile-centos7 deleted file mode 100644 index 791372b..0000000 --- a/test/Dockerfile-centos7 +++ /dev/null @@ -1,19 +0,0 @@ -FROM centos:7 - -RUN yum -y install \ - /bin/cpio \ - /usr/bin/bzip2 \ - /usr/bin/desktop-file-validate \ - /usr/bin/groff \ - /usr/bin/gtbl \ - /usr/bin/make \ - /usr/bin/man \ - /usr/bin/perl \ - /usr/bin/py.test \ - /usr/bin/readelf \ - /usr/bin/xz \ - python-enchant \ - python-magic - -WORKDIR /usr/src/rpmlint -COPY . . diff --git a/test/Dockerfile-fedora28 b/test/Dockerfile-fedora28 deleted file mode 100644 index ecf00c1..0000000 --- a/test/Dockerfile-fedora28 +++ /dev/null @@ -1,22 +0,0 @@ -FROM fedora:28 - -RUN dnf -y install \ - /usr/bin/appstream-util \ - /usr/bin/cpio \ - /usr/bin/bzip2 \ - /usr/bin/desktop-file-validate \ - /usr/bin/groff \ - /usr/bin/gtbl \ - /usr/bin/make \ - /usr/bin/man \ - /usr/bin/perl \ - /usr/bin/py.test-3 \ - /usr/bin/flake8-3 \ - /usr/bin/readelf \ - /usr/bin/xz \ - python3-enchant \ - python3-flake8-import-order \ - python3-magic - -WORKDIR /usr/src/rpmlint -COPY . . diff --git a/test/Dockerfile-fedoradev b/test/Dockerfile-fedoradev deleted file mode 100644 index 0d48c4c..0000000 --- a/test/Dockerfile-fedoradev +++ /dev/null @@ -1,25 +0,0 @@ -FROM fedora:rawhide - -# --nogpgcheck: Ignore transient(?) package signature failures -RUN dnf --nogpgcheck -y install \ - /usr/bin/appstream-util \ - /usr/bin/cpio \ - /usr/bin/bzip2 \ - /usr/bin/desktop-file-validate \ - /usr/bin/groff \ - /usr/bin/gtbl \ - /usr/bin/make \ - /usr/bin/man \ - /usr/bin/perl \ - /usr/bin/python \ - /usr/bin/readelf \ - /usr/bin/xz \ - python3-enchant \ - python3-flake8 \ - python3-flake8-import-order \ - python3-magic \ - python3-pytest \ - python3-devel rpm-build - -WORKDIR /usr/src/rpmlint -COPY . . diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..d4cc31a --- /dev/null +++ b/test/README.md @@ -0,0 +1,65 @@ +# Testing + +The RPMLint test suite has undergone some changes as part of the Google Summer of Code program. These changes can be seen in the links [openSUSE/mentoring#189](https://github.com/openSUSE/mentoring/issues/189) and [rpm-software-management/rpmlint#1101](https://github.com/rpm-software-management/rpmlint/pull/1101). The new test suite uses a mocking strategy to address the issue of relying on binary RPM files. Binary RPM files take a lot of time to unpack and consume real resources like storage in the repository. They also require significant computation when unpacked as individual files. + +In this new test suite, we will utilize a `FakePkg` class, which acts as a mock representation of a `Pkg`. This `Pkg` resembles a real RPM file, allowing any test function to use it. Although `FakePkg` is still in its early stages, it can already mock many tests compared to the current implementation. + +## `get_tested_mock_package` Function + +The `get_tested_mock_package` function's interface is as follows: + +```python +def get_tested_mock_package(files=None, header=None) +``` + +For each new test, we employ the `get_tested_mock_package` function, a helper from `test/Testing.py`. This function leverages the `FakePkg` class to create a mock package named `mockPkg`. + +The current implementation of the `get_tested_mock_package` function is as follows: + +```python +def get_tested_mock_package(files=None, header=None): + mockPkg = FakePkg('mockPkg') + if files is not None: + mockPkg.create_files(files) + if header is not None: + mockPkg.add_header(header) + mockPkg.initiate_files_base_data() + return mockPkg +``` + +The `get_tested_mock_package` function can accept arguments +- `files` +- `header` + +See the example test function below to get basic idea + +```python +@pytest.mark.parametrize('package', [get_tested_mock_package( + files={ + '/usr/lib/python2.7/site-packages/doc': {}, + '/usr/lib/python2.7/site-packages/docs': {}, + '/usr/lib/python3.10/site-packages/doc': {}, + '/usr/lib/python3.10/site-packages/docs': {}, + '/usr/lib64/python2.7/site-packages/doc': {}, + '/usr/lib64/python2.7/site-packages/docs': {}, + '/usr/lib64/python3.10/site-packages/doc': {}, + '/usr/lib64/python3.10/site-packages/docs': {} + } +)]) +def test_python_doc_in_site_packages(package, pythoncheck): + output, test = pythoncheck + test.check(package) + out = output.print_results(output.results) + assert 'E: python-doc-in-site-packages /usr/lib/python2.7/site-packages/doc' in out + # ... (similar assertions for other paths) +``` + +**`files`**: +`files` argument takes each file's path and a dictionary as shown above `'/usr/lib/python2.7/site-packages/doc': {}` the value part is again a dictionary with file related data such as `create_dirs`, `metadata` and `include_dirs`. `metadata` is yet versatile it can assign any rpm related options or simply rpm file meta data unique to file. + +If the content or metadata of the files in the package is not important, it's +possible to use just a list of paths and the files will be created with default +empty content and default flags. + +**`header`**: +Header is dictionary object that is specific to rpm file. We can pass specific rpm file header information with this parameter. See [`test_python.py`](https://github.com/afrid18/rpmlint/blob/c7e36548742f94acc9e102dc328605fdea06329c/test/test_python.py#L183) tests for more info diff --git a/test/Testing.py b/test/Testing.py new file mode 100644 index 0000000..79976d6 --- /dev/null +++ b/test/Testing.py @@ -0,0 +1,78 @@ +import glob +import os +from pathlib import Path +import platform +import re +import shutil +import subprocess + +from rpmlint.config import Config +from rpmlint.pkg import FakePkg, Pkg +import rpmlint.spellcheck + + +def _testpath(): + return Path(os.environ.get('TESTPATH', Path(__file__).parent)) + + +TEST_CONFIG = [_testpath() / 'configs/test.config'] +CONFIG = Config(TEST_CONFIG) + +# predicates used for pytest.mark.skipif decorators +IS_X86_64 = platform.machine() == 'x86_64' +IS_I686 = re.match(platform.machine(), 'i[3456]86') +HAS_32BIT_GLIBC = glob.glob('/lib/ld-linux.so.*') +HAS_CHECKBASHISMS = shutil.which('checkbashisms') +HAS_DASH = shutil.which('dash') +HAS_DESKTOP_FILE_UTILS = shutil.which('desktop-file-validate') +HAS_APPSTREAM_GLIB = shutil.which('appstream-util') + +RPMDB_PATH = subprocess.run(['rpm', '--eval', '%_dbpath'], encoding='utf8', stdout=subprocess.PIPE).stdout +HAS_RPMDB = RPMDB_PATH and Path(RPMDB_PATH.strip()).exists() + + +def _has_dictionary(language): + if not rpmlint.spellcheck.ENCHANT: + return False + spell = rpmlint.spellcheck.Spellcheck() + spell._init_checker(language) + return spell._enchant_checkers.get(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): + return _testpath().joinpath(*paths) + + +def get_tested_package(name, testdir): + filename = Path(name).name + '-*.rpm' + candidates = list(get_tested_path(name).parent.glob(filename)) + assert len(candidates) == 1 + return Pkg(candidates[0], testdir) + + +def get_tested_spec_package(name): + filename = Path(name).name + '.spec' + candidates = list(get_tested_path(name).parent.glob(filename)) + assert len(candidates) == 1 + return FakePkg(candidates[0]) + + +def get_tested_mock_package(files=None, header=None): + mockPkg = FakePkg('mockPkg') + if files is not None: + if isinstance(files, dict): + # full path for test files + for attrs in files.values(): + if 'content-path' in attrs: + attrs['content-path'] = get_tested_path(attrs['content-path']) + + mockPkg.create_files(files) + if header is not None: + mockPkg.add_header(header) + mockPkg.initiate_files_base_data() + return mockPkg diff --git a/test/binary/PamCheck-0.1-1.i586.rpm b/test/binary/PamCheck-0.1-1.i586.rpm Binary files differdeleted file mode 100644 index 2606df9..0000000 --- a/test/binary/PamCheck-0.1-1.i586.rpm +++ /dev/null diff --git a/test/binary/SpecCheck4-0.0.1-0.x86_64.rpm b/test/binary/SpecCheck4-0.0.1-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..7624f74 --- /dev/null +++ b/test/binary/SpecCheck4-0.0.1-0.x86_64.rpm diff --git a/test/binary/alternatives-borked-1.0-0.x86_64.rpm b/test/binary/alternatives-borked-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..344dcf2 --- /dev/null +++ b/test/binary/alternatives-borked-1.0-0.x86_64.rpm diff --git a/test/binary/alternatives-ok-1.0-0.x86_64.rpm b/test/binary/alternatives-ok-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..a751fca --- /dev/null +++ b/test/binary/alternatives-ok-1.0-0.x86_64.rpm diff --git a/test/binary/appdata-1.91-0.noarch.rpm b/test/binary/appdata-1.91-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..61a6c5d --- /dev/null +++ b/test/binary/appdata-1.91-0.noarch.rpm diff --git a/test/binary/asm-1.5.3-0.noarch.rpm b/test/binary/asm-1.5.3-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..628dcaf --- /dev/null +++ b/test/binary/asm-1.5.3-0.noarch.rpm diff --git a/test/binary/autofs-5.0.7-28.fc19.i686.rpm b/test/binary/autofs-5.0.7-28.fc19.i686.rpm Binary files differdeleted file mode 100644 index ef8d97a..0000000 --- a/test/binary/autofs-5.0.7-28.fc19.i686.rpm +++ /dev/null diff --git a/test/binary/bad-crc-uncompressed-1.0-9.1.x86_64.rpm b/test/binary/bad-crc-uncompressed-1.0-9.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..d80aa5e --- /dev/null +++ b/test/binary/bad-crc-uncompressed-1.0-9.1.x86_64.rpm diff --git a/test/binary/bashisms-0-0.x86_64.rpm b/test/binary/bashisms-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..3d2e187 --- /dev/null +++ b/test/binary/bashisms-0-0.x86_64.rpm diff --git a/test/binary/bcc-lua-0.10.0-86.12.x86_64.rpm b/test/binary/bcc-lua-0.10.0-86.12.x86_64.rpm Binary files differnew file mode 100644 index 0000000..73f9c6e --- /dev/null +++ b/test/binary/bcc-lua-0.10.0-86.12.x86_64.rpm diff --git a/test/binary/binary-in-etc-1.0-0.x86_64.rpm b/test/binary/binary-in-etc-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..ec1aac0 --- /dev/null +++ b/test/binary/binary-in-etc-1.0-0.x86_64.rpm diff --git a/test/binary/builddate-0-0.x86_64.rpm b/test/binary/builddate-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..f650d18 --- /dev/null +++ b/test/binary/builddate-0-0.x86_64.rpm diff --git a/test/binary/buildroot-0-0.x86_64.rpm b/test/binary/buildroot-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..9f5be86 --- /dev/null +++ b/test/binary/buildroot-0-0.x86_64.rpm diff --git a/test/binary/crypto-policy-1.0-0.x86_64.rpm b/test/binary/crypto-policy-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..55f0a74 --- /dev/null +++ b/test/binary/crypto-policy-1.0-0.x86_64.rpm diff --git a/test/binary/cyrus-imapd-2.4.17-9.fc22.x86_64.rpm b/test/binary/cyrus-imapd-2.4.17-9.fc22.x86_64.rpm Binary files differdeleted file mode 100644 index 14b40df..0000000 --- a/test/binary/cyrus-imapd-2.4.17-9.fc22.x86_64.rpm +++ /dev/null diff --git a/test/binary/dbusrule-0-0.x86_64.rpm b/test/binary/dbusrule-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..29bc3c3 --- /dev/null +++ b/test/binary/dbusrule-0-0.x86_64.rpm diff --git a/test/binary/desktopfile-bad-binary-0-0.noarch.rpm b/test/binary/desktopfile-bad-binary-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..636fa71 --- /dev/null +++ b/test/binary/desktopfile-bad-binary-0-0.noarch.rpm diff --git a/test/binary/desktopfile-bad-duplicate-0-0.noarch.rpm b/test/binary/desktopfile-bad-duplicate-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..2aa554a --- /dev/null +++ b/test/binary/desktopfile-bad-duplicate-0-0.noarch.rpm diff --git a/test/binary/desktopfile-bad-section-0-0.noarch.rpm b/test/binary/desktopfile-bad-section-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..1954f2a --- /dev/null +++ b/test/binary/desktopfile-bad-section-0-0.noarch.rpm diff --git a/test/binary/desktopfile-bad-unicode-0-0.noarch.rpm b/test/binary/desktopfile-bad-unicode-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..e74b286 --- /dev/null +++ b/test/binary/desktopfile-bad-unicode-0-0.noarch.rpm diff --git a/test/binary/desktopfile-good-0-0.noarch.rpm b/test/binary/desktopfile-good-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..9eda931 --- /dev/null +++ b/test/binary/desktopfile-good-0-0.noarch.rpm diff --git a/test/binary/dev-dependency-0-3.1.x86_64.rpm b/test/binary/dev-dependency-0-3.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..bc235ca --- /dev/null +++ b/test/binary/dev-dependency-0-3.1.x86_64.rpm diff --git a/test/binary/development-0-0.x86_64.rpm b/test/binary/development-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..ad70d7d --- /dev/null +++ b/test/binary/development-0-0.x86_64.rpm diff --git a/test/binary/doc-file-dependency-1.0.0-0.noarch.rpm b/test/binary/doc-file-dependency-1.0.0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..ee088ea --- /dev/null +++ b/test/binary/doc-file-dependency-1.0.0-0.noarch.rpm diff --git a/test/binary/dovecot-2.2.9-1.fc20.x86_64.rpm b/test/binary/dovecot-2.2.9-1.fc20.x86_64.rpm Binary files differdeleted file mode 100644 index 003e38c..0000000 --- a/test/binary/dovecot-2.2.9-1.fc20.x86_64.rpm +++ /dev/null diff --git a/test/binary/erlang-test-0-2.1.x86_64.rpm b/test/binary/erlang-test-0-2.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..325eb23 --- /dev/null +++ b/test/binary/erlang-test-0-2.1.x86_64.rpm diff --git a/test/binary/fPing-4.2~dev-1.2~.3.x86_64.rpm b/test/binary/fPing-4.2~dev-1.2~.3.x86_64.rpm Binary files differnew file mode 100644 index 0000000..09b258c --- /dev/null +++ b/test/binary/fPing-4.2~dev-1.2~.3.x86_64.rpm diff --git a/test/binary/file-zero-length-1.1-0.noarch.rpm b/test/binary/file-zero-length-1.1-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..03ca67b --- /dev/null +++ b/test/binary/file-zero-length-1.1-0.noarch.rpm diff --git a/test/binary/filechecks-0-0.x86_64.rpm b/test/binary/filechecks-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..25ebbd1 --- /dev/null +++ b/test/binary/filechecks-0-0.x86_64.rpm diff --git a/test/binary/foo-devel-0-0.x86_64.rpm b/test/binary/foo-devel-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..433874f --- /dev/null +++ b/test/binary/foo-devel-0-0.x86_64.rpm diff --git a/test/binary/fuse-common-3.10.2-5.el8.x86_64.rpm b/test/binary/fuse-common-3.10.2-5.el8.x86_64.rpm Binary files differnew file mode 100644 index 0000000..9b576ba --- /dev/null +++ b/test/binary/fuse-common-3.10.2-5.el8.x86_64.rpm diff --git a/test/binary/ghc-0-0.x86_64.rpm b/test/binary/ghc-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..11d7693 --- /dev/null +++ b/test/binary/ghc-0-0.x86_64.rpm diff --git a/test/binary/glibc-0-0.x86_64.rpm b/test/binary/glibc-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..d46a6e5 --- /dev/null +++ b/test/binary/glibc-0-0.x86_64.rpm diff --git a/test/binary/hello-2.0-1.x86_64-signed.rpm b/test/binary/hello-2.0-1.x86_64-signed.rpm Binary files differnew file mode 100644 index 0000000..32f312f --- /dev/null +++ b/test/binary/hello-2.0-1.x86_64-signed.rpm diff --git a/test/binary/init-0-0.x86_64.rpm b/test/binary/init-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..8323a2a --- /dev/null +++ b/test/binary/init-0-0.x86_64.rpm diff --git a/test/binary/install-file-in-docs-1.0-0.x86_64.rpm b/test/binary/install-file-in-docs-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..b0633b4 --- /dev/null +++ b/test/binary/install-file-in-docs-1.0-0.x86_64.rpm diff --git a/test/binary/invalid-dependency-0-0.x86_64.rpm b/test/binary/invalid-dependency-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..d861bbc --- /dev/null +++ b/test/binary/invalid-dependency-0-0.x86_64.rpm diff --git a/test/binary/invalid-exception-0-0.x86_64.rpm b/test/binary/invalid-exception-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..0bda10d --- /dev/null +++ b/test/binary/invalid-exception-0-0.x86_64.rpm diff --git a/test/binary/invalid-la-file-1.0-0.x86_64.rpm b/test/binary/invalid-la-file-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..928e7da --- /dev/null +++ b/test/binary/invalid-la-file-1.0-0.x86_64.rpm diff --git a/test/binary/invalid-license-0-1.1.x86_64.rpm b/test/binary/invalid-license-0-1.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..b317e64 --- /dev/null +++ b/test/binary/invalid-license-0-1.1.x86_64.rpm diff --git a/test/binary/invalid-version-0pre-3.1.x86_64.rpm b/test/binary/invalid-version-0pre-3.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..e75f832 --- /dev/null +++ b/test/binary/invalid-version-0pre-3.1.x86_64.rpm diff --git a/test/binary/libalternatives-borked-1.0-0.x86_64.rpm b/test/binary/libalternatives-borked-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..2e1b4a1 --- /dev/null +++ b/test/binary/libalternatives-borked-1.0-0.x86_64.rpm diff --git a/test/binary/libalternatives-ok-1.0-0.x86_64.rpm b/test/binary/libalternatives-ok-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..ecb0e89 --- /dev/null +++ b/test/binary/libalternatives-ok-1.0-0.x86_64.rpm diff --git a/test/binary/libnoexec-0-0.x86_64.rpm b/test/binary/libnoexec-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..bbac5d1 --- /dev/null +++ b/test/binary/libnoexec-0-0.x86_64.rpm diff --git a/test/binary/libslp-missing-suffix-1.0-0.x86_64.rpm b/test/binary/libslp-missing-suffix-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..10fc304 --- /dev/null +++ b/test/binary/libslp-missing-suffix-1.0-0.x86_64.rpm diff --git a/test/binary/libslp1234-1.0-0.x86_64.rpm b/test/binary/libslp1234-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..8e4aaae --- /dev/null +++ b/test/binary/libslp1234-1.0-0.x86_64.rpm diff --git a/test/binary/libtest-1.0-0.x86_64.rpm b/test/binary/libtest-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..1e6a578 --- /dev/null +++ b/test/binary/libtest-1.0-0.x86_64.rpm diff --git a/test/binary/libtest1-1.5-0.x86_64.rpm b/test/binary/libtest1-1.5-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..7e3692e --- /dev/null +++ b/test/binary/libtest1-1.5-0.x86_64.rpm diff --git a/test/binary/libtest2-1.5-0.x86_64.rpm b/test/binary/libtest2-1.5-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..db35d81 --- /dev/null +++ b/test/binary/libtest2-1.5-0.x86_64.rpm diff --git a/test/binary/libtest3-3.5-0.x86_64.rpm b/test/binary/libtest3-3.5-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..cc7b322 --- /dev/null +++ b/test/binary/libtest3-3.5-0.x86_64.rpm diff --git a/test/binary/libtest4-4.0-0.x86_64.rpm b/test/binary/libtest4-4.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..08ba077 --- /dev/null +++ b/test/binary/libtest4-4.0-0.x86_64.rpm diff --git a/test/binary/libtool-wrapper-0-0.x86_64.rpm b/test/binary/libtool-wrapper-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..99c8460 --- /dev/null +++ b/test/binary/libtool-wrapper-0-0.x86_64.rpm diff --git a/test/binary/logrotate-0-0.x86_64.rpm b/test/binary/logrotate-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..34da835 --- /dev/null +++ b/test/binary/logrotate-0-0.x86_64.rpm diff --git a/test/binary/lto-text-1.0-0.x86_64.rpm b/test/binary/lto-text-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..77da933 --- /dev/null +++ b/test/binary/lto-text-1.0-0.x86_64.rpm diff --git a/test/binary/macros-nodejs010-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm b/test/binary/macros-nodejs010-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm Binary files differdeleted file mode 100644 index 2eeccd5..0000000 --- a/test/binary/macros-nodejs010-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm +++ /dev/null diff --git a/test/binary/makefile-junk-0-0.x86_64.rpm b/test/binary/makefile-junk-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..388586a --- /dev/null +++ b/test/binary/makefile-junk-0-0.x86_64.rpm diff --git a/test/binary/manual-pages-0-0.noarch.rpm b/test/binary/manual-pages-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..54682ba --- /dev/null +++ b/test/binary/manual-pages-0-0.noarch.rpm diff --git a/test/binary/mc-4.8.15-10.3.1.x86_64.rpm b/test/binary/mc-4.8.15-10.3.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..27eb452 --- /dev/null +++ b/test/binary/mc-4.8.15-10.3.1.x86_64.rpm diff --git a/test/binary/mc-4.8.21-2.1.x86_64.rpm b/test/binary/mc-4.8.21-2.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..1f0c21f --- /dev/null +++ b/test/binary/mc-4.8.21-2.1.x86_64.rpm diff --git a/test/binary/misc-no-warnings-0-1.x86_64.rpm b/test/binary/misc-no-warnings-0-1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..16a865b --- /dev/null +++ b/test/binary/misc-no-warnings-0-1.x86_64.rpm diff --git a/test/binary/misc-warnings-0pre-3.1.x86_64.rpm b/test/binary/misc-warnings-0pre-3.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..3447ae5 --- /dev/null +++ b/test/binary/misc-warnings-0pre-3.1.x86_64.rpm diff --git a/test/binary/missingprovides-devel-0-0.x86_64.rpm b/test/binary/missingprovides-devel-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..b2e9217 --- /dev/null +++ b/test/binary/missingprovides-devel-0-0.x86_64.rpm diff --git a/test/binary/mixed-ownership-1.0-0.x86_64.rpm b/test/binary/mixed-ownership-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..a66af6c --- /dev/null +++ b/test/binary/mixed-ownership-1.0-0.x86_64.rpm diff --git a/test/binary/multiple_errors-1.0-0.x86_64.rpm b/test/binary/multiple_errors-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..73193ed --- /dev/null +++ b/test/binary/multiple_errors-1.0-0.x86_64.rpm diff --git a/test/binary/mydoc-0-0.x86_64.rpm b/test/binary/mydoc-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..ab02815 --- /dev/null +++ b/test/binary/mydoc-0-0.x86_64.rpm diff --git a/test/binary/needxinetd-0-0.x86_64.rpm b/test/binary/needxinetd-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..96f76e9 --- /dev/null +++ b/test/binary/needxinetd-0-0.x86_64.rpm diff --git a/test/binary/no-binary-1.0-0.x86_64.rpm b/test/binary/no-binary-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..23c4c95 --- /dev/null +++ b/test/binary/no-binary-1.0-0.x86_64.rpm diff --git a/test/binary/no-signature-1.0-0.noarch.rpm b/test/binary/no-signature-1.0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..f111fdd --- /dev/null +++ b/test/binary/no-signature-1.0-0.noarch.rpm diff --git a/test/binary/no-url-tag-0alpha-5.1.x86_64.rpm b/test/binary/no-url-tag-0alpha-5.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..b78cec8 --- /dev/null +++ b/test/binary/no-url-tag-0alpha-5.1.x86_64.rpm diff --git a/test/binary/noarch-noarch.rpm b/test/binary/noarch-noarch.rpm Binary files differnew file mode 100644 index 0000000..e50a790 --- /dev/null +++ b/test/binary/noarch-noarch.rpm diff --git a/test/binary/nodejs010-nodejs-0.10.3-3.el6_4.x86_64.rpm b/test/binary/nodejs010-nodejs-0.10.3-3.el6_4.x86_64.rpm Binary files differdeleted file mode 100644 index 53f426b..0000000 --- a/test/binary/nodejs010-nodejs-0.10.3-3.el6_4.x86_64.rpm +++ /dev/null diff --git a/test/binary/nodejs010-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm b/test/binary/nodejs010-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm Binary files differdeleted file mode 100644 index 59b4c37..0000000 --- a/test/binary/nodejs010-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm +++ /dev/null diff --git a/test/binary/nodejs010-runtime-1-7.el6_4.x86_64.rpm b/test/binary/nodejs010-runtime-1-7.el6_4.x86_64.rpm Binary files differdeleted file mode 100644 index 42c36d0..0000000 --- a/test/binary/nodejs010-runtime-1-7.el6_4.x86_64.rpm +++ /dev/null diff --git a/test/binary/nodejs110-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm b/test/binary/nodejs110-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm Binary files differdeleted file mode 100644 index b2e6396..0000000 --- a/test/binary/nodejs110-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm +++ /dev/null diff --git a/test/binary/non-fhs-0-0.x86_64.rpm b/test/binary/non-fhs-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..dabe8c0 --- /dev/null +++ b/test/binary/non-fhs-0-0.x86_64.rpm diff --git a/test/binary/non-position-independent-exec-1.0-0.x86_64.rpm b/test/binary/non-position-independent-exec-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..aec15fe --- /dev/null +++ b/test/binary/non-position-independent-exec-1.0-0.x86_64.rpm diff --git a/test/binary/non-standard-group-0-2.1.x86_64.rpm b/test/binary/non-standard-group-0-2.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..06d307a --- /dev/null +++ b/test/binary/non-standard-group-0-2.1.x86_64.rpm diff --git a/test/binary/not-standard-release-extension-0-1.1.x86_64.rpm b/test/binary/not-standard-release-extension-0-1.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..93debfe --- /dev/null +++ b/test/binary/not-standard-release-extension-0-1.1.x86_64.rpm diff --git a/test/binary/only-non-binary-in-usr-lib-1.0-0.x86_64.rpm b/test/binary/only-non-binary-in-usr-lib-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..217fa6c --- /dev/null +++ b/test/binary/only-non-binary-in-usr-lib-1.0-0.x86_64.rpm diff --git a/test/binary/only-non-binary-in-usr-lib_exception-1.0-0.x86_64.rpm b/test/binary/only-non-binary-in-usr-lib_exception-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..8a93986 --- /dev/null +++ b/test/binary/only-non-binary-in-usr-lib_exception-1.0-0.x86_64.rpm diff --git a/test/binary/outside-nodejs010-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm b/test/binary/outside-nodejs010-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm Binary files differdeleted file mode 100644 index aa907bb..0000000 --- a/test/binary/outside-nodejs010-nodejs-oauth-sign-0.2.0-2.el6_4.noarch.rpm +++ /dev/null diff --git a/test/binary/pam-module-1.0-0.x86_64.rpm b/test/binary/pam-module-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..c5cf191 --- /dev/null +++ b/test/binary/pam-module-1.0-0.x86_64.rpm diff --git a/test/binary/pc-0-0.x86_64.rpm b/test/binary/pc-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..ef7c71a --- /dev/null +++ b/test/binary/pc-0-0.x86_64.rpm diff --git a/test/binary/python3-greenlet-0.4.15-1.5.x86_64.rpm b/test/binary/python3-greenlet-0.4.15-1.5.x86_64.rpm Binary files differnew file mode 100644 index 0000000..b6a109e --- /dev/null +++ b/test/binary/python3-greenlet-0.4.15-1.5.x86_64.rpm diff --git a/test/binary/python39-evtx-0.7.4-29.1.noarch.rpm b/test/binary/python39-evtx-0.7.4-29.1.noarch.rpm Binary files differnew file mode 100644 index 0000000..d0e4b3a --- /dev/null +++ b/test/binary/python39-evtx-0.7.4-29.1.noarch.rpm diff --git a/test/binary/random-devel-0-2.1.x86_64.rpm b/test/binary/random-devel-0-2.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..2479879 --- /dev/null +++ b/test/binary/random-devel-0-2.1.x86_64.rpm diff --git a/test/binary/random-exp-0-0.x86_64.rpm b/test/binary/random-exp-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..69f8f7c --- /dev/null +++ b/test/binary/random-exp-0-0.x86_64.rpm diff --git a/test/binary/rc-links-1.0-0.x86_64.rpm b/test/binary/rc-links-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..d805ed4 --- /dev/null +++ b/test/binary/rc-links-1.0-0.x86_64.rpm diff --git a/test/binary/requires-on-release-0-1.1.x86_64.rpm b/test/binary/requires-on-release-0-1.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..0a75e1d --- /dev/null +++ b/test/binary/requires-on-release-0-1.1.x86_64.rpm diff --git a/test/binary/ruby2.5-rubygem-rubyzip-testsuite-1.2.1-0.x86_64.rpm b/test/binary/ruby2.5-rubygem-rubyzip-testsuite-1.2.1-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..a5a212f --- /dev/null +++ b/test/binary/ruby2.5-rubygem-rubyzip-testsuite-1.2.1-0.x86_64.rpm diff --git a/test/binary/ruby2.6-rubygem-fast_gettext-2.0.1-1.1.x86_64.rpm b/test/binary/ruby2.6-rubygem-fast_gettext-2.0.1-1.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..1121c05 --- /dev/null +++ b/test/binary/ruby2.6-rubygem-fast_gettext-2.0.1-1.1.x86_64.rpm diff --git a/test/binary/rust-0-0.x86_64.rpm b/test/binary/rust-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..53e6821 --- /dev/null +++ b/test/binary/rust-0-0.x86_64.rpm diff --git a/test/binary/self-0-0.x86_64.rpm b/test/binary/self-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..048c155 --- /dev/null +++ b/test/binary/self-0-0.x86_64.rpm diff --git a/test/binary/shlib1-0-0.x86_64.rpm b/test/binary/shlib1-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..e00a64b --- /dev/null +++ b/test/binary/shlib1-0-0.x86_64.rpm diff --git a/test/binary/shlib2-devel-0-0.x86_64.rpm b/test/binary/shlib2-devel-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..17b3857 --- /dev/null +++ b/test/binary/shlib2-devel-0-0.x86_64.rpm diff --git a/test/binary/spellingerrors-default-0-0.noarch.rpm b/test/binary/spellingerrors-default-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..0b5d648 --- /dev/null +++ b/test/binary/spellingerrors-default-0-0.noarch.rpm diff --git a/test/binary/spellingerrors-lang-0-0.noarch.rpm b/test/binary/spellingerrors-lang-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..59b7d84 --- /dev/null +++ b/test/binary/spellingerrors-lang-0-0.noarch.rpm diff --git a/test/binary/spellingerrors-lang2-0-0.noarch.rpm b/test/binary/spellingerrors-lang2-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..698be62 --- /dev/null +++ b/test/binary/spellingerrors-lang2-0-0.noarch.rpm diff --git a/test/binary/spellingerrors-lang3-0-0.noarch.rpm b/test/binary/spellingerrors-lang3-0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..d7bd3d3 --- /dev/null +++ b/test/binary/spellingerrors-lang3-0-0.noarch.rpm diff --git a/test/binary/statically-linked-binary-1.0-0.x86_64.rpm b/test/binary/statically-linked-binary-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..d219909 --- /dev/null +++ b/test/binary/statically-linked-binary-1.0-0.x86_64.rpm diff --git a/test/binary/summary-on-multiple-lines-1.0-0.x86_64.rpm b/test/binary/summary-on-multiple-lines-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..c59e350 --- /dev/null +++ b/test/binary/summary-on-multiple-lines-1.0-0.x86_64.rpm diff --git a/test/binary/summary-warning-0-1.1.x86_64.rpm b/test/binary/summary-warning-0-1.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..b4b72f1 --- /dev/null +++ b/test/binary/summary-warning-0-1.1.x86_64.rpm diff --git a/test/binary/systemd-tmpfiles-1.0-0.x86_64.rpm b/test/binary/systemd-tmpfiles-1.0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..53b9953 --- /dev/null +++ b/test/binary/systemd-tmpfiles-1.0-0.x86_64.rpm diff --git a/test/binary/systemd-tmpfiles_correct-1.0-2.1.x86_64.rpm b/test/binary/systemd-tmpfiles_correct-1.0-2.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..159cd9f --- /dev/null +++ b/test/binary/systemd-tmpfiles_correct-1.0-2.1.x86_64.rpm diff --git a/test/binary/tasque-0.1.12-12.7.x86_64.rpm b/test/binary/tasque-0.1.12-12.7.x86_64.rpm Binary files differnew file mode 100644 index 0000000..8bdd894 --- /dev/null +++ b/test/binary/tasque-0.1.12-12.7.x86_64.rpm diff --git a/test/binary/tclpackage-0-0.x86_64.rpm b/test/binary/tclpackage-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..4c29847 --- /dev/null +++ b/test/binary/tclpackage-0-0.x86_64.rpm diff --git a/test/binary/tempfiled-0-0.x86_64.rpm b/test/binary/tempfiled-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..ade9db6 --- /dev/null +++ b/test/binary/tempfiled-0-0.x86_64.rpm diff --git a/test/binary/testdocumentation-0-0.x86_64.rpm b/test/binary/testdocumentation-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..41cf893 --- /dev/null +++ b/test/binary/testdocumentation-0-0.x86_64.rpm diff --git a/test/binary/texlive-codepage-doc-2018.151.svn21126-38.1.noarch.rpm b/test/binary/texlive-codepage-doc-2018.151.svn21126-38.1.noarch.rpm Binary files differnew file mode 100644 index 0000000..af6bef7 --- /dev/null +++ b/test/binary/texlive-codepage-doc-2018.151.svn21126-38.1.noarch.rpm diff --git a/test/binary/unexpanded-macro-exp-0-4.1.x86_64.rpm b/test/binary/unexpanded-macro-exp-0-4.1.x86_64.rpm Binary files differnew file mode 100644 index 0000000..74012a4 --- /dev/null +++ b/test/binary/unexpanded-macro-exp-0-4.1.x86_64.rpm diff --git a/test/binary/unexpanded-macro-files-0.1-1.noarch.rpm b/test/binary/unexpanded-macro-files-0.1-1.noarch.rpm Binary files differnew file mode 100644 index 0000000..66d7564 --- /dev/null +++ b/test/binary/unexpanded-macro-files-0.1-1.noarch.rpm diff --git a/test/binary/unknown-key-1.0-0.noarch.rpm b/test/binary/unknown-key-1.0-0.noarch.rpm Binary files differnew file mode 100644 index 0000000..8591fb5 --- /dev/null +++ b/test/binary/unknown-key-1.0-0.noarch.rpm diff --git a/test/binary/valid-exception-0-0.x86_64.rpm b/test/binary/valid-exception-0-0.x86_64.rpm Binary files differnew file mode 100644 index 0000000..7887e61 --- /dev/null +++ b/test/binary/valid-exception-0-0.x86_64.rpm diff --git a/test/binary/xrootd-devel-5.5.4-1.fc37.x86_64.rpm b/test/binary/xrootd-devel-5.5.4-1.fc37.x86_64.rpm Binary files differnew file mode 100644 index 0000000..aa5533f --- /dev/null +++ b/test/binary/xrootd-devel-5.5.4-1.fc37.x86_64.rpm diff --git a/test/binary/xtables-addons-kmp-default-2.14_k4.12.14_lp151.16-lp151.3.10.x86_64.rpm b/test/binary/xtables-addons-kmp-default-2.14_k4.12.14_lp151.16-lp151.3.10.x86_64.rpm Binary files differnew file mode 100644 index 0000000..75d82d6 --- /dev/null +++ b/test/binary/xtables-addons-kmp-default-2.14_k4.12.14_lp151.16-lp151.3.10.x86_64.rpm diff --git a/test/configs/broken.config b/test/configs/broken.config new file mode 100644 index 0000000..3722ae8 --- /dev/null +++ b/test/configs/broken.config @@ -0,0 +1,2 @@ +Distribution = [ "Fedora Project" +Vendor = Fedora Project diff --git a/test/configs/descriptions.config b/test/configs/descriptions.config new file mode 100644 index 0000000..ab023d1 --- /dev/null +++ b/test/configs/descriptions.config @@ -0,0 +1,16 @@ +[Descriptions] +no-binary = """ +A new text for no-binary error. +""" + +no-soname = """ +A new text for no-soname error. +""" + +non-standard-dir-in-usr = """ +A new text for non-standard-dir-in-usr error. +""" + +non-standard-dir-in-var = """ +A new text for non-standard-dir-in-var error. +""" diff --git a/test/configs/test.config b/test/configs/test.config new file mode 100644 index 0000000..7a44d03 --- /dev/null +++ b/test/configs/test.config @@ -0,0 +1,25 @@ +Distribution = "Fedora Project" +Vendor = "Fedora Project" + +MandatoryOptflags = ['-fno-PIE', '-g', '-Ofast'] +ForbiddenOptflags = ['-frounding-math'] + +[WarnOnFunction.crypto-policy-non-compliance-openssl] +f_name = "SSL_CTX_set_cipher_list" +description = '''This application package calls a function to explicitly set crypto ciphers + for SSL/TLS. That may cause the application not to use the system-wide set + cryptographic policy and should be modified in accordance to: + https://fedoraproject.org/wiki/Packaging:CryptoPolicies''' +[WarnOnFunction.crypto-policy-non-compliance-gnutls-1] +f_name = "gnutls_priority_set_direct" +description = '''This application package calls a function to explicitly set crypto ciphers + for SSL/TLS. That may cause the application not to use the system-wide set + cryptographic policy and should be modified in accordance to: + https://fedoraproject.org/wiki/Packaging:CryptoPolicies''' +[WarnOnFunction.crypto-policy-non-compliance-gnutls-2] +f_name = "gnutls_priority_init" +good_param = "SYSLOG" +description = '''This application package calls a function to explicitly set crypto ciphers + for SSL/TLS. That may cause the application not to use the system-wide set + cryptographic policy and should be modified in accordance to: + https://fedoraproject.org/wiki/Packaging:CryptoPolicies''' diff --git a/test/configs/test.override.config b/test/configs/test.override.config new file mode 100644 index 0000000..de84100 --- /dev/null +++ b/test/configs/test.override.config @@ -0,0 +1,3 @@ +ValidGroups = [ + 'Only/One', +] diff --git a/test/configs/test2.config b/test/configs/test2.config new file mode 100644 index 0000000..d906605 --- /dev/null +++ b/test/configs/test2.config @@ -0,0 +1,11 @@ +Vendor = "SUSE" +ExtraMenuNeeds = [ + 'windows', +] + +[WarnOnFunction.crypto-policy-non-compliance-openssl] +f_name = "REPLACED" +good_param = "ADDED" + +[WarnOnFunction.crypto-policy-3] +f_name = "new_blobie" diff --git a/test/configs/testfilters.config b/test/configs/testfilters.config new file mode 100644 index 0000000..6326bae --- /dev/null +++ b/test/configs/testfilters.config @@ -0,0 +1,22 @@ +Filters = [ + '.*invalid-buildhost.*', + '.*executable-in-library-package.*', + '.*non-versioned-file-in-library-package.*', + '.*shlib-policy-name-error.*', + # this is just a comment to be ignored by the parsing + '.*hardcoded-path-in-buildroot-tag.*', + '.*no-buildroot-tag.*', + '.*cross-directory-hard-link.*', + 'no-regex', + ' no-regex-with-leading-space', + 'ngircd.*: E: bad-error', + '^ngircd.*: E: test-color-error details of the error$', + 'fatal-error' +] + +BlockedFilters = [ + 'fatal-error' +] + +[Scoring] +test-color-error = 12345 diff --git a/test/configs/testing-rpmlintrc b/test/configs/testing-rpmlintrc new file mode 100644 index 0000000..749e01f --- /dev/null +++ b/test/configs/testing-rpmlintrc @@ -0,0 +1,132 @@ +# This line is mandatory to access the configuration functions +from Config import * +setBadness('suse-dbus-unauthorized-service', 0) + setBadness('suse-other-error','20') +setBadness ('suse-other-error-123','200') +# # Output filters +addFilter(r"arch-independent-package-contains-binary-or-object ") +addFilter('.*arch-independent-package-contains-binary-or-object.*/boot/vc/.*.elf') +addFilter("class-path-in-manifest") +addFilter("deprecated-grep") +addFilter(".*desktopfile-without-binary.*") +addFilter("desktopfile-without-binary") +addFilter('devel-dependency') +addFilter('devel-dependency python-devel') +addFilter('devel-dependency python2-devel') +# These are binaries not executed on the CPU but on the graphics card +addFilter('devel-dependency python3-devel') +addFilter("devel-file-in-non-devel-package") +addFilter("devel-file-in-non-devel-package .*/clang/.*/include/.*") +addFilter("devel-file-in-non-devel-package .*/clang/.*/lib/.*") +addFilter('devel-file-in-non-devel-package .*/commands/src/compiler.c') +addFilter('devel-file-in-non-devel-package .*/Cython/.*') +addFilter("devel-file-in-non-devel-package .*/lib.*/*.a") +addFilter("devel-file-in-non-devel-package .*/lib.*/*.so") +addFilter("devel-file-in-non-devel-package .*/site-packages/cffi/_cffi_include.h") +addFilter("devel-file-in-non-devel-package .*/site-packages/cffi/_embedding.h") +addFilter("devel-file-in-non-devel-package .*/site-packages/cffi/parse_c_type.h") +addFilter('devel-file-in-non-devel-package .*/tests/.*') +addFilter(".*no-manual-page-for-binary.*") +addFilter("devel-file-in-non-devel-package .*/usr/include/.*") +addFilter("devel-file-in-non-devel-package .* /usr/lib.*/liblftp-.*.so") +addFilter('devel-file-in-non-devel-package .*/wcs/.*') +addFilter('doc-file-dependency') +addFilter("doc-file-dependency .*") +addFilter("doc-file-dependency .*\.py ") +addFilter("doc-file-dependency .*\.py ") +addFilter("E: devel-file-in-non-devel-package .*site-packages.*fake_libc_include.*") +addFilter("E: devel-file-in-non-devel-package") +addFilter("E: shlib-policy-name-error") +addFilter("executable-stack") +addFilter("explicit-lib-dependency") +addFilter("explicit-lib-dependency libgobject-2_0-0") +addFilter("explicit-lib-dependency .*libmpv1") +addFilter("explicit-lib-dependency libqt5_sql_backend") +addFilter("explicit-lib-dependency .*-tblib") +addFilter("file-contains-date-and-time") +addFilter("files-duplicate.*/usr/share/doc/packages") +addFilter("hidden-file-or-dir") +addFilter("hidden-file-or-dir /usr/lib/python.*/site-packages/tldextract/.tld_set_snapshot") +addFilter("incoherent-init-script-name quasselcore") +addFilter("incoherent-init-script-name raw") +addFilter("incoherent-logrotate-file /etc/logrotate.d/quasselcore") +addFilter("incorrect-fsf-address") +addFilter("init-script-without-%restart_on_update-postun /etc/init.d/raw") +addFilter("init-script-without-%stop_on_removal-preun /etc/init.d/raw") +addFilter("libguilereadline.* devel-file-in-non-devel-package") +addFilter("incoherent-init-script-name quasselcore") +addFilter("libvlccore.* shared-lib-calls-exit") +addFilter("missing-PT_GNU_STACK-section") +addFilter("name-repeated-in-summary") +addFilter("net-snmp-devel.* files-duplicate.*man.*") +addFilter("net-snmp.*incoherent-init-script-name") +addFilter("no-binary") +addFilter("no-dependency-on libffmpeg.*") +addFilter(".*no-manual-page-for-binary.*") +# /usr/local/bin/dib-python interpreter is only used inside +# the generated image so it's not something that affects the +# host or the package itself. +addFilter("no-manual-page-for-binary") +addFilter(".* no-manual-page-for-binary mitm.*") +addFilter(".* no-manual-page-for-binary path.*") +addFilter("no-manual-page-for-binary rst.*") +addFilter("no-manual-page-for-binary .*subunit.*") +addFilter("non-conffile-in-etc /etc/rpm/macros.python3") +addFilter("non-conffile-in-etc /etc/sysconfig/SuSEfirewall2.d/services/quassel") +addFilter("non-etc-or-var-file-marked-as-conffile") +addFilter('non-executable-script') +addFilter("non-executable-script") +addFilter("non-executable-script.*/usr/lib/python.*/site-packages/ansible/(cli|galaxy|module_utils|plugins/action|runner|utils)/.*.py"); +addFilter("non-executable-script.*/usr/lib/python.*/site-packages/ansible/modules/.*"); +addFilter(".*non-executable-script.*/usr/lib/python2.7/site-packages/diskimage_builder/lib/.*") +addFilter("non-standard-uid /var/lib/quasselcore quasslecore") +addFilter("non-standard-uid /var/log/quassel quasslecore") +addFilter("no-reload-entry /etc/init.d/raw") +addFilter(".*obsolete-not-provided.*") +addFilter("pem-certificate") +addFilter("pem-certificate .*/site-packages/distributed/tests/.*\.pem") +addFilter("perl.* devel-file-in-non-devel-package") +addFilter("perl-SNMP.* zero-length.*\.bs") +addFilter("python-naming-policy-not-applied") +addFilter("pyzmq-devel.*: W: no-dependency-on python\(abi\)") +addFilter('ratbagd.* suse-dbus-unauthorized-service.*') +addFilter("script-without-shebang") +addFilter("shlib-fixed-dependency") +addFilter("strict-aliasing-punning") +addFilter("unstripped-binary-or-object") +addFilter ("W: devel-file-in-non-devel-package") +addFilter("W: files-attr-not-set") +addFilter(".*W:.*files-duplicate.*/pam/su.*/pam.d/su-l.*") +addFilter("W: files-duplicate /usr/share/x11vnc/classes/index.vnc /usr/share/x11vnc/classes/ssl/index.vnc") +addFilter("W: hidden-file-or-dir /usr/lib/python3.*/site-packages/openlp/.version") +addFilter("W: no-binary"); +addFilter("W: obsolete-suse-version-check.*") +addFilter("W: patch-not-applied Patch5: x11vnc-thread-auth.diff") +addFilter(".*W:.*permissions-symlink.*/bin/mount.*") +addFilter(".*W:.*permissions-symlink.*/bin/su.*") +addFilter(".*W:.*permissions-symlink.*/bin/umount.*") +addFilter("W: python-naming-policy-not-applied .*") +addFilter("W: python-naming-policy-not-applied") +addFilter("wrong-file-end-of-line-encoding") +addFilter("wrong-file-end-of-line-encoding /usr/share/doc/packages/antlr-manual/examples/") +addFilter(".*wrong-script-interpreter.*/usr/lib/python2.7/site-packages/diskimage_builder/elements/.*") +addFilter("W: shlib-policy-missing-suffix") +addFilter("W: suse-branding-unversioned-requires") +addFilter(".*ZEO/tests/.*pem$") +addFilter("zero-length /usr/lib64/python3.3/test/namespace_pkgs/module_and_namespace_package/a_test/empty") +addFilter("zero-length /usr/lib64/python3.3/test/nullcert.pem") +addFilter("zero-length /usr/share/doc/packages/antlr-manual/examples/csharp/csharp_v1/testfiles/Empty.cs") +addFilter("zero-length /usr/share/doc/packages/python-alembic-doc/html/_static/site_custom_css.css") +addFilter("zlib-devel.*: W: no-dependency-on zlib*/zlib-libs/libzlib") +addFilter("zlib.src.*: W: make-check-outside-check-section time make check") +# These are binaries not executed on the CPU but on the graphics card +addFilter('.*arch-independent-package-contains-binary-or-object.*/boot/vc/.*.elf') +# These are binaries not executed on the CPU but on the graphics card +addFilter('.*arch-independent-package-contains-binary-or-object.*/boot/vc/.*.elf') + +import os +import time + +print(time.time()) +print(os.geteuid()) +print("I can do anyting as the above user") diff --git a/test/configs/testing2-rpmlintrc b/test/configs/testing2-rpmlintrc new file mode 100644 index 0000000..3da1821 --- /dev/null +++ b/test/configs/testing2-rpmlintrc @@ -0,0 +1,4 @@ +addFilter('I am not used') +addFilter('He is not used') +# addFilter('She is not used') +addFilter('no-%build-section') diff --git a/test/configs/testing3-rpmlintrc b/test/configs/testing3-rpmlintrc new file mode 100644 index 0000000..45caa0a --- /dev/null +++ b/test/configs/testing3-rpmlintrc @@ -0,0 +1,5 @@ +# Backward compatibility should match spaces within paren +addFilter('no-spaces-in-paren') +addFilter( 'has-spaces-in-paren' ) +addFilter( 'multiple-spaces-in-paren' ) +addFilter("doublequotes-instead-of-singlequotes")
\ No newline at end of file diff --git a/test/configs/testlists1.config b/test/configs/testlists1.config new file mode 100644 index 0000000..24b4347 --- /dev/null +++ b/test/configs/testlists1.config @@ -0,0 +1,7 @@ +Filters = [ + '.*invalid-buildhost.*', +] +ValidGroups = [ + 'bullshitgroup', + 'group2/wrong', +] diff --git a/test/configs/testlists2.config b/test/configs/testlists2.config new file mode 100644 index 0000000..2f66781 --- /dev/null +++ b/test/configs/testlists2.config @@ -0,0 +1,6 @@ +Filters = [ + '.*cross-directory-hard-link.*', +] +ValidGroups = [ + 'System/Libraries', +] diff --git a/test/docker-script.sh b/test/docker-script.sh deleted file mode 100755 index 93c1856..0000000 --- a/test/docker-script.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -ex - -: ${PYTHON:=python} -export PYTHON - -: ${PYTHONWARNINGS:=all} -export PYTHONWARNINGS - -make check PYTHON=$PYTHON - -make install PYTHON=$PYTHON DESTDIR=$(mktemp -d) diff --git a/test/dump_stats.py b/test/dump_stats.py new file mode 100755 index 0000000..eb5ecac --- /dev/null +++ b/test/dump_stats.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +if __name__ == '__main__': + import sys + import pstats + + p = pstats.Stats(sys.argv[1]) + N = 60 + p.sort_stats('cumulative').print_stats(N) + print('========================================================') + p.sort_stats('tottime').print_stats(N) + print('========================================================') + p.sort_stats('ncalls').print_stats(N) diff --git a/test/files/python-flit-metadata.txt b/test/files/python-flit-metadata.txt new file mode 100644 index 0000000..62da13d --- /dev/null +++ b/test/files/python-flit-metadata.txt @@ -0,0 +1,110 @@ +Metadata-Version: 2.1 +Name: flit +Version: 3.8.0 +Summary: A simple packaging tool for simple packages. +Author-email: Thomas Kluyver <thomas@kluyver.me.uk> +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Dist: flit_core >=3.8.0 +Requires-Dist: requests +Requires-Dist: docutils +Requires-Dist: tomli-w +Requires-Dist: sphinx ; extra == "doc" +Requires-Dist: sphinxcontrib_github_alt ; extra == "doc" +Requires-Dist: pygments-github-lexers ; extra == "doc" +Requires-Dist: testpath ; extra == "test" +Requires-Dist: responses ; extra == "test" +Requires-Dist: pytest>=2.7.3 ; extra == "test" +Requires-Dist: pytest-cov ; extra == "test" +Requires-Dist: tomli ; extra == "test" +Project-URL: Changelog, https://flit.pypa.io/en/stable/history.html +Project-URL: Documentation, https://flit.pypa.io +Project-URL: Source, https://github.com/pypa/flit +Provides-Extra: doc +Provides-Extra: test + +**Flit** is a simple way to put Python packages and modules on PyPI. +It tries to require less thought about packaging and help you avoid common +mistakes. +See `Why use Flit? <https://flit.readthedocs.io/en/latest/rationale.html>`_ for +more about how it compares to other Python packaging tools. + +Install +------- + +:: + + $ python3 -m pip install flit + +Flit requires Python 3 and therefore needs to be installed using the Python 3 +version of pip. + +Python 2 modules can be distributed using Flit, but need to be importable on +Python 3 without errors. + +Usage +----- + +Say you're writing a module ``foobar`` — either as a single file ``foobar.py``, +or as a directory — and you want to distribute it. + +1. Make sure that foobar's docstring starts with a one-line summary of what + the module is, and that it has a ``__version__``: + + .. code-block:: python + + """An amazing sample package!""" + + __version__ = "0.1" + +2. Install flit if you don't already have it:: + + python3 -m pip install flit + +3. Run ``flit init`` in the directory containing the module to create a + ``pyproject.toml`` file. It will look something like this: + + .. code-block:: ini + + [build-system] + requires = ["flit_core >=3.2,<4"] + build-backend = "flit_core.buildapi" + + [project] + name = "foobar" + authors = [{name = "Sir Robin", email = "robin@camelot.uk"}] + dynamic = ["version", "description"] + + [project.urls] + Home = "https://github.com/sirrobin/foobar" + + You can edit this file to add other metadata, for example to set up + command line scripts. See the + `pyproject.toml page <https://flit.readthedocs.io/en/latest/pyproject_toml.html#scripts-section>`_ + of the documentation. + + If you have already got a ``flit.ini`` file to use with older versions of + Flit, convert it to ``pyproject.toml`` by running ``python3 -m flit.tomlify``. + +4. Run this command to upload your code to PyPI:: + + flit publish + +Once your package is published, people can install it using *pip* just like +any other package. In most cases, pip will download a 'wheel' package, a +standard format it knows how to install. If you specifically ask pip to install +an 'sdist' package, it will install and use Flit in a temporary environment. + + +To install a package locally for development, run:: + + flit install [--symlink] [--python path/to/python] + +Flit packages a single importable module or package at a time, using the import +name as the name on PyPI. All subpackages and data files within a package are +included automatically. + diff --git a/test/files/python-jupyter-events-metadata.txt b/test/files/python-jupyter-events-metadata.txt new file mode 100644 index 0000000..2b7cf37 --- /dev/null +++ b/test/files/python-jupyter-events-metadata.txt @@ -0,0 +1,128 @@ +Metadata-Version: 2.1 +Name: jupyter-events +Version: 0.6.3 +Summary: Jupyter Event System library +Project-URL: Homepage, http://jupyter.org +Author-email: Jupyter Development Team <jupyter@googlegroups.com> +License: # Licensing terms + + This project is licensed under the terms of the Modified BSD License + (also known as New or Revised or 3-Clause BSD), as follows: + + - Copyright (c) 2022-, Jupyter Development Team + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + Neither the name of the Jupyter Development Team nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ## About the Jupyter Development Team + + The Jupyter Development Team is the set of all contributors to the Jupyter project. + This includes all of the Jupyter subprojects. + + The core team that coordinates development on GitHub can be found here: + https://github.com/jupyter/. + + ## Our Copyright Policy + + Jupyter uses a shared copyright model. Each contributor maintains copyright + over their contributions to Jupyter. But, it is important to note that these + contributions are typically only changes to the repositories. Thus, the Jupyter + source code, in its entirety is not the copyright of any single person or + institution. Instead, it is the collective copyright of the entire Jupyter + Development Team. If individual contributors want to maintain a record of what + changes/contributions they have specific copyright on, they should indicate + their copyright in the commit message of the change, when they commit the + change to one of the Jupyter repositories. + + With this in mind, the following banner should be used in any source code file + to indicate the copyright and license terms: + + ``` + # Copyright (c) Jupyter Development Team. + # Distributed under the terms of the Modified BSD License. + ``` +License-File: COPYING.md +Keywords: Jupyter,JupyterLab +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Science/Research +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Requires-Python: >=3.7 +Requires-Dist: jsonschema[format-nongpl]>=3.2.0 +Requires-Dist: python-json-logger>=2.0.4 +Requires-Dist: pyyaml>=5.3 +Requires-Dist: rfc3339-validator +Requires-Dist: rfc3986-validator>=0.1.1 +Requires-Dist: traitlets>=5.3 +Provides-Extra: cli +Requires-Dist: click; extra == 'cli' +Requires-Dist: rich; extra == 'cli' +Provides-Extra: docs +Requires-Dist: jupyterlite-sphinx; extra == 'docs' +Requires-Dist: myst-parser; extra == 'docs' +Requires-Dist: pydata-sphinx-theme; extra == 'docs' +Requires-Dist: sphinxcontrib-spelling; extra == 'docs' +Provides-Extra: test +Requires-Dist: click; extra == 'test' +Requires-Dist: coverage; extra == 'test' +Requires-Dist: pre-commit; extra == 'test' +Requires-Dist: pytest-asyncio>=0.19.0; extra == 'test' +Requires-Dist: pytest-console-scripts; extra == 'test' +Requires-Dist: pytest-cov; extra == 'test' +Requires-Dist: pytest>=7.0; extra == 'test' +Requires-Dist: rich; extra == 'test' +Description-Content-Type: text/markdown + +# Jupyter Events + +[![Build Status](https://github.com/jupyter/jupyter_events/actions/workflows/python-tests.yml/badge.svg?query=branch%3Amain++)](https://github.com/jupyter/jupyter_events/actions/workflows/python-tests.yml/badge.svg?query=branch%3Amain++) +[![codecov](https://codecov.io/gh/jupyter/jupyter_events/branch/main/graph/badge.svg?token=S9WiBg2iL0)](https://codecov.io/gh/jupyter/jupyter_events) +[![Documentation Status](https://readthedocs.org/projects/jupyter-events/badge/?version=latest)](http://jupyter-events.readthedocs.io/en/latest/?badge=latest) + +_An event system for Jupyter Applications and extensions._ + +Jupyter Events enables Jupyter Python Applications (e.g. Jupyter Server, JupyterLab Server, JupyterHub, etc.) to emit **events**—structured data describing things happening inside the application. Other software (e.g. client applications like JupyterLab) can _listen_ and respond to these events. + +## Install + +Install Jupyter Events directly from PyPI: + +``` +pip install jupyter_events +``` + +or conda-forge: + +``` +conda install -c conda-forge jupyter_events +``` + +## Documentation + +Documentation is available at [jupyter-events.readthedocs.io](https://jupyter-events.readthedocs.io). diff --git a/test/files/python-jupyter_server_fileid-metadata.txt b/test/files/python-jupyter_server_fileid-metadata.txt new file mode 100644 index 0000000..d46b015 --- /dev/null +++ b/test/files/python-jupyter_server_fileid-metadata.txt @@ -0,0 +1,146 @@ +Metadata-Version: 2.1 +Name: jupyter_server_fileid +Version: 0.9.0 +Project-URL: Home, https://github.com/jupyter-server/jupyter_server_fileid +Author-email: "David L. Qiu" <david@qiu.dev> +License: BSD 3-Clause License + + Copyright (c) 2022, David L. Qiu + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +License-File: LICENSE +Keywords: Extension,Jupyter +Classifier: Framework :: Jupyter +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Requires-Python: >=3.7 +Requires-Dist: jupyter-events>=0.5.0 +Requires-Dist: jupyter-server<3,>=1.15 +Provides-Extra: cli +Requires-Dist: click; extra == 'cli' +Provides-Extra: test +Requires-Dist: jupyter-server[test]<3,>=1.15; extra == 'test' +Requires-Dist: pytest; extra == 'test' +Requires-Dist: pytest-cov; extra == 'test' +Description-Content-Type: text/markdown + +# jupyter_server_fileid + +[![Github Actions Status](https://github.com/jupyter-server/jupyter_server_fileid/workflows/Build/badge.svg)](https://github.com/jupyter-server/jupyter_server_fileid/actions/workflows/build.yml) + +A Jupyter Server extension providing an implementation of the File ID service. + +## Requirements + +- Jupyter Server + +## Install + +To install the extension, execute: + +```bash +pip install jupyter_server_fileid +``` + +## Uninstall + +To remove the extension, execute: + +```bash +pip uninstall jupyter_server_fileid +``` + +## Troubleshoot + +If you are seeing the frontend extension, but it is not working, check +that the server extension is enabled: + +```bash +jupyter server extension list +``` + +## Contributing + +### Development install + +```bash +# Clone the repo to your local environment +# Change directory to the jupyter_server_fileid directory +# Install package in development mode - will automatically enable +# The server extension. +pip install -e . +``` + + +You can watch the source directory and run your Jupyter Server-based application at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. For example, +when running JupyterLab: + +```bash +jupyter lab --autoreload +``` + +If your extension does not depend a particular frontend, you can run the +server directly: + +```bash +jupyter server --autoreload +``` + +### Running Tests + +Install dependencies: + +```bash +pip install -e ".[test]" +``` + +To run the python tests, use: + +```bash +pytest + +# To test a specific file +pytest jupyter_server_fileid/tests/test_handlers.py + +# To run a specific test +pytest jupyter_server_fileid/tests/test_handlers.py -k "test_get" +``` + +### Development uninstall + +```bash +pip uninstall jupyter_server_fileid +``` + +### Packaging the extension + +See [RELEASE](RELEASE.md) diff --git a/test/files/python-scikit_build-metadata.txt b/test/files/python-scikit_build-metadata.txt new file mode 100644 index 0000000..722971e --- /dev/null +++ b/test/files/python-scikit_build-metadata.txt @@ -0,0 +1,242 @@ +Metadata-Version: 2.1 +Name: scikit-build +Version: 0.17.2 +Summary: Improved build system generator for Python C/C++/Fortran/Cython extensions +Project-URL: Bug Tracker, https://github.com/scikit-build/scikit-build/issues +Project-URL: Changelog, https://scikit-build.readthedocs.io/en/latest/changes.html +Project-URL: Discussions, https://github.com/orgs/scikit-build/discussions +Project-URL: Documentation, https://scikit-build.readthedocs.io/ +Project-URL: Examples, https://github.com/scikit-build/scikit-build-sample-projects +Project-URL: Homepage, https://github.com/scikit-build/scikit-build +Author: The scikit-build team +License-Expression: MIT +License-File: AUTHORS.rst +License-File: LICENSE +Keywords: scikit-build +Classifier: Development Status :: 2 - Pre-Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Natural Language :: English +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Typing :: Typed +Requires-Python: >=3.7 +Requires-Dist: distro +Requires-Dist: packaging +Requires-Dist: setuptools>=42.0.0 +Requires-Dist: tomli; python_version < '3.11' +Requires-Dist: typing-extensions>=3.7; python_version < '3.8' +Requires-Dist: wheel>=0.32.0 +Provides-Extra: cov +Requires-Dist: coverage[toml]>=4.2; extra == 'cov' +Requires-Dist: pytest-cov>=2.7.1; extra == 'cov' +Provides-Extra: docs +Requires-Dist: pygments; extra == 'docs' +Requires-Dist: sphinx-issues; extra == 'docs' +Requires-Dist: sphinx-rtd-theme>=1.0; extra == 'docs' +Requires-Dist: sphinx>=4; extra == 'docs' +Requires-Dist: sphinxcontrib-moderncmakedomain>=3.19; extra == 'docs' +Provides-Extra: doctest +Requires-Dist: ubelt>=0.8.2; extra == 'doctest' +Requires-Dist: xdoctest>=0.10.0; extra == 'doctest' +Provides-Extra: test +Requires-Dist: build>=0.7; extra == 'test' +Requires-Dist: cython>=0.25.1; extra == 'test' +Requires-Dist: importlib-metadata; python_version < '3.8' and extra == 'test' +Requires-Dist: pytest-mock>=1.10.4; extra == 'test' +Requires-Dist: pytest-virtualenv>=1.2.5; extra == 'test' +Requires-Dist: pytest>=6.0.0; extra == 'test' +Requires-Dist: requests; extra == 'test' +Requires-Dist: virtualenv; extra == 'test' +Description-Content-Type: text/x-rst + +=============================== +scikit-build +=============================== + +.. image:: https://github.com/scikit-build/scikit-build/actions/workflows/ci.yml/badge.svg + :target: https://github.com/scikit-build/scikit-build/actions/workflows/ci.yml + +.. image:: https://dev.azure.com/scikit-build/scikit-build/_apis/build/status/scikit-build.scikit-build?branchName=main + :target: https://dev.azure.com/scikit-build/scikit-build/_build/latest?definitionId=1&branchName=main + +.. image:: https://codecov.io/gh/scikit-build/scikit-build/branch/main/graph/badge.svg + :target: https://codecov.io/gh/scikit-build/scikit-build + :alt: Code coverage status + +.. image:: https://img.shields.io/static/v1?label=Discussions&message=Ask&color=blue&logo=github + :target: https://github.com/orgs/scikit-build/discussions + :alt: GitHub Discussion + +Improved build system generator for CPython C/C++/Fortran/Cython extensions. + +Better support is available for additional compilers, build systems, cross +compilation, and locating dependencies and determining their build +requirements. + +The **scikit-build** package is fundamentally just glue between +the ``setuptools`` Python module and `CMake <https://cmake.org/>`_. + +To get started, see `this example <https://scikit-build.readthedocs.io/en/latest/usage.html#example-of-setup-py-cmakelists-txt-and-pyproject-toml>`_ and `scikit-build-sample-projects <https://github.com/scikit-build/scikit-build-sample-projects>`_. + + +Latest Release +-------------- + +.. table:: + + +-----------------------------------------------------------------------------+-------------------------------------------------------------------------------+ + | Versions | Downloads | + +=============================================================================+===============================================================================+ + | .. image:: https://img.shields.io/pypi/v/scikit-build.svg | .. image:: https://img.shields.io/pypi/dm/scikit-build | + | :target: https://pypi.python.org/pypi/scikit-build | :target: https://pypi.python.org/pypi/scikit-build | + +-----------------------------------------------------------------------------+-------------------------------------------------------------------------------+ + | .. image:: https://anaconda.org/conda-forge/scikit-build/badges/version.svg | .. image:: https://anaconda.org/conda-forge/scikit-build/badges/downloads.svg | + | :target: https://anaconda.org/conda-forge/scikit-build | :target: https://anaconda.org/conda-forge/scikit-build | + +-----------------------------------------------------------------------------+-------------------------------------------------------------------------------+ + + + +Scikit-build 0.17.2 +=================== + +Another small release with fixes for non-MSVC Windows platforms. + +Bug fixes +--------- + +* RPM spec fix by `@LecrisUT <https://github.com/LecrisUT>`_ in `#937 <https://github.com/scikit-build/scikit-build/pull/937>`_. +* Validate value before returning library path by `@dlech <https://github.com/dlech>`_ in `#942 <https://github.com/scikit-build/scikit-build/pull/942>`_. +* Only add ``Python_LIBRARY`` on Windows MSVC in `#943 <https://github.com/scikit-build/scikit-build/pull/943>`_ and `#944 <https://github.com/scikit-build/scikit-build/pull/944>`_. +* Slightly nicer traceback for failed compiler in `#947 <https://github.com/scikit-build/scikit-build/pull/947>`_. + +Testing +------- +* Hide a few warnings that are expected in `#948 <https://github.com/scikit-build/scikit-build/pull/948>`_. + +Scikit-build 0.17.1 +=================== + +This is a small release fixing a few bugs; the primary one being a change that +was triggering a bug in older FindPython. The unused variable messages have +been deactivated to simplify output, as well. + +Bug fixes +--------- + +* Older (<3.24) CMake breaks when lib specified in `#932 <https://github.com/scikit-build/scikit-build/pull/932>`_. +* An error output was missing formatting in `#931 <https://github.com/scikit-build/scikit-build/pull/931>`_. +* Make empty ``CMAKE_OSX_DEPLOYMENT_TARGET`` a warning (bug in conda-forge's + clang activation fixed upstream) in `#934 <https://github.com/scikit-build/scikit-build/pull/934>`_. +* Remove unused variable warnings by in `#930 <https://github.com/scikit-build/scikit-build/pull/930>`_. + +Testing +------- + +* Add Fedora packaging with packit automation by `@LecrisUT <https://github.com/LecrisUT>`_ in `#928 <https://github.com/scikit-build/scikit-build/pull/928>`_. +* Fix codecov ci by `@LecrisUT <https://github.com/LecrisUT>`_ in `#929 <https://github.com/scikit-build/scikit-build/pull/929>`_. +* Update some coverage settings in `#933 <https://github.com/scikit-build/scikit-build/pull/933>`_. + + + +Scikit-build 0.17.0 +=================== + +A lot of bug fixes are present in this release, focusing on Windows, PyPy, and +cross compiling. We've also improved the compatibility with default setuptools +behaviors a little, and enabled some things that were previously unavailable, +like overriding the build type via the cmake argument environment variables. +We've expanded our CI matrix to include Windows and macOS PyPy and some Fortran +tests on Linux. This release requires Python 3.7+. + +Bug fixes +--------- + +* Match setuptools behavior for ``include_package_data`` default. by `@vyasr <https://github.com/vyasr>`_ in `#873 <https://github.com/scikit-build/scikit-build/pull/873>`_. +* Misc. fixes for F2PY and PythonExtensions modules by `@benbovy <https://github.com/benbovy>`_ in `#495 <https://github.com/scikit-build/scikit-build/pull/495>`_. +* Provide more useful error if user provides ``CMAKE_INSTALL_PREFIX`` by `@vyasr <https://github.com/vyasr>`_ in `#872 <https://github.com/scikit-build/scikit-build/pull/872>`_. +* Stop assuming that ``.pyx`` files are in the same directory as ``CMakeLists.txt`` by `@vyasr <https://github.com/vyasr>`_ in `#871 <https://github.com/scikit-build/scikit-build/pull/871>`_. +* Allow build type overriding in `#902 <https://github.com/scikit-build/scikit-build/pull/902>`_. +* Detect PyPy library correctly on Windows by user:`gershnik` in `#904 <https://github.com/scikit-build/scikit-build/pull/904>`_. +* Include library for FindPython for better Windows cross-compiles in `#913 <https://github.com/scikit-build/scikit-build/pull/913>`_. Thanks to user:`maxbachmann` for testing. +* Fix logic for default generator when cross-compiling for ARM on Windows in `#917 <https://github.com/scikit-build/scikit-build/pull/917>`_ by `@dlech <https://github.com/dlech>`_. +* Use f2py's ``get_include`` if present in `#877 <https://github.com/scikit-build/scikit-build/pull/877>`_. +* Fix support for cross-compilation exception using ``targetLinkLibrariesWithDynamicLookup`` by `@erykoff <https://github.com/erykoff>`_ in `#901 <https://github.com/scikit-build/scikit-build/pull/901>`_. +* Treat empty ``MACOSX_DEPLOYMENT_TARGET`` as if it was unset in `#918 <https://github.com/scikit-build/scikit-build/pull/918>`_. + +Testing +------- + +* Add hello fortran sample package + tests by `@benbovy <https://github.com/benbovy>`_ in `#493 <https://github.com/scikit-build/scikit-build/pull/493>`_. +* Add sdist check & fix in `#906 <https://github.com/scikit-build/scikit-build/pull/906>`_. +* Fix some setuptools types in `#888 <https://github.com/scikit-build/scikit-build/pull/888>`_. +* Add PyPy Win & macOS to the CI in `#907 <https://github.com/scikit-build/scikit-build/pull/907>`_. +* Add tests for Python 3.12 Linux alphas in `#922 <https://github.com/scikit-build/scikit-build/pull/922>`_. + +Miscellaneous +------------- + +* Drop Python 3.6 in `#862 <https://github.com/scikit-build/scikit-build/pull/862>`_. +* Move building backend to hatchling in `#870 <https://github.com/scikit-build/scikit-build/pull/870>`_. +* Avoid mutating function input parameters in `#899 <https://github.com/scikit-build/scikit-build/pull/899>`_. +* Use _compat/typing name in `#869 <https://github.com/scikit-build/scikit-build/pull/869>`_. + + + +Publications +------------ + +Please use the first citation when referencing scikit-build in scientific publications. + +* Jean-Christophe Fillion-Robin, Matt McCormick, Omar Padron, Max Smolens, Michael Grauer, & Michael Sarahan. (2018, July 13). jcfr/scipy_2018_scikit-build_talk: SciPy 2018 Talk | scikit-build: A Build System Generator for CPython C/C++/Fortran/Cython Extensions. Zenodo. https://doi.org/10.5281/zenodo.2565368 + +* Schreiner, Henry, Rickerby, Joe, Grosse-Kunstleve, Ralf, Jakob, Wenzel, Darbois, Matthieu, Gokaslan, Aaron, Fillion-Robin, Jean-Christophe, & McCormick, Matt. (2022, August 1). Building Binary Extensions with pybind11, scikit-build, and cibuildwheel. https://doi.org/10.25080/majora-212e5952-033 + + +History +------- + +PyCMake was created at SciPy 2014 in response to general difficulties building +C++ and Fortran based Python extensions across platforms. It was renamed to +"scikit-build" in 2016. + + +Known Issues +------------ + +These issues are likely to be addressed in upcoming releases. + +* Editable installs do not work with the latest versions of Setuptools (and had + issues with older versions, too). +* Configuration scikit-build cares about _must_ be specified in ``setup()`` + currently. +* The cache directory (``_skbuild``) may need to be deleted between builds in + some cases (like rebuilding with a different Python interpreter). + +We are also working on improving scikit-build, so there are some upcoming +changes and deprecations: + + +* All deprecated setuptools/distutils features are also deprecated in + scikit-build, like the ``test`` command, ``easy_install``, etc. +* Older versions of CMake (<3.15) are not recommended; a future version will + remove support for older CMake's (along with providing a better mechanism for + ensuring a proper CMake is available). + +If you need any of these features, please open or find an issue explaining what +and why you need something. + +Miscellaneous +------------- + +* Free software: MIT license +* Documentation: http://scikit-build.readthedocs.org +* Source code: https://github.com/scikit-build/scikit-build +* Discussions: https://github.com/orgs/scikit-build/discussions + + +Support for this work was provided by NSF cooperative agreement `OAC-2209877 <https://www.nsf.gov/awardsearch/showAward?AWD_ID=2209877>`_. diff --git a/test/ldd/appletviewer b/test/ldd/appletviewer Binary files differnew file mode 100755 index 0000000..a67ddd0 --- /dev/null +++ b/test/ldd/appletviewer diff --git a/test/ldd/libtirpc.so.3.0.0 b/test/ldd/libtirpc.so.3.0.0 Binary files differnew file mode 100755 index 0000000..15251f8 --- /dev/null +++ b/test/ldd/libtirpc.so.3.0.0 diff --git a/test/ldd/opt-dependency b/test/ldd/opt-dependency Binary files differnew file mode 100755 index 0000000..b48d4d1 --- /dev/null +++ b/test/ldd/opt-dependency diff --git a/test/ldd/usr-dependency b/test/ldd/usr-dependency Binary files differnew file mode 100755 index 0000000..d115179 --- /dev/null +++ b/test/ldd/usr-dependency diff --git a/test/readelf/archive-with-debuginfo.a b/test/readelf/archive-with-debuginfo.a Binary files differnew file mode 100644 index 0000000..8c233f2 --- /dev/null +++ b/test/readelf/archive-with-debuginfo.a diff --git a/test/readelf/call-mktemp b/test/readelf/call-mktemp Binary files differnew file mode 100755 index 0000000..bd72aef --- /dev/null +++ b/test/readelf/call-mktemp diff --git a/test/readelf/call-setgroups b/test/readelf/call-setgroups Binary files differnew file mode 100755 index 0000000..b42d575 --- /dev/null +++ b/test/readelf/call-setgroups diff --git a/test/readelf/empty-archive.a b/test/readelf/empty-archive.a new file mode 100644 index 0000000..8b277f0 --- /dev/null +++ b/test/readelf/empty-archive.a @@ -0,0 +1 @@ +!<arch> diff --git a/test/readelf/executable-stack b/test/readelf/executable-stack Binary files differnew file mode 100755 index 0000000..ef17f70 --- /dev/null +++ b/test/readelf/executable-stack diff --git a/test/readelf/function-sections.a b/test/readelf/function-sections.a Binary files differnew file mode 100644 index 0000000..02be679 --- /dev/null +++ b/test/readelf/function-sections.a diff --git a/test/readelf/hostname b/test/readelf/hostname Binary files differnew file mode 100755 index 0000000..a5c270d --- /dev/null +++ b/test/readelf/hostname diff --git a/test/readelf/invalid-soname.so b/test/readelf/invalid-soname.so Binary files differnew file mode 100755 index 0000000..dafffb0 --- /dev/null +++ b/test/readelf/invalid-soname.so diff --git a/test/readelf/libbsd-ctor.a b/test/readelf/libbsd-ctor.a Binary files differnew file mode 100644 index 0000000..eaec056 --- /dev/null +++ b/test/readelf/libbsd-ctor.a diff --git a/test/readelf/libclang_rt.asan-preinit-x86_64.a b/test/readelf/libclang_rt.asan-preinit-x86_64.a Binary files differnew file mode 100644 index 0000000..40034ce --- /dev/null +++ b/test/readelf/libclang_rt.asan-preinit-x86_64.a diff --git a/test/readelf/libgame.so b/test/readelf/libgame.so Binary files differnew file mode 100755 index 0000000..3010160 --- /dev/null +++ b/test/readelf/libgame.so diff --git a/test/readelf/libkleeRuntimeFreeStanding.bca b/test/readelf/libkleeRuntimeFreeStanding.bca Binary files differnew file mode 100644 index 0000000..b650d2b --- /dev/null +++ b/test/readelf/libkleeRuntimeFreeStanding.bca diff --git a/test/readelf/libutil-2.29.so b/test/readelf/libutil-2.29.so Binary files differnew file mode 100755 index 0000000..b2bb51c --- /dev/null +++ b/test/readelf/libutil-2.29.so diff --git a/test/readelf/lto-object.o b/test/readelf/lto-object.o Binary files differnew file mode 100644 index 0000000..2cb29a0 --- /dev/null +++ b/test/readelf/lto-object.o diff --git a/test/readelf/main.a b/test/readelf/main.a Binary files differnew file mode 100644 index 0000000..c4ad699 --- /dev/null +++ b/test/readelf/main.a diff --git a/test/readelf/nested-function b/test/readelf/nested-function Binary files differnew file mode 100755 index 0000000..319e05c --- /dev/null +++ b/test/readelf/nested-function diff --git a/test/readelf/no-soname.so b/test/readelf/no-soname.so Binary files differnew file mode 100755 index 0000000..352159b --- /dev/null +++ b/test/readelf/no-soname.so diff --git a/test/readelf/non-pic-shared-m32.so b/test/readelf/non-pic-shared-m32.so Binary files differnew file mode 100755 index 0000000..07b4d5f --- /dev/null +++ b/test/readelf/non-pic-shared-m32.so diff --git a/test/readelf/only-data.a b/test/readelf/only-data.a Binary files differnew file mode 100644 index 0000000..a41ef4d --- /dev/null +++ b/test/readelf/only-data.a diff --git a/test/readelf/rpath-lib.so b/test/readelf/rpath-lib.so Binary files differnew file mode 100755 index 0000000..97880d5 --- /dev/null +++ b/test/readelf/rpath-lib.so diff --git a/test/readelf/small_archive.a b/test/readelf/small_archive.a new file mode 100644 index 0000000..269e7ae --- /dev/null +++ b/test/readelf/small_archive.a @@ -0,0 +1,8 @@ +!<arch> +limerick/ 0 0 0 644 191 ` +There was a young man from Japan +Whose limericks never would scan. +When asked why that was, +He replied "It's because +I always try to cram as many words into the last line as I possibly can." + diff --git a/test/readelf/stripped-archive.a b/test/readelf/stripped-archive.a Binary files differnew file mode 100644 index 0000000..6eacc42 --- /dev/null +++ b/test/readelf/stripped-archive.a diff --git a/test/readelf/stripped-lto.a b/test/readelf/stripped-lto.a Binary files differnew file mode 100644 index 0000000..530dd32 --- /dev/null +++ b/test/readelf/stripped-lto.a diff --git a/test/rpmlintrc/multiple/sample-rpmlintrc b/test/rpmlintrc/multiple/sample-rpmlintrc new file mode 100644 index 0000000..00f263b --- /dev/null +++ b/test/rpmlintrc/multiple/sample-rpmlintrc @@ -0,0 +1 @@ +addFilter("W: files-attr-not-set") diff --git a/test/rpmlintrc/multiple/sample.rpmlintrc b/test/rpmlintrc/multiple/sample.rpmlintrc new file mode 100644 index 0000000..8668d5e --- /dev/null +++ b/test/rpmlintrc/multiple/sample.rpmlintrc @@ -0,0 +1 @@ +setBadness('suse-dbus-unauthorized-service', 0) diff --git a/test/rpmlintrc/multiple/sample.spec b/test/rpmlintrc/multiple/sample.spec new file mode 100644 index 0000000..e1b4139 --- /dev/null +++ b/test/rpmlintrc/multiple/sample.spec @@ -0,0 +1,8 @@ +Name: sample +Version: 0 +Release: 0 +License: GPL-2.0-only +Summary: Whatever + +%description +Whatever. diff --git a/test/rpmlintrc/single/sample.rpmlintrc b/test/rpmlintrc/single/sample.rpmlintrc new file mode 100644 index 0000000..8668d5e --- /dev/null +++ b/test/rpmlintrc/single/sample.rpmlintrc @@ -0,0 +1 @@ +setBadness('suse-dbus-unauthorized-service', 0) diff --git a/test/rpmlintrc/single/sample.spec b/test/rpmlintrc/single/sample.spec new file mode 100644 index 0000000..e1b4139 --- /dev/null +++ b/test/rpmlintrc/single/sample.spec @@ -0,0 +1,8 @@ +Name: sample +Version: 0 +Release: 0 +License: GPL-2.0-only +Summary: Whatever + +%description +Whatever. diff --git a/test/source/invalid-spec-name-0-0.src.rpm b/test/source/invalid-spec-name-0-0.src.rpm Binary files differnew file mode 100644 index 0000000..bcf8406 --- /dev/null +++ b/test/source/invalid-spec-name-0-0.src.rpm diff --git a/test/source/no-spec-file-0-0.src.rpm b/test/source/no-spec-file-0-0.src.rpm Binary files differnew file mode 100644 index 0000000..ad5c512 --- /dev/null +++ b/test/source/no-spec-file-0-0.src.rpm diff --git a/test/source/nodejs010-1-7.el6_4.src.rpm b/test/source/nodejs010-1-7.el6_4.src.rpm Binary files differdeleted file mode 100644 index 3523dec..0000000 --- a/test/source/nodejs010-1-7.el6_4.src.rpm +++ /dev/null diff --git a/test/source/nodejs010-nodejs-0.10.3-3.el6_4.src.rpm b/test/source/nodejs010-nodejs-0.10.3-3.el6_4.src.rpm Binary files differdeleted file mode 100644 index b92a568..0000000 --- a/test/source/nodejs010-nodejs-0.10.3-3.el6_4.src.rpm +++ /dev/null diff --git a/test/source/nodejs010-nodejs-forever-agent-0.2.0-2.el6_4.src.rpm b/test/source/nodejs010-nodejs-forever-agent-0.2.0-2.el6_4.src.rpm Binary files differdeleted file mode 100644 index c575d0f..0000000 --- a/test/source/nodejs010-nodejs-forever-agent-0.2.0-2.el6_4.src.rpm +++ /dev/null diff --git a/test/source/not-compressed-multi-spec-1.0-0.src.rpm b/test/source/not-compressed-multi-spec-1.0-0.src.rpm Binary files differnew file mode 100644 index 0000000..c50028c --- /dev/null +++ b/test/source/not-compressed-multi-spec-1.0-0.src.rpm diff --git a/test/source/valid-exception-begin-grouping-1.0-1.src.rpm b/test/source/valid-exception-begin-grouping-1.0-1.src.rpm Binary files differnew file mode 100644 index 0000000..d0aba49 --- /dev/null +++ b/test/source/valid-exception-begin-grouping-1.0-1.src.rpm diff --git a/test/source/valid-exception-in-grouping-1.0-1.src.rpm b/test/source/valid-exception-in-grouping-1.0-1.src.rpm Binary files differnew file mode 100644 index 0000000..c74b2f0 --- /dev/null +++ b/test/source/valid-exception-in-grouping-1.0-1.src.rpm diff --git a/test/spec/%autopatch-not-in-prep.spec b/test/spec/%autopatch-not-in-prep.spec new file mode 100644 index 0000000..d80714d --- /dev/null +++ b/test/spec/%autopatch-not-in-prep.spec @@ -0,0 +1,28 @@ +Name: %autopatch-not-in-prep +Version: 0 +Release: 0 +Summary: autopatch not inside prep warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Requires: Somethingwithsinglespace >=1.0 +Conflicts: Someotherthinwithsinglespace<= 1.0 +Obsoletes: %{name} <= %{version} +Provides: %{name} = %{version} + +%description +autopatch macro must be inside %prep. + +%autopatch + +%prep + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/%autosetup-not-in-prep.spec b/test/spec/%autosetup-not-in-prep.spec new file mode 100644 index 0000000..45d5c7b --- /dev/null +++ b/test/spec/%autosetup-not-in-prep.spec @@ -0,0 +1,28 @@ +# This is comment to check macro-in-comment not found. +Name: autosetup-not-in-prep +Version: 0 +Release: 0 +Summary: autosetup-not-in-prep warning. +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Requires: Somethingwithdoublespace = 1.0 +Conflicts: Some thing with double space == 2.0 +Provides: /Something + +%description +The specfile contains %autosetup outside the %prep. + +%autosetup + +%prep + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/%ifarch-applied-patch.spec b/test/spec/%ifarch-applied-patch.spec new file mode 100644 index 0000000..3d16ba2 --- /dev/null +++ b/test/spec/%ifarch-applied-patch.spec @@ -0,0 +1,29 @@ +Name: %ifarch-applied-patch +Version: 0 +Release: 0 +Summary: %ifarch-applied-patch warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Patch1: Patch1.patch +Requires(post): foo + +%description +A patch is applied inside an %ifarch block. Patches must be applied +on all architectures and may contain necessary configure and/or code +patch to be effective only on a given arch. + +%prep + +%build + +%install +%ifarch +%patch1 -P 1 +%endif + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/MacroInComment.spec b/test/spec/MacroInComment.spec new file mode 100644 index 0000000..4af0636 --- /dev/null +++ b/test/spec/MacroInComment.spec @@ -0,0 +1,49 @@ +Name: MacroInComment +Version: 0 +Release: 0 +Summary: None here + +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +Provides: unversioned-provides, versioned-provides = 1.0 +Obsoletes: versioned-obsoletes < 2.0 +Obsoletes: unversioned-obsoletes +Obsoletes: /usr/bin/unversioned-but-filename +Provides: /sbin/another-unversioned-but-filename +#!BuildIgnore: %{name} + +%description +MacroInComment test. + +%package noarch-sub +Summary: Noarch subpackage +Group: Undefined +BuildArch: noarch + +%description noarch-sub +Noarch subpackage test. + +%prep +%autosetup -p 1 + +%build +# %configure +# %%% + +%install +rm -rf $RPM_BUILD_ROOT + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +%{_libdir}/foo + +%files noarch-sub +%defattr(-,root,root,-) + +%changelog diff --git a/test/spec/SpecCheck.spec b/test/spec/SpecCheck.spec index 7a0c0cb..645dcc7 100644 --- a/test/spec/SpecCheck.spec +++ b/test/spec/SpecCheck.spec @@ -33,7 +33,6 @@ BuildArch: noarch %description noarch-sub Noarch subpackage test. - %prep %setup %patch1 @@ -43,20 +42,16 @@ sed -e s/foo/bar/ %{PATCH5} | %{__patch} -p1 %{__patch} -p2 < %{PATCH6} patch -i %{PATCH7} - %build # %configure # %%% - %install rm -rf $RPM_BUILD_ROOT - %clean rm -rf $RPM_BUILD_ROOT - %files %defattr(-,root,root,-) %{_libdir}/foo @@ -64,5 +59,4 @@ rm -rf $RPM_BUILD_ROOT %files noarch-sub %defattr(-,root,root,-) - %changelog diff --git a/test/spec/SpecCheck2.spec b/test/spec/SpecCheck2.spec index 189f522..3fb11d4 100644 --- a/test/spec/SpecCheck2.spec +++ b/test/spec/SpecCheck2.spec @@ -14,29 +14,36 @@ Patch3: Patch3.patch Patch4: Patch4.patch Patch5: Patch5.patch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +BuildRequires: source-for-second-rpm +BuildArch: noarch +ExclusiveArch: i586 +Requires: Oneanotherthing>=1.0 +Conflicts: Onelastthing==2.0 %description -SpecCheck test 2. - +macro-in-%changelog-deptoken:- +(Developer Note) + Macro can cause a warning which you can escape by using %%buildroot or + %+buildroot or %.buildroot or any othersign prefixed with % + for example %(-, +, .) and so on. + Make sure you exclude %_buildroot or usage of % followed by _ %prep %autosetup - %build - +%configure +./configure --libdir=%{_libdir} +make %{_libdir} %install rm -rf $RPM_BUILD_ROOT - %clean rm -rf $RPM_BUILD_ROOT - %files %defattr(-,root,root,-) %{_libdir}/foo - %changelog diff --git a/test/spec/SpecCheck4.spec b/test/spec/SpecCheck4.spec new file mode 100644 index 0000000..288b5b1 --- /dev/null +++ b/test/spec/SpecCheck4.spec @@ -0,0 +1,36 @@ +Name: SpecCheck4 +Version: 0.0.1 +Release: 0 +Summary: None here + +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Requires: require +Provides: provide +Obsoletes: obsolete +Conflicts: conflict + +%description + +%prep +%setup -q + +%build +%configure +%make_build + +%install +%make_install + +%post +%postun + +%files +%license COPYING +%doc ChangeLog README + +%changelog +* Wed Oct 23 14:15:39 UTC 2019 - Frank Schreiner <frank@fs.samaxi.de> +- changelog entry .... diff --git a/test/spec/SpecCheckPatch.spec b/test/spec/SpecCheckPatch.spec new file mode 100644 index 0000000..f605922 --- /dev/null +++ b/test/spec/SpecCheckPatch.spec @@ -0,0 +1,40 @@ + +Name: SpecCheckPatch +Version: 0 +Release: 0 +Summary: None here + +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Patch1: Patch1.patch +Patch2: Patch2.patch +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +SpecCheck test 2. + + +%prep +%setup -q +%patch -P 1 -p1 +%patch -P2 -p1 + +%build + + +%install +rm -rf $RPM_BUILD_ROOT + + +%clean +rm -rf $RPM_BUILD_ROOT + + +%files +%defattr(-,root,root,-) +%{_libdir}/foo + + +%changelog diff --git a/test/spec/SpecCheckTemp.spec b/test/spec/SpecCheckTemp.spec new file mode 100644 index 0000000..a7d28c1 --- /dev/null +++ b/test/spec/SpecCheckTemp.spec @@ -0,0 +1,88 @@ +# 1. rpm-buildroot-usage: +# %{buildroot} should not be touched during %build or %prep stage, +# as it may break short circuit builds. +# Developer Note:- This file contains %buildroot under %build macro +# since rpm-buildroot-usage.spec contains the +# %build under %prep macro and we need to test "a warning if +# %{buildroot} is placed under %build". +# +# 2. make-check-outside-check-section: +# Make check or other automated regression test should be run +# in %check, as they can be disabled with a rpm macro for short +# circuiting purposes. +# Developer Note:- This file contains `make check` inside %check +# %description %package %changelog to test the required check +# not in out. +# +# 3. setup-not-quiet: +# Use the -q option to the %setup macro to avoid useless +# build output from unpacking the sources. +# Developer Note:- This file contains the %setup -q macro to test +# the required check not in out. +# +# 4. setup-not-in-prep: +# The %setup macro should only be used within the %prep +# section because it may not expand to anything outside +# of it and can break the build in unpredictable. +# Developer Note:- This file contains %setup -q inside %prep +# macro to test if check setup-not-in-prep is not in out. +# +# 5. %autopatch-not-in-prep: +# Developer Note:- This file contains %autopatch inside the %prep macro. +# +# 6. %autosetup-not-in-prep: +# Developer Note:- This file contains %autosetup inside the %prep macro. +# +# 7. comparision-operator-in-deptoken: +# This dependency token contains a comparison operator (<, > or =). +# This is usually not intended and may be caused by missing +# whitespace between the token's name, the comparison operator and +# the version strig. +# Developer Note:- This file contains < and <= operators as seen in +# Requires and Conflicts respectively and is responsible for +# respective check since it does not contains spaces around the operators. +%define __find_provides +%define _use_internal_dependency_generator 0 + +Name: SpecCheckTemp +Version: 0 +Release: 0 +Summary: rpm-buildroot-usage, make-check-outside-check, setup-not-quite, setup-not-in-prep, %autopatch-not-in-prep, %autosetup-not-in-prep warning, comparision-operator-in-deptoken. +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Requires: Someotherthing<1.0 +Conflicts: Someotherthing<=2.0 +Obsoletes: /something + +%description +make check + egrep something + +%build +%{buildroot} + +%prep +%setup -q +%autopatch +%autosetup + +%check +make check + +%package +make check + grep something + grep -F Someotherthing + grep -E something + +%install + +%files +%{_libdir}/foo + fgrep -F something + +%changelog +make check + egrep -E something diff --git a/test/spec/buildarch-instead-of-exclusivearch-tag.spec b/test/spec/buildarch-instead-of-exclusivearch-tag.spec new file mode 100644 index 0000000..4ea3b8e --- /dev/null +++ b/test/spec/buildarch-instead-of-exclusivearch-tag.spec @@ -0,0 +1,32 @@ +Name: buildarch-instead-of-exclusivearch-tag +Version: 0 +Release: 0 +Summary: buildarch-instead-of-exclusivearch-tag warning + +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildArch: x86_64 +BuildArchitectures: i586 +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +Use ExclusiveArch instead of BuildArch (or BuildArchitectures) +to restrict build on some specific architectures. +Only use BuildArch with noarch + +%prep + %autosetup + +%build + +%install + +%clean + +%files +%defattr(-,root,root,-) +%{_libdir}/foo + +%changelog diff --git a/test/spec/buildprereq-use.spec b/test/spec/buildprereq-use.spec new file mode 100644 index 0000000..84ffb38 --- /dev/null +++ b/test/spec/buildprereq-use.spec @@ -0,0 +1,25 @@ +Name: buildprereq-use +Version: 0 +Release: 0 +Summary: buildprereq-use warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildPreReq: Something + +%description +The use of BuildPreReq is deprecated, build dependencies are always required +before a package can be built. Use plain BuildRequires instead. + +%prep + %autosetup + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/comparison-operator-in-deptoken.spec b/test/spec/comparison-operator-in-deptoken.spec new file mode 100644 index 0000000..f5d4b50 --- /dev/null +++ b/test/spec/comparison-operator-in-deptoken.spec @@ -0,0 +1,29 @@ +Name: comparison-operator-in-deptoken +Version: 0 +Release: 0 +Summary: comparison-operator-in-deptoken warning. + +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildRequires: something>2.0 +Requires: Something>1.0 +Conflicts: Something=2.0 + +%description +This dependency token contains a comparison operator (<, > or =). This is +usually not intended and may be caused by missing whitespace between the +token's name, the comparison operator and the version string. + +%prep + %autosetup + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/configure-without-libdir-spec.spec b/test/spec/configure-without-libdir-spec.spec new file mode 100644 index 0000000..d5973b8 --- /dev/null +++ b/test/spec/configure-without-libdir-spec.spec @@ -0,0 +1,25 @@ +Name: configure-without-libdir-spec +Version: 0 +Release: 0 +Summary: configure-without-libdir-spec warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +A configure script is run without specifying the libdir. configure +options must be augmented with something like --libdir=%{_libdir} whenever +the script supports it. + +%prep + %autosetup + +%build +./configure + +%install + +%files + +%changelog diff --git a/test/spec/deprecated-grep.spec b/test/spec/deprecated-grep.spec new file mode 100644 index 0000000..066bb2b --- /dev/null +++ b/test/spec/deprecated-grep.spec @@ -0,0 +1,24 @@ +Name: deprecated-grep +Version: 0 +Release: 0 +Summary: deprecated-grep warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +Direct use of grep as egrep or fgrep is deprecated in GNU grep and +historical in POSIX, use grep -E and grep -F instead. + +%prep + egrep something + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/depscript-without-disabling-depgen.spec b/test/spec/depscript-without-disabling-depgen.spec new file mode 100644 index 0000000..638df72 --- /dev/null +++ b/test/spec/depscript-without-disabling-depgen.spec @@ -0,0 +1,26 @@ +%define __find_provides + +Name: depscript-without-disabling-depgen +Version: 0 +Summary: depscript-without-disabling-depgen warning +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +In some common rpm configurations/versions, defining __find_provides and/or +__find_requires has no effect if rpm's internal dependency generator has not +been disabled for the build. %define _use_internal_dependency_generator to 0 +to disable it in the specfile, or don't define __find_provides/requires. + +%prep + %autosetup + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/forbidden-controlchar-found.spec b/test/spec/forbidden-controlchar-found.spec new file mode 100644 index 0000000..0071b02 --- /dev/null +++ b/test/spec/forbidden-controlchar-found.spec @@ -0,0 +1,38 @@ +Name: SpecCheck_forbidden-controlchar-found +Version: 1.0 +Release: 0 +Summary: forbidden-controlchar-found warning +License: MIT +URL: https://www.example.com +Source: Source.tar.gz +Requires: something_needed > 1.0 +Provides: something_new +Obsoletes: something_old +Conflicts: something_bad +BuildRequires: gcc + +%description +This package contains tags which contain forbidden control characters. +These are all ASCII characters with a decimal value below 32, except TAB(9), +LF(10) and CR(13) + +%prep +%setup -q + +%build +%configure +%make_build + +%install +%make_install + +%post +%postun + +%files +%license COPYING +%doc ChangeLog README + +%changelog +- This is a changelog entry with forbidden control character + diff --git a/test/spec/hardcoded-library-path.spec b/test/spec/hardcoded-library-path.spec new file mode 100644 index 0000000..127920a --- /dev/null +++ b/test/spec/hardcoded-library-path.spec @@ -0,0 +1,30 @@ +Name: hardcoded-library-path +Version: 0 +Release: 0 +Summary: hardcoded-library-path error + +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +A library path is hardcoded to one of the following paths: /lib, +/usr/lib. It should be replaced by something like /%{_lib} or %{_libdir}. + +%prep + %autosetup + +%build +/usr/lib/bash/dirname/ +/usr/lib +/lib + +%clean + +%files +%defattr(-,root,root,-) +%{_libdir}/foo + +%changelog diff --git a/test/spec/hardcoded-packager-tag.spec b/test/spec/hardcoded-packager-tag.spec new file mode 100644 index 0000000..6aed3fb --- /dev/null +++ b/test/spec/hardcoded-packager-tag.spec @@ -0,0 +1,24 @@ +Name: hardcoded-packager-tag +Version: 0 +Release: 0 +Summary: hardcoded-packager-tag warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Packager: Someone + +%description +The Packager tag is hardcoded in your spec file. It should be removed, so +as to use rebuilder's own defaults. + +%prep + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/hardcoded-path-in-buildroot-tag.spec b/test/spec/hardcoded-path-in-buildroot-tag.spec new file mode 100644 index 0000000..1b7dc87 --- /dev/null +++ b/test/spec/hardcoded-path-in-buildroot-tag.spec @@ -0,0 +1,25 @@ +Name: hardcoded-path-in-buildroot-tag +Version: 0 +Release: 0 +Summary: hardcoded-path-in-buildroot-tag warning. +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Buildroot: /usr/bin/bash +Source0: Source0.tar.gz + +%description +A path is hardcoded in your Buildroot tag. It should be replaced +by something like %{_tmppath}/%{name}-%{version}-build. + +%prep + %autosetup + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/hardcoded-prefix-tag.spec b/test/spec/hardcoded-prefix-tag.spec new file mode 100644 index 0000000..bc820c2 --- /dev/null +++ b/test/spec/hardcoded-prefix-tag.spec @@ -0,0 +1,24 @@ +Name: hardcoded-prefix-tag +Version: 0 +Release: 0 +Summary: hardcoded-prefix-tag warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Prefix: /usr/bin/bash + +%description +The Prefix tag is hardcoded in your spec file. It should be removed, so as +to allow package relocation. + +%prep + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/intltool.spec b/test/spec/intltool.spec new file mode 100644 index 0000000..593d73c --- /dev/null +++ b/test/spec/intltool.spec @@ -0,0 +1,442 @@ +Name: intltool +Summary: Utility for internationalizing various kinds of data files +Version: 0.51.0 +Release: 24%{?dist} +License: GPL-2.0-or-later WITH Autoconf-exception-generic +#VCS: bzr:https://code.edge.launchpad.net/~intltool/intltool/trunk +Source: https://edge.launchpad.net/intltool/trunk/%{version}/+download/intltool-%{version}.tar.gz +URL: https://launchpad.net/intltool +BuildArch: noarch +Requires: patch +# for /usr/share/aclocal +Requires: automake +Requires: gettext-devel +Requires: perl(Getopt::Long) +Requires: perl(XML::Parser) +BuildRequires: perl-generators +BuildRequires: perl(Getopt::Long) +BuildRequires: perl(XML::Parser) +BuildRequires: gettext +BuildRequires: make +# http://bugzilla.gnome.org/show_bug.cgi?id=568845 +# Dropping this patch per the last comment on that thread: +# Martin Pitt: As the reporter of the bug I close this, as the new API du jour is gsettings, +# which has a sensible gettext integration. +#Patch0: schemas-merge.patch +# Fix intltool-update to work with perl 5.26. Patch taken from +# Debian's intltool_0.51.0-4.debian.tar.xz +Patch1: intltool-perl5.26-regex-fixes.patch +# https://bugs.launchpad.net/intltool/+bug/1505260 +# https://bugzilla.redhat.com/show_bug.cgi?id=1249051 +Patch2: intltool-merge-Create-cache-file-atomically.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=1318674 +Patch3: intltool_distcheck-fix.patch + +%description +This tool automatically extracts translatable strings from oaf, glade, +bonobo ui, nautilus theme, .desktop, and other data files and puts +them in the po files. + +%prep +%setup -q +%patch 1 -p1 +%patch 2 -p1 +%patch 3 -p1 + +%build +%configure + +%make_build + +%install +%make_install + +%check +if ! make check; then + find . -type f -name 'test-suite.log' | while read trs; do + echo "BEGIN " ${trs}; cat ${trs} 1>&2; + done + echo "Exiting abnormally due to make check failure above" 1>&2 + exit 1 +fi + +%files +%doc AUTHORS README +%license COPYING +%{_bindir}/intltool* +%{_datadir}/intltool +%{_datadir}/aclocal/intltool.m4 +%{_mandir}/man8/intltool*.8* + +%changelog +* Thu Sep 21 2023 Jens Petersen <petersen@redhat.com> - 0.51.0-24 +- SPDX migration of license tag + +* Thu Jul 20 2023 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-23 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Thu Jan 19 2023 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-22 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild + +* Thu Jul 21 2022 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-21 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild + +* Thu Jan 20 2022 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-20 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild + +* Thu Jul 22 2021 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-19 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild + +* Tue Jan 26 2021 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-18 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Tue Jul 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-17 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Wed Jan 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-16 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Thu Jul 25 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-15 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Fri Feb 01 2019 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-14 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Fri Jul 13 2018 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-13 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Fri Jun 29 2018 Jitka Plesnikova <jplesnik@redhat.com> - 0.51.0-12 +- Perl 5.28 rebuild + +* Wed Feb 07 2018 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-11 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Tue Aug 08 2017 Kalev Lember <klember@redhat.com> - 0.51.0-10 +- Fix intltool-update to work with perl 5.26 (#1462217) + +* Wed Jul 26 2017 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-9 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Fri Feb 10 2017 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Fri Jul 29 2016 Wolfgang Ulbrich <chat-to-me@raveit.de> - 0.51.0-7 +- fix usage of distcheck for some packages rhbz (#1318674) + +* Tue Feb 16 2016 Yaakov Selkowitz <yselkowi@redhat.com> - 0.51.0-6 +- Depend on perl(Getopt::Long) (#1307638) + +* Thu Feb 04 2016 Fedora Release Engineering <releng@fedoraproject.org> - 0.51.0-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Mon Oct 12 2015 Colin Walters <walters@redhat.com> - 0.51.0-4 +- Add patch to hopefully close race condition in systemd builds +- Related to https://bugzilla.redhat.com/show_bug.cgi?id=1249051 +- And the test suite is failing but the logs are hidden under + test-suite.log, so copy some code I had in dbus.spec to cat them. + +* Mon Jul 13 2015 Ralf Corsépius <corsepiu@fedoraproject.org> - 0.51.0-3 +- Add intltool-0.51.0-perl-5.22.patch (Address RHBZ#1233444) +- Remove unnecessary %%debug_package. + +* Wed Jun 17 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.51.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Mon Mar 09 2015 David King <amigadave@amigadave.com> - 0.51.0-1 +- Update to 0.51.0 +- Use license macro for COPYING +- Preserve timestamps during install +- Use parallel make flags +- Update man page glob in files section + +* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.50.2-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Sat Aug 03 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.50.2-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Wed Jul 24 2013 Petr Pisar <ppisar@redhat.com> - 0.50.2-6 +- Perl 5.18 rebuild + +* Fri Feb 8 2013 Matthias Clasen <mclasen@redhat.com> - 0.50.2-5 +- Update url (#908562) + +* Sun Oct 21 2012 Matthias Clasen <mclasen@redhat.com> - 0.50.2-3 +- Rebuild + +* Thu Jul 19 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.50.2-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild + +* Tue Apr 17 2012 Kalev Lember <kalevlember@gmail.com> - 0.50.2-2 +- Clean up previous change and fix Requires/BuildRequires (#225902) + +* Fri Apr 06 2012 Jon Ciesla <limburgher@gmail.com> - 0.50.2-1 +- Latest stable release. +- Merge review BZ 225902 fixes: +- Removed Obsoletes/Provides for xml-i18n-tools. +- Swapped gettext/gettext-devel Requires, BuildRequires. +- Added %%check section. + +* Fri Jan 13 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.50.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Tue Dec 20 2011 Alon Levy <alevy@redhat.com> +- Update to 0.50.0 +- Drop patch carried for bz#568845 (schemas-merge) per last comment + in that bug. + +* Wed Feb 09 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.41.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Sun Mar 28 2010 Matthias Clasen <mclasen@redhat.com> - 0.41.1-1 +- Update to 0.41.1 + +* Wed Aug 12 2009 Matthias Clasen <mclasen@redhat.com> - 0.41.0-1 +- Update to 0.41.0 + +* Mon Aug 10 2009 Ville Skyttä <ville.skytta@iki.fi> - 0.40.6-4 +- Convert specfile to UTF-8. + +* Fri Jul 24 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.40.6-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild + +* Mon Apr 27 2009 Matthias Clasen <mclasen@redhat.com> - 0.40.6-2 +- Don't merge translations back into GConf schemas + +* Mon Mar 16 2009 Matthias Clasen <mclasen@redhat.com> - 0.40.6-1 +- Update to 0.40.6 + +* Tue Feb 24 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.40.5-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild + +* Thu Feb 12 2009 Matthias Clasen <mclasen@redhat.com> - 0.40.5-2 +- turn noarch + +* Sun Oct 19 2008 Matthias Clasen <mclasen@redhat.com> - 0.40.5-1 +- Update to 0.40.5 + +* Sun Sep 21 2008 Matthias Clasen <mclasen@redhat.com> - 0.40.4-1 +- Update to 0.40.4 + +* Wed Aug 6 2008 Matthias Clasen <mclasen@redhat.com> - 0.40.3-3 +- Require gettext-devel + +* Thu Jul 31 2008 Tom "spot" Callaway <tcallawa@redhat.com> - 0.40.3-2 +- fix license tag + +* Fri Jul 25 2008 Matthias Clasen <mclasen@redhat.com> - 0.40.3-1 +- Update to 0.40.3 + +* Mon Jul 21 2008 Matthias Clasen <mclasen@redhat.com> - 0.40.1-1 +- Update to 0.40.1 + +* Tue Jun 3 2008 Matthias Clasen <mclasen@redhat.com> - 0.40.0-1 +- Update to 0.40.0 + +* Mon Feb 25 2008 Matthias Clasen <mclasen@redhat.com> - 0.37.1-1 +- Update to 0.37.1 + +* Tue Feb 19 2008 Fedora Release Engineering <rel-eng@fedoraproject.org> - 0.37.0-3 +- Autorebuild for GCC 4.3 + +* Tue Jan 15 2008 Matthias Clasen <mclasen@redhat.com> - 0.37.0-2 +- Require gettext + +* Mon Dec 17 2007 Matthias Clasen <mclasen@redhat.com> - 0.37.0-1 +- Update to 0.37.0 + +* Thu Dec 13 2007 Matthias Clasen <mclasen@redhat.com> - 0.36.3-1 +- Update to 0.36.3 + +* Sun Sep 16 2007 Matthias Clasen <mclasen@redhat.com> - 0.36.2-1 +- Update to 0.36.2 + +* Mon Aug 13 2007 Matthias Clasen <mclasen@redhat.com> - 0.36.1-1 +- Update to 0.36.1 + +* Fri Aug 3 2007 Matthias Clasen <mclasen@redhat.com> - 0.36.0-1 +- Update to 0.36.0 +- Update license field +- Drop patch rejected, obsolete and upstreamed patches +- Some spec file cleanups +- Require automake + +* Tue Jul 31 2007 David Zeuthen <davidz@redhat.com> - 0.35.5-5 +- Add support for PolicyKit .policy files (b.g.o #462312) + +* Sat Jul 28 2007 Matthias Clasen <mclasen@redhat.com> - 0.35.5-4 +- Don't produce useless debuginfo (#249969) + +* Wed Mar 21 2007 Ray Strode <rstrode@redhat.com> - 0.35.5-3 +- don't store a translation if it is equal to the original + string + +* Mon Mar 19 2007 Bill Nottingham <notting@redhat.com> - 0.35.5-2 +- add upstream changeset 674 (GNOME bz#413461 - fix intltool-extract path) + +* Sat Feb 24 2007 Matthias Clasen <mclasen@redhat.com> - 0.35.5-1 +- Update to 0.35.5 + +* Wed Jan 10 2007 Matthias Clasen <mclasen@redhat.com> - 0.35.4-1 +- Update to 0.35.4 + +* Thu Dec 21 2006 Matthias Clasen <mclasen@redhat.com> - 0.35.2-1 +- Update to 0.35.2 + +* Tue Aug 1 2006 Matthias Clasen <mclasen@redhat.com> - 0.35.0-2 +- Add a missing BuildRequires: gettext + +* Wed Jul 12 2006 Jesse Keating <jkeating@redhat.com> - 0.35.0-1.1 +- rebuild + +* Tue May 16 2006 Matthias Clasen <mclasen@redhat.com> 0.35.0-1 +- Update to 0.35.1 + +* Tue May 9 2006 Matthias Clasen <mclasen@redhat.com> 0.34.90.cvs20060509-1 +- Update to a cvs snapshot to allow building gnome 2.15 + +* Fri Feb 10 2006 Jesse Keating <jkeating@redhat.com> - 0.34.2-1.1 +- bump again for double-long bug on ppc(64) + +* Mon Feb 6 2006 Matthias Clasen <mclasen@redhat.com> 0.34.2-1 +- Update to 0.34.2 + +* Fri Dec 09 2005 Jesse Keating <jkeating@redhat.com> +- rebuilt + +* Thu Aug 4 2005 Matthias Clasen <mclasen@redhat.com> - 0.34.1-1 +- New upstream version + +* Wed Mar 2 2005 Matthias Clasen <mclasen@redhat.com> - 0.33-2 +- Rebuild with gcc4 + +* Wed Jan 26 2005 Matthias Clasen <mclasen@redhat.com> - 0.33-1 +- Upgrade to 0.33 + +* Thu Jan 13 2005 Jeremy Katz <katzj@redhat.com> - 0.31.2-3 +- fix intltool local mode (upstream 163981) + +* Wed Nov 3 2004 <jrb@redhat.com> - 0.31.2-1 +- add BuildRequires on perl-XML-Parser, #132622 + +* Thu Sep 23 2004 Jonathan Blandford <jrb@redhat.com> 0.31.2-1 +- bump version + +* Tue Aug 3 2004 Owen Taylor <otaylor@redhat.com> - 0.31.1-1 +- Upgrade to 0.31.1 + +* Tue Jun 15 2004 Elliot Lee <sopwith@redhat.com> +- rebuilt + +* Fri Mar 12 2004 Alex Larsson <alexl@redhat.com> 0.30-1 +- update to 0.30 + +* Fri Feb 13 2004 Elliot Lee <sopwith@redhat.com> +- rebuilt + +* Mon Jan 19 2004 Jonathan Blandford <jrb@redhat.com> 0.29-1 +- new version + +* Mon Aug 25 2003 Alexander Larsson <alexl@redhat.com> 0.27.2-1 +- update + +* Mon Aug 11 2003 Havoc Pennington <hp@redhat.com> 0.27-1 +- 0.27 + +* Wed Jul 30 2003 Havoc Pennington <hp@redhat.com> 0.26-1 +- rebuild + +* Wed Jul 9 2003 Havoc Pennington <hp@redhat.com> 0.26-1 +- 0.26 + +* Wed Jun 04 2003 Elliot Lee <sopwith@redhat.com> +- rebuilt + +* Wed Jan 22 2003 Tim Powers <timp@redhat.com> +- rebuilt + +* Mon Jan 6 2003 Havoc Pennington <hp@redhat.com> +- 0.25 + +* Fri Nov 8 2002 Havoc Pennington <hp@redhat.com> +- 0.23 + +* Fri Jun 21 2002 Tim Powers <timp@redhat.com> +- automated rebuild + +* Sun Jun 09 2002 Havoc Pennington <hp@redhat.com> +- rebuild in different environment + +* Sun Jun 9 2002 Havoc Pennington <hp@redhat.com> +- 0.22 +- remove perl patch, perl is fixed + +* Thu Jun 6 2002 Nalin Dahyabhai <nalin@redhat.com> +- tweak the perl5 check to not bomb with perl 5.8 + +* Thu May 23 2002 Tim Powers <timp@redhat.com> +- automated rebuild + +* Thu Apr 25 2002 Havoc Pennington <hp@redhat.com> +- rebuild in different environment + +* Thu Apr 4 2002 Jeremy Katz <katzj@redhat.com> +- update to 0.18 + +* Thu Mar 14 2002 Jeremy Katz <katzj@redhat.com> +- update to 0.17 + +* Thu Feb 21 2002 Jeremy Katz <katzj@redhat.com> +- rebuild in new environment + +* Tue Feb 12 2002 Havoc Pennington <hp@redhat.com> +- 0.15 +- remove dbm patch, dbm no longer used upstream +- shorten summary line, #56739 + +* Wed Jan 30 2002 Owen Taylor <otaylor@redhat.com> +- Version 0.14 +- Try again on DBM fix +- Patch to use AnyDBM_File rather than NDBM_File for caching +- Version 0.14 + +* Wed Jan 09 2002 Tim Powers <timp@redhat.com> +- automated rebuild + +* Wed Jan 2 2002 Havoc Pennington <hp@redhat.com> +- 0.12.90 cvs snap + +* Mon Nov 26 2001 Havoc Pennington <hp@redhat.com> +- 0.12 tarball + +* Sun Oct 28 2001 Havoc Pennington <hp@redhat.com> +- new cvs snap, no longer noarch + +* Fri Oct 5 2001 Havoc Pennington <hp@redhat.com> +- intltool specfile, based on xml-i18n-tools (but fixed up) +- obsolete/provide xml-i18n-tools + +* Tue Aug 14 2001 Alexander Larsson <alexl@redhat.com> 0.9-2 +- Require patch + +* Wed Aug 8 2001 Jonathan Blandford <jrb@redhat.com> +- Fix bug #45699 and #50634 by upgrading version. + +* Mon Jul 16 2001 Trond Eivind Glomsrød <teg@redhat.com> +- s/Copyright/License/ +- Shorter summary +- Remove empty post/postun scripts +- Don't define name and ver on the top and use this in the headers later + +* Tue Jul 10 2001 Tim Powers <timp@redhat.com> +- cleaned up files list so that there aren't non-standard dirs and so + that it owns the xml-i18n-tools dir + +* Tue Apr 17 2001 Jonathan Blandford <jrb@redhat.com> +- Cleaned up spec file a little for Red Hat. + +* Thu Mar 01 2001 Maciej Stachowiak <mjs@eazel.com> +- removed devel subpackage + +* Tue Jan 04 2000 Robin * Slomkowski <rslomkow@eazel.com> +- created this thing diff --git a/test/spec/lib-package-without-%mklibname.spec b/test/spec/lib-package-without-%mklibname.spec new file mode 100644 index 0000000..9f191d7 --- /dev/null +++ b/test/spec/lib-package-without-%mklibname.spec @@ -0,0 +1,36 @@ +Name: SpecCheck34 +Version: 0 +Release: 0 +Summary: None here + +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +SpecCheck test 34. + +%package -n %{libname} + +%prep + %autosetup + +%build + + +%install +rm -rf $RPM_BUILD_ROOT + + +%clean +rm -rf $RPM_BUILD_ROOT + + +%files +%defattr(-,root,root,-) +%{_libdir}/foo + + +%changelog diff --git a/test/spec/libdir-macro-in-noarch-package.spec b/test/spec/libdir-macro-in-noarch-package.spec new file mode 100644 index 0000000..6810a35 --- /dev/null +++ b/test/spec/libdir-macro-in-noarch-package.spec @@ -0,0 +1,28 @@ +Name: libdir-macro-in-noarch-package +Version: 0 +Release: 0 +Summary: libdir-macro-in-noarch-packagew warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildArch: noarch + +%description +The %{_libdir} or %{_lib} macro was found in a noarch package in a section +that gets included in binary packages. This is most likely an error because +these macros are expanded on the build host and their values vary between +architectures, probably resulting in a package that does not work properly +on all architectures at runtime. Investigate whether the package is really +architecture independent or if some other dir/macro should be instead. + +%prep + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/macro-in-changelog-autochangelog.spec b/test/spec/macro-in-changelog-autochangelog.spec new file mode 100644 index 0000000..08c54de --- /dev/null +++ b/test/spec/macro-in-changelog-autochangelog.spec @@ -0,0 +1,28 @@ +Name: macro-in-%changelog +Version: 0 +Release: 0 +Summary: macro-in-%changelog warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +Macros are expanded in %changelog too, which can in unfortunate cases lead +to the package not building at all, or other subtle unexpected conditions that +affect the build. Even when that doesn't happen, the expansion results in +possibly 'rewriting history' on subsequent package revisions and generally +odd entries eg. in source rpms, which is rarely wanted. Avoid use of macros +in %changelog altogether, or use two '%'s to escape them, like '%%foo'. + +%prep + +%build + +%install + +%files +%{_libdir}/foo + +%changelog +%autochangelog diff --git a/test/spec/macro-in-changelog.spec b/test/spec/macro-in-changelog.spec new file mode 100644 index 0000000..0964668 --- /dev/null +++ b/test/spec/macro-in-changelog.spec @@ -0,0 +1,28 @@ +Name: macro-in-%changelog +Version: 0 +Release: 0 +Summary: macro-in-%changelog warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +Macros are expanded in %changelog too, which can in unfortunate cases lead +to the package not building at all, or other subtle unexpected conditions that +affect the build. Even when that doesn't happen, the expansion results in +possibly 'rewriting history' on subsequent package revisions and generally +odd entries eg. in source rpms, which is rarely wanted. Avoid use of macros +in %changelog altogether, or use two '%'s to escape them, like '%%foo'. + +%prep + +%build + +%install + +%files +%{_libdir}/foo + +%changelog +You have a %buildroot macro diff --git a/test/spec/macro-in-comment.spec b/test/spec/macro-in-comment.spec new file mode 100644 index 0000000..07c115d --- /dev/null +++ b/test/spec/macro-in-comment.spec @@ -0,0 +1,28 @@ +Name: macro-in-comment +Version: 0 +Release: 0 +Summary: macro-in-comment-warning +Patch0: patch0.patch +License: GPL-2.0-only +Group: +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +There is a unescaped macro after a shell style comment in the specfile. +Macros are expanded everywhere, so check if it can cause a problem in this +case and escape the macro with another leading % if appropriate. + +%prep +%autopatch +%autosetup + +%build +# this is a comment %{version} + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/make-check-outside-check-section.spec b/test/spec/make-check-outside-check-section.spec new file mode 100644 index 0000000..158a74a --- /dev/null +++ b/test/spec/make-check-outside-check-section.spec @@ -0,0 +1,19 @@ +Name: make-check-outside-check-section +Version: 0 +Release: 0 +Summary: make-check-outside-check-section warning. +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} + +%description +Make check or other automated regression test should be run in %check, as +they can be disabled with a rpm macro for short circuiting purposes + +%prep + +make check + +%check + +%changelog diff --git a/test/spec/mixed-use-of-spaces-and-tabs.spec b/test/spec/mixed-use-of-spaces-and-tabs.spec new file mode 100644 index 0000000..62f883a --- /dev/null +++ b/test/spec/mixed-use-of-spaces-and-tabs.spec @@ -0,0 +1,33 @@ +Name: mixed-use-of-spaces-and-tabs +Version: 0 +Release: 0 +Summary: mixed-use-of-spaces-and-tabs warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Patch0: Patch0.patch +Requires: php + +%description +The specfile mixes use of spaces and tabs for indentation, which is a +cosmetic annoyance. + +%prep +cd lib + +%autopatch +%autosetup + +%build + +%install +%ifarch +# apply patch0 +%patch0 -p0 +%endif + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/more-than-one-%changelog.spec b/test/spec/more-than-one-%changelog.spec new file mode 100644 index 0000000..c6691f5 --- /dev/null +++ b/test/spec/more-than-one-%changelog.spec @@ -0,0 +1,26 @@ +Name: more-than-one-%changelog-section +Version: 0 +Release: 0 +Summary: more-than-one-%changelog-section +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +The spec file unnecessarily contains more than one %changelog section. + +%prep + %autosetup + +%build + +%install + +%files +%{_libdir}/foo + +%changelog +something. +%changelog +one another thing. diff --git a/test/spec/no-%%%s-section.spec b/test/spec/no-%%%s-section.spec new file mode 100644 index 0000000..4886a10 --- /dev/null +++ b/test/spec/no-%%%s-section.spec @@ -0,0 +1,32 @@ +Name: no-%%%s-section +Version: 0 +Release: 0 +Summary: no-%%%s-section warning +License: GPL-2.0-only +Group: Undefined + +%description +no-%prep-section:- + The spec file does not contain a %prep section. Even if some packages don't + directly need it, section markers may be overridden in rpm's configuration + to provide additional 'under the hood' functionality. Add the section, even + if empty. +no-%build-section:- + The spec file does not contain a %build section. Even if some packages + don't directly need it, section markers may be overridden in rpm's + configuration to provide additional 'under the hood' functionality, such as + injection of automatic -debuginfo subpackages. Add the section, even if + empty. +no-%install-section:- + The spec file does not contain an %install section. Even if some packages + don't directly need it, section markers may be overridden in rpm's + configuration to provide additional 'under the hood' functionality. Add the + section, even if empty. +no-%{clean}-section:- + The spec file doesn't contain a %{clean} section to remove the files installed + by the %install section + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/no-buildroot-tag.spec b/test/spec/no-buildroot-tag.spec new file mode 100644 index 0000000..ce0de73 --- /dev/null +++ b/test/spec/no-buildroot-tag.spec @@ -0,0 +1,20 @@ +Name: no-buildroot-tag +Version: 0 +Release: 0 +Summary: no-buildroot-tag warning + +Group: Undefined +License: GPLv2 + +%description +The BuildRoot tag isn't used in your spec. It must be used in order to +allow building the package as non root on some systems. For some rpm versions +(e.g. rpm.org >= 4.6) the BuildRoot tag is not necessary in specfiles and is +ignored by rpmbuild; if your package is only going to be built with such rpm +versions you can ignore this warning. + +%files +%defattr(-,root,root,-) +%{_libdir}/foo + +%changelog diff --git a/test/spec/nodejs-conflicts-without-prefix.spec b/test/spec/nodejs-conflicts-without-prefix.spec deleted file mode 100644 index 20a502e..0000000 --- a/test/spec/nodejs-conflicts-without-prefix.spec +++ /dev/null @@ -1,302 +0,0 @@ -%{?scl:%scl_package nodejs} -%{!?scl:%global pkg_name %{name}} - -Name: %{?scl_prefix}nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -%{?scl:Requires: %{scl}-runtime} -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: %{?scl_prefix}v8%{?isa} >= %{v8_ge} -Requires: %{?scl_prefix}v8%{?isa} < %{v8_lt} - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: %{?scl_prefix}nodejs(abi) = %{nodejs_abi} -Provides: %{?scl_prefix}nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q -n node-v%{version} - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs-good.spec b/test/spec/nodejs-good.spec deleted file mode 100644 index 4f1d8dc..0000000 --- a/test/spec/nodejs-good.spec +++ /dev/null @@ -1,302 +0,0 @@ -%{?scl:%scl_package nodejs} -%{!?scl:%global pkg_name %{name}} - -Name: %{?scl_prefix}nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -%{?scl:Requires: %{scl}-runtime} -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: %{?scl_prefix}v8%{?isa} >= %{v8_ge} -Requires: %{?scl_prefix}v8%{?isa} < %{v8_lt} - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: %{?scl_prefix}nodejs(abi) = %{nodejs_abi} -Provides: %{?scl_prefix}nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: %{?scl_prefix}node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q -n node-v%{version} - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs-n-noprefix.spec b/test/spec/nodejs-n-noprefix.spec deleted file mode 100644 index 128d08b..0000000 --- a/test/spec/nodejs-n-noprefix.spec +++ /dev/null @@ -1,302 +0,0 @@ -%{?scl:%scl_package nodejs} -%{!?scl:%global pkg_name %{name}} - -Name: %{?scl_prefix}nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -%{?scl:Requires: %{scl}-runtime} -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: %{?scl_prefix}v8%{?isa} >= %{v8_ge} -Requires: %{?scl_prefix}v8%{?isa} < %{v8_lt} - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: %{?scl_prefix}nodejs(abi) = %{nodejs_abi} -Provides: %{?scl_prefix}nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: %{?scl_prefix}node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package -n nodejs-devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q -n node-v%{version} - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs-name-with-noncondition-prefix.spec b/test/spec/nodejs-name-with-noncondition-prefix.spec deleted file mode 100644 index c52a103..0000000 --- a/test/spec/nodejs-name-with-noncondition-prefix.spec +++ /dev/null @@ -1,302 +0,0 @@ -%{?scl:%scl_package nodejs} -%{!?scl:%global pkg_name %{name}} - -Name: %{scl_prefix}nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -%{?scl:Requires: %{scl}-runtime} -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: %{?scl_prefix}v8%{?isa} >= %{v8_ge} -Requires: %{?scl_prefix}v8%{?isa} < %{v8_lt} - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: %{?scl_prefix}nodejs(abi) = %{nodejs_abi} -Provides: %{?scl_prefix}nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: %{?scl_prefix}node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q -n node-v%{version} - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs-name-without-prefix.spec b/test/spec/nodejs-name-without-prefix.spec deleted file mode 100644 index 429a209..0000000 --- a/test/spec/nodejs-name-without-prefix.spec +++ /dev/null @@ -1,302 +0,0 @@ -%{?scl:%scl_package nodejs} -%{!?scl:%global pkg_name %{name}} - -Name: nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -%{?scl:Requires: %{scl}-runtime} -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: %{?scl_prefix}v8%{?isa} >= %{v8_ge} -Requires: %{?scl_prefix}v8%{?isa} < %{v8_lt} - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: %{?scl_prefix}nodejs(abi) = %{nodejs_abi} -Provides: %{?scl_prefix}nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: %{?scl_prefix}node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q -n node-v%{version} - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs-no-pkg_name.spec b/test/spec/nodejs-no-pkg_name.spec deleted file mode 100644 index 174d709..0000000 --- a/test/spec/nodejs-no-pkg_name.spec +++ /dev/null @@ -1,301 +0,0 @@ -%{?scl:%scl_package nodejs} - -Name: %{?scl_prefix}nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -%{?scl:Requires: %{scl}-runtime} -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: %{?scl_prefix}v8%{?isa} >= %{v8_ge} -Requires: %{?scl_prefix}v8%{?isa} < %{v8_lt} - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: %{?scl_prefix}nodejs(abi) = %{nodejs_abi} -Provides: %{?scl_prefix}nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: %{?scl_prefix}node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q -n node-v%{version} - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs-norequire.spec b/test/spec/nodejs-norequire.spec deleted file mode 100644 index e2e5740..0000000 --- a/test/spec/nodejs-norequire.spec +++ /dev/null @@ -1,300 +0,0 @@ -%{?scl:%scl_package nodejs} -%{!?scl:%global pkg_name %{name}} - -Name: %{?scl_prefix}nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: libfoo - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: %{?scl_prefix}nodejs(abi) = %{nodejs_abi} -Provides: %{?scl_prefix}nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: %{?scl_prefix}node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q -n node-v%{version} - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs-provides-without-prefix.spec b/test/spec/nodejs-provides-without-prefix.spec deleted file mode 100644 index 332b3f1..0000000 --- a/test/spec/nodejs-provides-without-prefix.spec +++ /dev/null @@ -1,302 +0,0 @@ -%{?scl:%scl_package nodejs} -%{!?scl:%global pkg_name %{name}} - -Name: %{?scl_prefix}nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -%{?scl:Requires: %{scl}-runtime} -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: %{?scl_prefix}v8%{?isa} >= %{v8_ge} -Requires: %{?scl_prefix}v8%{?isa} < %{v8_lt} - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: nodejs(abi) = %{nodejs_abi} -Provides: nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: %{?scl_prefix}node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q -n node-v%{version} - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs-setup-no-n.spec b/test/spec/nodejs-setup-no-n.spec deleted file mode 100644 index 9c4ac4c..0000000 --- a/test/spec/nodejs-setup-no-n.spec +++ /dev/null @@ -1,302 +0,0 @@ -%{?scl:%scl_package nodejs} -%{!?scl:%global pkg_name %{name}} - -Name: %{?scl_prefix}nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -%{?scl:Requires: %{scl}-runtime} -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: %{?scl_prefix}v8%{?isa} >= %{v8_ge} -Requires: %{?scl_prefix}v8%{?isa} < %{v8_lt} - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: %{?scl_prefix}nodejs(abi) = %{nodejs_abi} -Provides: %{?scl_prefix}nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: %{?scl_prefix}node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs-undeclared.spec b/test/spec/nodejs-undeclared.spec deleted file mode 100644 index 7c2ee5d..0000000 --- a/test/spec/nodejs-undeclared.spec +++ /dev/null @@ -1,301 +0,0 @@ -%{!?scl:%global pkg_name %{name}} - -Name: %{?scl_prefix}nodejs -Version: 0.10.3 -Release: 3%{?dist} -Summary: JavaScript runtime -License: MIT and ASL 2.0 and ISC and BSD -Group: Development/Languages -URL: http://nodejs.org/ - -# Exclusive archs must match v8 -ExclusiveArch: %{ix86} x86_64 %{arm} - -Source0: http://nodejs.org/dist/v%{version}/node-v%{version}.tar.gz - -# V8 presently breaks ABI at least every x.y release while never bumping SONAME, -# so we need to be more explicit until spot fixes that -%global v8_ge 1:3.14.5.7 -%global v8_lt 1:3.15 -%global v8_abi 3.14 - -%{?scl:Requires: %{scl}-runtime} -BuildRequires: %{?scl_prefix}v8-devel >= %{v8_ge} -BuildRequires: %{?scl_prefix}http-parser-devel >= 2.0 -BuildRequires: %{?scl_prefix}libuv-devel -BuildRequires: %{?scl_prefix}c-ares-devel -BuildRequires: zlib-devel -# Node.js requires some features from openssl 1.0.1 for SPDY support -BuildRequires: openssl-devel - -Requires: %{?scl_prefix}v8%{?isa} >= %{v8_ge} -Requires: %{?scl_prefix}v8%{?isa} < %{v8_lt} - -#we need ABI virtual provides where SONAMEs aren't enough/not present so deps -#break when binary compatibility is broken -%global nodejs_abi 0.10 -Provides: %{?scl_prefix}nodejs(abi) = %{nodejs_abi} -Provides: %{?scl_prefix}nodejs(v8-abi) = %{v8_abi} - -#this corresponds to the "engine" requirement in package.json -Provides: %{?scl_prefix}nodejs(engine) = %{version} - -# Node.js currently has a conflict with the 'node' package in Fedora -# The ham-radio group has agreed to rename their binary for us, but -# in the meantime, we're setting an explicit Conflicts: %{?scl_prefix}here -Conflicts: %{?scl_prefix}node <= 0.3.2-11 - -%description -Node.js is a platform built on Chrome's JavaScript runtime -for easily building fast, scalable network applications. -Node.js uses an event-driven, non-blocking I/O model that -makes it lightweight and efficient, perfect for data-intensive -real-time applications that run across distributed devices. - -%package devel -Summary: JavaScript runtime - development headers -Group: Development/Languages -Requires: %{name} = %{version}-%{release} -Requires: %{?scl_prefix}libuv-devel %{?scl_prefix}http-parser-devel openssl-devel %{?scl_prefix}c-ares-devel zlib-devel - -%description devel -Development headers for the Node.js JavaScript runtime. - -%package docs -Summary: Node.js API documentation -Group: Documentation - -%description docs -The API documentation for the Node.js JavaScript runtime. - -%prep -%setup -q -n node-v%{version} - -# Make sure nothing gets included from bundled deps: -# We only delete the source and header files, because -# the remaining build scripts are still used. - -find deps/cares -name "*.c" -exec rm -f {} \; -find deps/cares -name "*.h" -exec rm -f {} \; - -find deps/npm -name "*.c" -exec rm -f {} \; -find deps/npm -name "*.h" -exec rm -f {} \; - -find deps/zlib -name "*.c" -exec rm -f {} \; -find deps/zlib -name "*.h" -exec rm -f {} \; - -find deps/v8 -name "*.c" -exec rm -f {} \; -find deps/v8 -name "*.h" -exec rm -f {} \; - -find deps/http_parser -name "*.c" -exec rm -f {} \; -find deps/http_parser -name "*.h" -exec rm -f {} \; - -find deps/openssl -name "*.c" -exec rm -f {} \; -find deps/openssl -name "*.h" -exec rm -f {} \; - -find deps/uv -name "*.c" -exec rm -f {} \; -find deps/uv -name "*.h" -exec rm -f {} \; - - -%build -# build with debugging symbols and add defines from libuv (#892601) -export CFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export CXXFLAGS='%{optflags} -g -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \ - -I%{_includedir}' -export LDFLAGS='%{optflags} -L%{_libdir}' - -./configure --prefix=%{_prefix} \ - --shared-v8 \ - --shared-openssl \ - --shared-zlib \ - --shared-cares \ - --shared-libuv \ - --shared-http-parser \ - --without-npm \ - --without-dtrace - -# Setting BUILDTYPE=Debug builds both release and debug binaries -make BUILDTYPE=Debug %{?_smp_mflags} - -%install -rm -rf %{buildroot} - -./tools/install.py install %{buildroot} - -# and remove dtrace file again -rm -rf %{buildroot}/%{_prefix}/lib/dtrace - -# Set the binary permissions properly -chmod 0755 %{buildroot}/%{_bindir}/node - -# Install the debug binary and set its permissions -install -Dpm0755 out/Debug/node %{buildroot}/%{_bindir}/node_g - -# own the sitelib directory -mkdir -p %{buildroot}%{_prefix}/lib/node_modules - -#install documentation -mkdir -p %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -cp -pr doc/* %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/html -rm -f %{_defaultdocdir}/%{pkg_name}-docs-%{version}/html/nodejs.1 -cp -p LICENSE %{buildroot}%{_defaultdocdir}/%{pkg_name}-docs-%{version}/ - -#install development headers -#FIXME: we probably don't really need *.h but node-gyp downloads the whole -#freaking source tree so I can't be sure ATM -mkdir -p %{buildroot}%{_includedir}/node -cp -p src/*.h %{buildroot}%{_includedir}/node - -#node-gyp needs common.gypi too -mkdir -p %{buildroot}%{_datadir}/node -cp -p common.gypi %{buildroot}%{_datadir}/node - -%files -%doc ChangeLog LICENSE README.md AUTHORS -%{_bindir}/node -%{_mandir}/man1/node.* -%dir %{_prefix}/lib/node_modules - -%files devel -%{_bindir}/node_g -%{_includedir}/node -%{_datadir}/node - -%files docs -%{_defaultdocdir}/%{pkg_name}-docs-%{version} - -%changelog -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 0.10.3-3 -- Add support for software collections -- Move rpm macros and tooling to separate package - -* Thu Apr 04 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-2 -- nodejs-symlink-deps: symlink unconditionally in the buildroot - -* Wed Apr 03 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.3-1 -- new upstream release 0.10.3 - http://blog.nodejs.org/2013/04/03/node-v0-10-3-stable/ -- nodejs-symlink-deps: only create symlink if target exists -- nodejs-symlink-deps: symlink devDependencies when --check is used - -* Sun Mar 31 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.2-1 -- new upstream release 0.10.2 - http://blog.nodejs.org/2013/03/28/node-v0-10-2-stable/ -- remove %%nodejs_arches macro since it will only be useful if it is present in - the redhat-rpm-config package -- add default filtering macro to remove unwanted Provides from native modules -- nodejs-symlink-deps now supports multiple modules in one SRPM properly -- nodejs-symlink-deps also now supports a --check argument that works in the - current working directry instead of the buildroot - -* Fri Mar 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.1-1 -- new upstream release 0.10.1 - http://blog.nodejs.org/2013/03/21/node-v0-10-1-stable/ - -* Wed Mar 20 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-4 -- fix escaping in dependency generator regular expressions (RHBZ#923941) - -* Wed Mar 13 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.10.0-3 -- add virtual ABI provides for node and v8 so binary module's deps break when - binary compatibility is broken -- automatically add matching Requires to nodejs binary modules -- add %%nodejs_arches macro to future-proof ExcluseArch stanza in dependent - packages - -* Tue Mar 12 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-2 -- Fix up documentation subpackage - -* Mon Mar 11 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.10.0-1 -- Update to stable 0.10.0 release -- https://raw.github.com/joyent/node/v0.10.0/ChangeLog - -* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.9.5-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Jan 22 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-10 -- minor bugfixes to RPM magic - - nodejs-symlink-deps: don't create an empty node_modules dir when a module - has no dependencies - - nodes-fixdep: support adding deps when none exist -- Add the full set of headers usually bundled with node as deps to nodejs-devel. - This way `npm install` for native modules that assume the stuff bundled with - node exists will usually "just work". --move RPM magic to nodejs-devel as requested by FPC - -* Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9 -- fix brown paper bag bug in requires generation script - -* Thu Jan 10 2013 Stephen Gallagher <sgallagh@redhat.com> - 0.9.5-8 -- Build debug binary and install it in the nodejs-devel subpackage - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-7 -- don't use make install since it rebuilds everything - -* Thu Jan 10 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-6 -- add %%{?isa}, epoch to v8 deps - -* Wed Jan 09 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-5 -- add defines to match libuv (#892601) -- make v8 dependency explicit (and thus more accurate) -- add -g to $C(XX)FLAGS instead of patching configure to add it -- don't write pointless 'npm(foo) > 0' deps - -* Sat Jan 05 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-4 -- install development headers -- add nodejs_sitearch macro - -* Wed Jan 02 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-3 -- make nodejs-symlink-deps actually work - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-2 -- provide nodejs-devel so modules can BuildRequire it (and be consistent - with other interpreted languages in the distro) - -* Tue Jan 01 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-1 -- new upstream release 0.9.5 -- provide nodejs-devel for the moment -- fix minor bugs in RPM magic -- add nodejs_fixdep macro so packagers can easily adjust dependencies in - package.json files - -* Wed Dec 26 2012 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.4-1 -- new upstream release 0.9.4 -- system library patches are now upstream -- respect optflags -- include documentation in subpackage -- add RPM dependency generation and related magic -- guard libuv depedency so it always gets bumped when nodejs does -- add -devel subpackage with enough to make node-gyp happy - -* Wed Dec 19 2012 Dan Horák <dan[at]danny.cz> - 0.9.3-8 -- set exclusive arch list to match v8 - -* Tue Dec 18 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-7 -- Add remaining changes from code review -- Remove unnecessary BuildRequires on findutils -- Remove %%clean section - -* Fri Dec 14 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-6 -- Fixes from code review -- Fix executable permissions -- Correct the License field -- Build debuginfo properly - -* Thu Dec 13 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-5 -- Return back to using the standard binary name -- Temporarily adding a conflict against the ham radio node package until they - complete an agreed rename of their binary. - -* Wed Nov 28 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-4 -- Rename binary and manpage to nodejs - -* Mon Nov 19 2012 Stephen Gallagher <sgallagh@redhat.com> - 0.9.3-3 -- Update to latest upstream development release 0.9.3 -- Include upstreamed patches to unbundle dependent libraries - -* Tue Oct 23 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.8.12-1 -- Fixes and Patches suggested by Matthias Runge - -* Mon Apr 09 2012 Adrian Alves <alvesadrian@fedoraproject.org> 0.6.5 -- First build. diff --git a/test/spec/nodejs010-alien-subpackage-n.spec b/test/spec/nodejs010-alien-subpackage-n.spec deleted file mode 100644 index 6f432f9..0000000 --- a/test/spec/nodejs010-alien-subpackage-n.spec +++ /dev/null @@ -1,131 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. -Requires: scl-utils - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%package -n hehe -Summary: Package that should not be here -Requires: bullshit - -%description hehe -This package should not be in SCL metapackage. - -%package build -Summary: Package shipping basic build configuration -Requires: scl-utils-build - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files - -%files runtime -%scl_files - -%files build -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010-alien-subpackage.spec b/test/spec/nodejs010-alien-subpackage.spec deleted file mode 100644 index 813ede9..0000000 --- a/test/spec/nodejs010-alien-subpackage.spec +++ /dev/null @@ -1,131 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. -Requires: scl-utils - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%package hehe -Summary: Package that should not be here -Requires: bullshit - -%description hehe -This package should not be in SCL metapackage. - -%package build -Summary: Package shipping basic build configuration -Requires: scl-utils-build - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files - -%files runtime -%scl_files - -%files build -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010-badfiles.spec b/test/spec/nodejs010-badfiles.spec deleted file mode 100644 index f4d5b61..0000000 --- a/test/spec/nodejs010-badfiles.spec +++ /dev/null @@ -1,123 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. -Requires: scl-utils - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%package build -Summary: Package shipping basic build configuration -Requires: scl-utils-build - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files -%scl_files - -%files runtime - -%files build -%{_rpmconfigdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010-missing-requires.spec b/test/spec/nodejs010-missing-requires.spec deleted file mode 100644 index ec42b21..0000000 --- a/test/spec/nodejs010-missing-requires.spec +++ /dev/null @@ -1,121 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%package build -Summary: Package shipping basic build configuration - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files - -%files runtime -%scl_files - -%files build -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010-noarch-good.spec b/test/spec/nodejs010-noarch-good.spec deleted file mode 100644 index 22c4177..0000000 --- a/test/spec/nodejs010-noarch-good.spec +++ /dev/null @@ -1,124 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ -BuildArch: noarch - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. -Requires: scl-utils - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%package build -Summary: Package shipping basic build configuration -Requires: scl-utils-build - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files - -%files runtime -%scl_files - -%files build -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010-noarch-libdir.spec b/test/spec/nodejs010-noarch-libdir.spec deleted file mode 100644 index 041dc47..0000000 --- a/test/spec/nodejs010-noarch-libdir.spec +++ /dev/null @@ -1,125 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ -BuildArch: noarch - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. -Requires: scl-utils - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%package build -Summary: Package shipping basic build configuration -Requires: scl-utils-build - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files - -%files runtime -%scl_files - -%files build -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010-nobuild.spec b/test/spec/nodejs010-nobuild.spec deleted file mode 100644 index a610e99..0000000 --- a/test/spec/nodejs010-nobuild.spec +++ /dev/null @@ -1,115 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. -Requires: scl-utils - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files - -%files runtime -%scl_files -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010-noruntime.spec b/test/spec/nodejs010-noruntime.spec deleted file mode 100644 index 0f8b5ec..0000000 --- a/test/spec/nodejs010-noruntime.spec +++ /dev/null @@ -1,115 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package build -Summary: Package shipping basic build configuration -Requires: scl-utils-build - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files -%scl_files - -%files build -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010-nosclinstall.spec b/test/spec/nodejs010-nosclinstall.spec deleted file mode 100644 index 313c94c..0000000 --- a/test/spec/nodejs010-nosclinstall.spec +++ /dev/null @@ -1,122 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. -Requires: scl-utils - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%package build -Summary: Package shipping basic build configuration -Requires: scl-utils-build - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%files - -%files runtime -%scl_files - -%files build -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010-undeclared.spec b/test/spec/nodejs010-undeclared.spec deleted file mode 100644 index e3e9de4..0000000 --- a/test/spec/nodejs010-undeclared.spec +++ /dev/null @@ -1,123 +0,0 @@ -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. -Requires: scl-utils - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%package build -Summary: Package shipping basic build configuration -Requires: scl-utils-build - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files - -%files runtime -%scl_files - -%files build -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/nodejs010.spec b/test/spec/nodejs010.spec deleted file mode 100644 index 1d1a492..0000000 --- a/test/spec/nodejs010.spec +++ /dev/null @@ -1,124 +0,0 @@ -%global scl nodejs010 -%scl_package %scl -%global install_scl 1 - -Summary: %scl Software Collection -Name: %scl_name -Version: 1 -Release: 7%{?dist} - -Source1: macros.nodejs -Source2: nodejs.attr -Source3: nodejs.prov -Source4: nodejs.req -Source5: nodejs-symlink-deps -Source6: nodejs-fixdep -Source7: nodejs_native.attr - -License: GPLv2+ - -%if 0%{?install_scl} -Requires: %{scl_prefix}nodejs -%endif - -BuildRequires: scl-utils-build -BuildRequires: python-devel - -%description -This is the main package for %scl Software Collection. - -%package runtime -Summary: Package that handles %scl Software Collection. -Requires: scl-utils - -%description runtime -Package shipping essential scripts to work with %scl Software Collection. - -%package build -Summary: Package shipping basic build configuration -Requires: scl-utils-build - -%description build -Package shipping essential configuration macros to build %scl Software Collection. - -%prep -%setup -c -T - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}%{_scl_scripts}/root -cat >> %{buildroot}%{_scl_scripts}/enable << EOF -export PATH=%{_bindir}:\$PATH -export LD_LIBRARY_PATH=%{_libdir}:\$LD_LIBRARY_PATH -export PYTHONPATH=%{_scl_root}%{python_sitelib}:\$PYTHONPATH -EOF - -# install rpm magic -install -Dpm0644 %{SOURCE1} %{buildroot}%{_root_sysconfdir}/rpm/macros.%{name} -install -Dpm0644 %{SOURCE2} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}.attr -install -pm0755 %{SOURCE3} %{buildroot}%{_rpmconfigdir}/%{name}.prov -install -pm0755 %{SOURCE4} %{buildroot}%{_rpmconfigdir}/%{name}.req -install -pm0755 %{SOURCE5} %{buildroot}%{_rpmconfigdir}/%{name}-symlink-deps -install -pm0755 %{SOURCE6} %{buildroot}%{_rpmconfigdir}/%{name}-fixdep -install -Dpm0644 %{SOURCE7} %{buildroot}%{_rpmconfigdir}/fileattrs/%{name}_native.attr - - -# ensure Requires are added to every native module that match the Provides from -# the nodejs build in the buildroot -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}_native.req -#!/bin/sh -echo 'nodejs010-nodejs(abi) = %nodejs_abi' -echo 'nodejs010-nodejs(v8-abi) = %v8_abi' -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}_native.req - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-require.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.req $* -%{_rpmconfigdir}/find-requires $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-require.sh - -cat << EOF > %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh -#!/bin/sh -%{_rpmconfigdir}/%{name}.prov $* -%{_rpmconfigdir}/find-provides $* -EOF -chmod 0755 %{buildroot}%{_rpmconfigdir}/%{name}-provide.sh - -%scl_install - -%files - -%files runtime -%scl_files - -%files build -%{_root_sysconfdir}/rpm/macros.%{scl}-config -%{_root_sysconfdir}/rpm/macros.%{name} -%{_rpmconfigdir}/fileattrs/%{name}*.attr -%{_rpmconfigdir}/%{name}* - - -%changelog -* Mon Apr 15 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-7 -- Update macros and requires/provides generator to latest - -* Wed Apr 10 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-6 -- Fix rpm requires/provides generator paths -- Add requires to main meta package - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-5 -- Make package architecture specific for libdir usage - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-4 -- Add rpm macros and tooling - -* Mon Apr 08 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-3 -- Add proper scl-utils-build requires - -* Fri Apr 05 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-2 -- Add PYTHONPATH to configuration - -* Tue Mar 26 2013 Stanislav Ochotnicky <sochotnicky@redhat.com> - 1-1 -- Initial version of the Node.js Software Collection diff --git a/test/spec/non-break-space.spec b/test/spec/non-break-space.spec new file mode 100644 index 0000000..662944f --- /dev/null +++ b/test/spec/non-break-space.spec @@ -0,0 +1,24 @@ +Name: non-break-space +Version: 0 +Release: 0 +Summary: non-break-space warning. +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +The spec file contains a non-break space, which looks like a regular space +in some editors but can lead to obscure errors. It should be replaced by a +regular space. + +%prep + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/non-standard-group.spec b/test/spec/non-standard-group.spec new file mode 100644 index 0000000..c8ed734 --- /dev/null +++ b/test/spec/non-standard-group.spec @@ -0,0 +1,31 @@ +Name: non-standard-group +Version: 1.0 +Release: 0 +Summary: non-standard-group warning +License: MIT +Group: Something +URL: https://www.example.com +Source: Source.tar.gz +BuildRequires: gcc + +%description +A test specfile with Group (Group: Something) that is not standard. The value of the Group tag in the package is not valid. + +%prep +%setup -q + +%build +%configure +%make_build + +%install +%make_install + +%post +%postun + +%files +%license COPYING +%doc ChangeLog README + +%changelog diff --git a/test/spec/non-utf8-spec-file.spec b/test/spec/non-utf8-spec-file.spec Binary files differnew file mode 100644 index 0000000..d877f30 --- /dev/null +++ b/test/spec/non-utf8-spec-file.spec diff --git a/test/spec/null-char-first.spec b/test/spec/null-char-first.spec Binary files differnew file mode 100644 index 0000000..89e3d49 --- /dev/null +++ b/test/spec/null-char-first.spec diff --git a/test/spec/null-char-last.spec b/test/spec/null-char-last.spec Binary files differnew file mode 100644 index 0000000..d27b59e --- /dev/null +++ b/test/spec/null-char-last.spec diff --git a/test/spec/obsolete-tag.spec b/test/spec/obsolete-tag.spec new file mode 100644 index 0000000..ae3cc6c --- /dev/null +++ b/test/spec/obsolete-tag.spec @@ -0,0 +1,26 @@ +Name: obsolete-tag +Version: 0 +Release: 0 +Summary: obsolete-tag warning. +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Serial: 2 +Copyright: Something + +%description +The following tags are obsolete: Copyright and Serial. They must +be replaced by License and Epoch respectively. + +%prep + %autosetup + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/patch-fuzz-is-changed.spec b/test/spec/patch-fuzz-is-changed.spec new file mode 100644 index 0000000..827b382 --- /dev/null +++ b/test/spec/patch-fuzz-is-changed.spec @@ -0,0 +1,34 @@ +%define _default_patch_fuzz 2 +Name: patch-fuzz-is-changed +Version: 1.0 +Release: 0 +Summary: patch-fuzz-is-changed warning +License: MIT +URL: https://www.example.com +Source: Source.tar.gz +BuildRequires: gcc + +%description +The internal patch fuzz value was changed, and could hide patchs issues, or +could lead to applying a patch at the wrong location. Usually, this is often +the sign that someone didn't check if a patch is still needed and do not want +to rediff it. It is usually better to rediff the patch and try to send it + +%prep +%setup -q + +%build +%configure +%make_build + +%install +%make_install + +%post +%postun + +%files +%license COPYING +%doc ChangeLog README + +%changelog diff --git a/test/spec/patch-not-applied.spec b/test/spec/patch-not-applied.spec new file mode 100644 index 0000000..34fe2b9 --- /dev/null +++ b/test/spec/patch-not-applied.spec @@ -0,0 +1,23 @@ +Name: patch-not-applied +Version: 0 +Release: 0 +Summary: invalid-url warning +License: GPL-2.0-only +Group: Undefined +Patch0: Patch.patch +Patch1: Patch1.patch + +%description +A patch is included in your package but was not applied. + +%prep + +%build + +%install +%patch -P + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/prereq_use.spec b/test/spec/prereq_use.spec new file mode 100644 index 0000000..89de7b6 --- /dev/null +++ b/test/spec/prereq_use.spec @@ -0,0 +1,35 @@ +Name: prereq_use +Version: 0 +Release: 0 +Summary: prereq_use warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Patch0: Patch0.patch +PreReq(pre): none +PreReq(post): none_other + +%description +The use of PreReq is deprecated. In the majority of cases, a plain Requires +is enough and the right thing to do. Sometimes Requires(pre), Requires(post), +Requires(preun) and/or Requires(postun) can also be used instead of PreReq. + +%prep +cd lib + +%autopatch +%autosetup + +%build + +%install +%ifarch +# apply patch0 +%patch0 -p0 +%endif + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/python-module-def.spec b/test/spec/python-module-def.spec new file mode 100644 index 0000000..dfa9799 --- /dev/null +++ b/test/spec/python-module-def.spec @@ -0,0 +1,36 @@ +%{?!python_module:%define python_module() python-%{**} python3-%{**}} + +Name: python-module-def +Version: 1.0 +Release: 0 +Summary: python-module-def warning +License: MIT +URL: https://www.example.com +Source: Source.tar.gz +BuildRequires: gcc +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +A test specfile with python setup.py test that is deprecated. + +%prep +%setup -q + +%build +%configure +%make_build + +%install +%make_install + +%check +%pytest + +%post +%postun + +%files +%license COPYING +%doc ChangeLog README + +%changelog diff --git a/test/spec/python-setup-test.spec b/test/spec/python-setup-test.spec new file mode 100644 index 0000000..1a0fcf1 --- /dev/null +++ b/test/spec/python-setup-test.spec @@ -0,0 +1,34 @@ +Name: python-setup-test +Version: 1.0 +Release: 0 +Summary: python-setup-test warning +License: MIT +URL: https://www.example.com +Source: Source.tar.gz +BuildRequires: gcc +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +A test specfile with python setup.py test that is deprecated. + +%prep +%setup -q + +%build +%configure +%make_build + +%install +%make_install + +%check +%python_exec setup.py test + +%post +%postun + +%files +%license COPYING +%doc ChangeLog README + +%changelog diff --git a/test/spec/python-sitearch-glob.spec b/test/spec/python-sitearch-glob.spec new file mode 100644 index 0000000..a65f30d --- /dev/null +++ b/test/spec/python-sitearch-glob.spec @@ -0,0 +1,45 @@ +# +# spec file for package python-sitearch-glob +# +# Copyright (c) specCURRENT_YEAR SUSE LINUX GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + + +Name: python-sitearch-glob +Version: 0.1 +Release: 0 +Summary: Cool Python package +License: GPL-2.0-or-later +# FIXME: use correct group, see "https://en.opensuse.org/openSUSE:Package_group_guidelines" +Group: Development/Libraries/Python +Url: http://rpmlint.zarb.org/#%{name} +BuildRequires: python-rpm-macros +%{python_subpackages} + +%description +Cool Python Package + +%prep +%autosetup -p1 + +%build +%python_build + +%install +%python_install + +%files %{python_files} +%{python_sitearch}/* + +%changelog diff --git a/test/spec/python-sitelib-glob.spec b/test/spec/python-sitelib-glob.spec new file mode 100644 index 0000000..9ff1b52 --- /dev/null +++ b/test/spec/python-sitelib-glob.spec @@ -0,0 +1,46 @@ +# +# spec file for package python-sitelib-glob +# +# Copyright (c) specCURRENT_YEAR SUSE LINUX GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + + +Name: python-sitelib-glob +Version: 0.1 +Release: 0 +Summary: Cool Python package +License: GPL-2.0-or-later +# FIXME: use correct group, see "https://en.opensuse.org/openSUSE:Package_group_guidelines" +Group: Development/Libraries/Python +Url: http://rpmlint.zarb.org/#%{name} +BuildRequires: python-rpm-macros +%{python_subpackages} + +%description +Cool Python Package + +%prep +%autosetup -p1 + +%build +%python_build + +%install +%python_install + +%files %{python_files} +%{python_sitelib}/* + +%changelog + diff --git a/test/spec/python-sitelib.spec b/test/spec/python-sitelib.spec new file mode 100644 index 0000000..97a8e38 --- /dev/null +++ b/test/spec/python-sitelib.spec @@ -0,0 +1,48 @@ +# +# spec file for package python-sitelib +# +# Copyright (c) specCURRENT_YEAR SUSE LINUX GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + + +Name: python-sitelib +Version: 0.1 +Release: 0 +Summary: Cool Python package +License: GPL-2.0-or-later +# FIXME: use correct group, see "https://en.opensuse.org/openSUSE:Package_group_guidelines" +Group: Development/Libraries/Python +Url: http://rpmlint.zarb.org/#%{name} +BuildRequires: python-rpm-macros +%{python_subpackages} + +%description +Cool Python Package + +%prep +%autosetup -p1 + +%build +%python_build + +%install +%python_install + +%files %{python_files} +%{python_sitelib}/sitelib +%{python_sitelib}/sitelib/* +%{python_sitelib}/sitelib-%{version}*-info + +%changelog + diff --git a/test/spec/rpm-buildroot-usage-shell-var.spec b/test/spec/rpm-buildroot-usage-shell-var.spec new file mode 100644 index 0000000..1950923 --- /dev/null +++ b/test/spec/rpm-buildroot-usage-shell-var.spec @@ -0,0 +1,33 @@ +Name: rpm-buildroot-usage-shell-var +Version: 0 +Release: 0 +Summary: rpm-buildroot-usage warning (when referenced as shell variable). +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +$RPM_BUILD_ROOT should not be touched during %build or %prep stage, as it +may break short circuit builds. + +%prep +# None of these actually refer to the build root +\$RPM_BUILD_ROOT +\\\$RPM_BUILD_ROOT +# $RPM_BUILD_ROOT + +%build +\\$RPM_BUILD_ROOT +echo ${RPM_BUILD_ROOT} # comment + +%install + +%clean + +%files +%defattr(-,root,root,-) +%{_libdir}/foo + +%changelog diff --git a/test/spec/rpm-buildroot-usage.spec b/test/spec/rpm-buildroot-usage.spec new file mode 100644 index 0000000..2572ca7 --- /dev/null +++ b/test/spec/rpm-buildroot-usage.spec @@ -0,0 +1,26 @@ +Name: rpm-buildroot-usage +Version: 0 +Release: 0 +Summary: rpm-buildroot-usage warning. +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +$RPM_BUILD_ROOT should not be touched during %build or %prep stage, as it +may break short circuit builds. + +%prep +%buildroot + +%install + +%clean + +%files +%defattr(-,root,root,-) +%{_libdir}/foo + +%changelog diff --git a/test/spec/setup-not-in-prep.spec b/test/spec/setup-not-in-prep.spec new file mode 100644 index 0000000..e54d7d4 --- /dev/null +++ b/test/spec/setup-not-in-prep.spec @@ -0,0 +1,23 @@ +Name: setup-not-in-prep +Version: 0 +Release: 0 +Summary: setup-not-in-prep warning +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +The %setup macro should only be used within the %prep section because it may +not expand to anything outside of it and can break the build in unpredictable. + +%setup + +%prep + +%build + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/setup-not-quiet.spec b/test/spec/setup-not-quiet.spec new file mode 100644 index 0000000..951951a --- /dev/null +++ b/test/spec/setup-not-quiet.spec @@ -0,0 +1,25 @@ +Name: setup-not-quiet +Version: 0 +Release: 0 +Summary: setup-not-quiet warning. +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +Use the -q option to the %setup macro to avoid useless build output from +unpacking the sources. + +%prep + +%setup + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/unversioned-explicit-obsoletes.spec b/test/spec/unversioned-explicit-obsoletes.spec new file mode 100644 index 0000000..a594609 --- /dev/null +++ b/test/spec/unversioned-explicit-obsoletes.spec @@ -0,0 +1,28 @@ +Name: unversioned-explicit-obsoletes +Version: 0 +Release: 0 +Summary: unversioned-explicit-obsoletes +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +Obsoletes: Something + +%description +The specfile contains an unversioned Obsoletes: token, which will match all +older, equal and newer versions of the obsoleted thing. This may cause update +problems, restrict future package/provides naming, and may match something it +was originally not inteded to match -- make the Obsoletes versioned if +possible. + +%prep + %autosetup + +%build + +%install + +%files +%{_libdir}/foo + +%changelog diff --git a/test/spec/unversioned-explicit-version.spec b/test/spec/unversioned-explicit-version.spec new file mode 100644 index 0000000..de379cf --- /dev/null +++ b/test/spec/unversioned-explicit-version.spec @@ -0,0 +1,34 @@ +Name: unversioned-explicit-provides +Version: 0 +Release: 0 +Summary: unversioned-explicit-provides warning. + +Group: Undefined +License: GPLv2 +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +Provides: someones-something=%{version} + +%description +The specfile contains an unversioned Provides: token, which will match all +older, equal, and newer versions of the provided thing. This may cause +update problems and will make versioned dependencies, obsoletions and conflicts +on the provided thing useless -- make the Provides versioned if possible. + +%prep + %autosetup + +%build + +%install +rm -rf $RPM_BUILD_ROOT + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +%{_libdir}/foo + +%changelog diff --git a/test/spec/use-of-RPM-SOURCE-DIR.spec b/test/spec/use-of-RPM-SOURCE-DIR.spec new file mode 100644 index 0000000..870143e --- /dev/null +++ b/test/spec/use-of-RPM-SOURCE-DIR.spec @@ -0,0 +1,27 @@ +Name: use-of-RPM-SOURCE-DIR +Version: 0 +Release: 0 +Summary: use-of-RPM-SOURCE-DIR error +License: GPL-2.0-only +Group: Undefined +URL: http://rpmlint.zarb.org/#%{name} +Source0: Source0.tar.gz + +%description +You use $RPM_SOURCE_DIR or %{_sourcedir} in your spec file. If you have to +use a directory for building, use %{buildroot} instead. + +%prep + %autosetup + +%build + +%{_sourcedir} + +%install +rm -rf $RPM_SOURCE_DIR + +%files +%{_libdir}/foo + +%changelog diff --git a/test/test.CheckInclude.py b/test/test.CheckInclude.py deleted file mode 100644 index 29b1077..0000000 --- a/test/test.CheckInclude.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- - -import re -import unittest - -import SpecCheck -import Testing - - -class TestCheckInclude(unittest.TestCase): - - def setUp(self): - self.pkg = Testing.getTestedPackage('source/CheckInclude') - Testing.startTest() - - def testcheck(self): - SpecCheck.check.check_source(self.pkg) - out = "\n".join(Testing.getOutput()) - self.assertFalse(re.search(r" E: specfile-error error: query of specfile .*\.spec failed, can't parse", out)) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test.PamCheck.py b/test/test.PamCheck.py deleted file mode 100644 index a1a7474..0000000 --- a/test/test.PamCheck.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -import PamCheck -import Testing -# FIXME harcode - - -class TestPamCheck(unittest.TestCase): - - def setUp(self): - self.pkg = Testing.getTestedPackage('binary/PamCheck') - Testing.startTest() - - def tearDown(self): - self.pkg.cleanup() - - def testcheck(self): - PamCheck.check.check(self.pkg) - self.assertEqual(Testing.getOutput(), - ['PamCheck.i586: E: use-old-pam-stack ' - '/etc/pam.d/PamCheck (line 1)']) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test.Pkg.py b/test/test.Pkg.py deleted file mode 100644 index 35a9eac..0000000 --- a/test/test.Pkg.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -import rpm - -import Pkg -import Testing - - -class TestPkg(unittest.TestCase): - - def setUp(self): - Testing.startTest() - - def test_parse_deps(self): - for (arg, exp) in ( - ("a, b < 1.0 c = 5:2.0-3 d", - [("a", 0, (None, None, None)), - ("b", rpm.RPMSENSE_LESS, (None, "1.0", None)), - ("c", rpm.RPMSENSE_EQUAL, ("5", "2.0", "3")), - ("d", 0, (None, None, None))]), - ): - self.assertEqual(Pkg.parse_deps(arg), exp) - - def test_rangeCompare(self): - for (req, prov) in ( - (("foo", rpm.RPMSENSE_LESS, (None, "1.0", None)), - ("foo", rpm.RPMSENSE_EQUAL, ("1", "0.5", None))), - ): - self.assertFalse(Pkg.rangeCompare(req, prov)) - - def test_b2s(self): - for thing in ("foo", ["foo"], None, []): - self.assertEqual(thing, Pkg.b2s(thing)) - self.assertEqual("foo", Pkg.b2s(b"foo")) - self.assertEqual(["foo"], Pkg.b2s([b"foo"])) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test.SpecCheck.py b/test/test.SpecCheck.py deleted file mode 100644 index 1f04354..0000000 --- a/test/test.SpecCheck.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- - -import re -import unittest - -import SpecCheck -import Testing - - -class TestSpecCheck(unittest.TestCase): - - def setUp(self): - self.pkg = Testing.getTestedSpecPackage('spec/SpecCheck') - Testing.startTest() - - def testcheck(self): - SpecCheck.check.check_spec(self.pkg, self.pkg.name) - out = "\n".join(Testing.getOutput()) - self.assertTrue("patch-not-applied Patch3" in out) - self.assertFalse(re.search(r"patch-not-applied Patch\b", out)) - self.assertFalse(re.search("patch-not-applied Patch[0124567]", out)) - self.assertTrue("libdir-macro-in-noarch-package" not in out) - self.assertEqual(len(re.findall("macro-in-comment", out)), 1) - self.assertTrue("unversioned-explicit-provides unversioned-provides" - in out) - self.assertTrue("unversioned-explicit-provides versioned-provides" - not in out) - self.assertTrue("unversioned-explicit-provides /" not in out) - self.assertTrue("unversioned-explicit-obsoletes unversioned-obsoletes" - in out) - self.assertTrue("unversioned-explicit-obsoletes versioned-obsoletes" - not in out) - self.assertTrue("unversioned-explicit-obsoletes /" not in out) - self.assertTrue("setup-not-quiet" in out) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test.SpecCheck2.py b/test/test.SpecCheck2.py deleted file mode 100644 index 42faabd..0000000 --- a/test/test.SpecCheck2.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -import SpecCheck -import Testing - - -class TestSpecCheck(unittest.TestCase): - - def setUp(self): - self.pkg = Testing.getTestedSpecPackage('spec/SpecCheck2') - Testing.startTest() - - def testcheck(self): - SpecCheck.check.check_spec(self.pkg, self.pkg.name) - out = "\n".join(Testing.getOutput()) - self.assertFalse("patch-not-applied" in out) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test.SpecCheck3.py b/test/test.SpecCheck3.py deleted file mode 100644 index f355a47..0000000 --- a/test/test.SpecCheck3.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -import SpecCheck -import Testing - - -class TestSpecCheck(unittest.TestCase): - - def setUp(self): - self.pkg = Testing.getTestedSpecPackage('spec/SpecCheck3') - Testing.startTest() - - def testcheck(self): - SpecCheck.check.check_spec(self.pkg, self.pkg.name) - out = "\n".join(Testing.getOutput()) - self.assertFalse("patch-not-applied" in out) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test.config b/test/test.config deleted file mode 100644 index a27bee0..0000000 --- a/test/test.config +++ /dev/null @@ -1,23 +0,0 @@ -# -*- python -*- - -from Config import setOption - -bad_crypto_warning = \ -'''This application package calls a function to explicitly set crypto ciphers -for SSL/TLS. That may cause the application not to use the system-wide set -cryptographic policy and should be modified in accordance to: -https://fedoraproject.org/wiki/Packaging:CryptoPolicies''' -call_blacklist = {'crypto-policy-non-compliance-openssl' : - {'f_name' : 'SSL_CTX_set_cipher_list', - 'description' : bad_crypto_warning}, - 'crypto-policy-non-compliance-gnutls-1' : - {'f_name' : 'gnutls_priority_set_direct', - 'description' : bad_crypto_warning}, - 'crypto-policy-non-compliance-gnutls-2' : - {'f_name' : 'gnutls_priority_init', - 'good_param' : 'SYSLOG', - 'description' : bad_crypto_warning} - } -setOption("WarnOnFunction", call_blacklist) -setOption("Distribution", "Fedora Project") -setOption("Vendor", "Fedora Project") diff --git a/test/test_FHS.py b/test/test_FHS.py new file mode 100644 index 0000000..81ea460 --- /dev/null +++ b/test/test_FHS.py @@ -0,0 +1,31 @@ +import pytest +from rpmlint.checks.FHSCheck import FHSCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def fhscheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = FHSCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/non-fhs']) +def test_FHS_compliance(tmp_path, package, fhscheck): + """ + Check that the directories are not FHS compliant. + """ + output, test = fhscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + + # Check invalid /usr subdirectory + assert 'non-standard-dir-in-usr' in out + assert 'sbin' in out + + # Check invalid /var subdirectory + assert 'non-standard-dir-in-var' in out + assert 'lib' in out diff --git a/test/test_LSB.py b/test/test_LSB.py new file mode 100644 index 0000000..d5fbb57 --- /dev/null +++ b/test/test_LSB.py @@ -0,0 +1,35 @@ +import pytest +from rpmlint.checks.LSBCheck import LSBCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def lsbcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = LSBCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/fPing']) +def test_LSB_compliance(tmp_path, package, lsbcheck): + """ + Check that the package name, version and release number are LSB compliant. + """ + output, test = lsbcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + + # Check invalid package name + assert 'non-lsb-compliant-package-name' in out + assert 'package name contains an illegal character' in out + + # Check invalid package version + assert 'non-lsb-compliant-version' in out + assert 'version number contains an illegal character' in out + + # Check invalid package release + assert 'non-lsb-compliant-release' in out + assert 'release number contains an illegal character' in out diff --git a/test/test_alternatives.py b/test/test_alternatives.py new file mode 100644 index 0000000..e12669e --- /dev/null +++ b/test/test_alternatives.py @@ -0,0 +1,87 @@ +import pytest +from rpmlint.checks.AlternativesCheck import AlternativesCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def alternativescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = AlternativesCheck(CONFIG, output) + return output, test + + +# +# udpate-alternatives tests +# + + +@pytest.mark.parametrize('package', ['binary/alternatives-ok']) +def test_update_alternative_ok(tmp_path, package, alternativescheck): + output, test = alternativescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'I: package-supports-update-alternatives' in out + assert 'E' not in out + assert 'W' not in out + + +@pytest.mark.parametrize('package', ['binary/alternatives-borked']) +def test_update_alternative_borked(tmp_path, package, alternativescheck): + output, test = alternativescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: update-alternatives-requirement-missing' in out + assert 'E: alternative-generic-name-not-symlink' in out + assert 'E: alternative-link-not-ghost' in out + assert 'E: update-alternatives-postun-call-missing' in out + + +@pytest.mark.parametrize('package', ['binary/self']) +def test_non_update_alternative_pkg(tmp_path, package, alternativescheck): + output, test = alternativescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # here we just check if there is no requirements checking on + # non update-alternatived package + assert 'E' not in out + assert 'W' not in out + + +@pytest.mark.parametrize('package', ['binary/python39-evtx']) +def test_update_alternatives_correctness(tmp_path, package, alternativescheck): + output, test = alternativescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: update-alternatives-postun-call-missing' not in out + + +# +# libalternatives tests +# + + +@pytest.mark.parametrize('package', ['binary/libalternatives-ok']) +def test_libalternative_ok(tmp_path, package, alternativescheck): + output, test = alternativescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'I: package-supports-libalternatives' in out + assert 'E' not in out + assert 'W' not in out + + +@pytest.mark.parametrize('package', ['binary/libalternatives-borked']) +def test_libalternative_borked(tmp_path, package, alternativescheck): + output, test = alternativescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'I: package-supports-libalternatives' in out + assert 'I: libalternatives-conf-not-found' in out + assert 'E: alts-requirement-missed' in out + assert 'E: libalternatives-directory-not-exist' in out + assert 'E: empty-libalternatives-directory' in out + assert 'W: man-entry-value-not-found' in out + assert 'W: binary-entry-value-not-found' in out diff --git a/test/test_appdata.py b/test/test_appdata.py new file mode 100644 index 0000000..eac5936 --- /dev/null +++ b/test/test_appdata.py @@ -0,0 +1,38 @@ +from unittest.mock import patch + +import pytest +from rpmlint.checks.AppDataCheck import AppDataCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package, HAS_APPSTREAM_GLIB + + +@pytest.fixture(scope='function', autouse=True) +def appdatacheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = AppDataCheck(CONFIG, output) + return output, test + + +@pytest.mark.skipif(not HAS_APPSTREAM_GLIB, reason='Optional dependency appstream-glib not installed') +@pytest.mark.parametrize('package', ['binary/appdata']) +def test_appdata_fail(tmp_path, package, appdatacheck): + output, test = appdatacheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # there are two borked packages + assert len(output.results) == 2 + assert 'invalid-appdata-file' in out + + +@pytest.mark.parametrize('package', ['binary/appdata']) +@patch('rpmlint.checks.AppDataCheck.AppDataCheck.cmd', 'command-really-not-found') +def test_appdata_fail_no_checker(tmp_path, package, appdatacheck): + output, test = appdatacheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # there is just one borked file as the other is invalid content + # but valid xml + assert len(output.results) == 1 + assert 'invalid-appdata-file' in out diff --git a/test/test_bashisms.py b/test/test_bashisms.py new file mode 100644 index 0000000..f790e62 --- /dev/null +++ b/test/test_bashisms.py @@ -0,0 +1,35 @@ +import pytest +from rpmlint.checks.BashismsCheck import BashismsCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package, HAS_CHECKBASHISMS, HAS_DASH + + +@pytest.fixture(scope='function', autouse=True) +def bashismscheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = BashismsCheck(CONFIG, output) + return output, test + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('package', ['binary/bashisms']) +def test_bashisms(tmp_path, package, bashismscheck): + output, test = bashismscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: potential-bashisms /bin/script1' in out + assert 'W: bin-sh-syntax-error /bin/script2' in out + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('package', ['binary/bashisms']) +def test_bashisms_error(tmp_path, package, bashismscheck): + output, test = bashismscheck + package = get_tested_package(package, tmp_path) + package.dirname = 'I-do-not-exist-for-sure' + with pytest.raises(FileNotFoundError): + test.check(package) diff --git a/test/test_binaries.py b/test/test_binaries.py index c57971b..233a2e4 100644 --- a/test/test_binaries.py +++ b/test/test_binaries.py @@ -1,26 +1,289 @@ -import os +import pytest +from rpmlint.checks.BinariesCheck import BinariesCheck +from rpmlint.filter import Filter -import BinariesCheck -import Testing +from Testing import CONFIG, Config, get_tested_mock_package, get_tested_package, IS_X86_64, TEST_CONFIG -class TestForbiddenCCalls(Testing.OutputTest): +@pytest.fixture(scope='function', autouse=True) +def binariescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = BinariesCheck(CONFIG, output) + return output, test - @classmethod - def setup_class(cls): - cls.check = BinariesCheck.check.check - def test_forbidden_c_calls(self): - for package in ['cyrus-imapd', 'dovecot']: - out = self._rpm_test_output(os.path.join('binary', package)) - assert 'crypto-policy-non-compliance' in "\n".join(out) +@pytest.mark.parametrize('package', ['binary/crypto-policy']) +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_forbidden_c_calls(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'crypto-policy-non-compliance-openssl /usr/lib/cyrus-imapd/arbitron SSL_CTX_set_cipher_list' in out + assert 'crypto-policy-non-compliance-openssl /usr/lib64/dovecot/libssl_iostream_openssl.so SSL_CTX_set_cipher_list' in out - def test_waived_forbidden_c_calls(self): - for package in ['ngircd']: - out = self._rpm_test_output(os.path.join('binary', package)) - assert 'crypto-policy-non-compliance' not in "\n".join(out) - def test_lto_bytecode(self): - for package in ['libreiserfscore-devel']: - out = self._rpm_test_output(os.path.join('binary', package)) - assert 'lto-bytecode' in "\n".join(out) +@pytest.mark.parametrize('package', ['binary/ngircd']) +def test_waived_forbidden_c_calls(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'crypto-policy-non-compliance' not in out + + +@pytest.mark.parametrize('package', ['binary/libreiserfscore-devel']) +def test_lto_bytecode(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'lto-bytecode' in out + + +@pytest.mark.parametrize('package', ['binary/lto-text']) +def test_lto_archive_text(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'lto-no-text-in-archive /usr/lib64/libiberty.a' in out + assert 'lto-no-text-in-archive /usr/lib64/libdl_p.a' not in out + + +@pytest.mark.parametrize('package', ['binary/ghc']) +def test_lto_ghc_archive(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'lto-no-text-in-archive' not in out + + +@pytest.mark.parametrize('package', ['binary/libtool-wrapper']) +def test_libtool_wrapper(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: libtool-wrapper-in-package' in out + assert 'W: unstripped-binary-or-object' in out + assert 'E: arch-dependent-file-in-usr-share' in out + assert 'W: unstripped-binary-or-object /bin/main' in out + assert 'W: position-independent-executable-suggested /usr/share/main' in out + + +@pytest.mark.parametrize('package', ['binary/noarch']) +def test_no_arch_issues(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: arch-independent-package-contains-binary-or-object /bin/main' in out + assert 'E: noarch-with-lib64' in out + + +@pytest.mark.parametrize('package', ['binary/libnoexec']) +def test_shlib_with_no_exec(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: shared-library-not-executable /lib64/libfoo.so' not in out + + +@pytest.mark.parametrize('package', ['binary/glibc']) +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_shlib_with_no_exec_glibc(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: shared-library-not-executable /lib64/libpthread.so' in out + assert 'missing-hash-section' not in out + assert 'missing-gnu-hash-section' not in out + + +@pytest.mark.parametrize('package', ['binary/bcc-lua']) +def test_position_independent_executable(tmp_path, package, binariescheck): + CONFIG.configuration['PieExecutables'] = ['.*'] + output = Filter(CONFIG) + test = BinariesCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: non-position-independent-executable /usr/bin/bcc-lua' in out + + +@pytest.mark.parametrize('package', ['binary/only-non-binary-in-usr-lib']) +def test_only_non_binary_in_usr_lib(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: only-non-binary-in-usr-lib' in out + # there is a file in /usr/lib64, so no error + assert 'E: no-binary' not in out + # we have no 'noarch' or wrapper here + assert 'E: noarch-with-lib64' not in out + assert 'E: arch-independent-package-contains-binary-or-object' not in out + assert 'E: libtool-wrapper-in-package' not in out + + +# In general we want to throw a warning if we have only non-binary files in +# the /usr/lib. But we can allow non-binaries via UsrLibBinaryException config +# option. These files will be considered binaries and no warning should be +# thrown. +@pytest.mark.parametrize('package', + ['binary/only-non-binary-in-usr-lib_exception']) +def test_only_non_binary_in_usr_lib_exception(tmp_path, package, binariescheck): + config = Config(TEST_CONFIG) + config.configuration['UsrLibBinaryException'] = '^/usr/lib(64)?/python' + output = Filter(config) + test = BinariesCheck(config, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: only-non-binary-in-usr-lib' not in out + + +@pytest.mark.parametrize('package', ['binary/no-binary']) +def test_no_binary(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: no-binary' in out + # no .la file or binary there + assert 'E: invalid-la-file' not in out + assert 'E: binary-in-etc' not in out + + +@pytest.mark.parametrize('package', ['binary/invalid-la-file']) +def test_invalid_la_file(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: invalid-la-file' in out + # no /usr/share dir there + assert 'E: arch-dependent-file-in-usr-share' not in out + + +@pytest.mark.parametrize('package', ['binary/binary-in-etc']) +def test_binary_in_etc(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: binary-in-etc' in out + # it's not a library package + assert 'E: executable-in-library-package' not in out + + +@pytest.mark.parametrize('package', ['binary/non-position-independent-exec']) +def test_non_position_independent_sugg(tmp_path, package, binariescheck): + # reset PieExecutable option + CONFIG.configuration['PieExecutables'] = [] + output = Filter(CONFIG) + test = BinariesCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: position-independent-executable-suggested' in out + # it should throw just a warning as it's not forced by PieExecutables opt + assert 'E: non-position-independent-executable' not in out + + +# Force an error by setting PieExecutables option to the no-pie binary +@pytest.mark.parametrize('package', ['binary/non-position-independent-exec']) +def test_non_position_independent(tmp_path, package, binariescheck): + CONFIG.configuration['PieExecutables'] = ['sparta', '.*hello'] + output = Filter(CONFIG) + test = BinariesCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: non-position-independent-executable' in out + # It should throw just the error, not warning + assert 'W: position-independent-executable-suggested' not in out + + +# libtest package +@pytest.mark.parametrize('package', ['binary/libtest']) +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_library(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: executable-in-library-package' in out + assert 'W: no-soname' in out + # there is no soname here so it can't be invalid + assert 'E: invalid-soname' not in out + + +# invalid-soname test package +@pytest.mark.parametrize('package', ['binary/libtest1']) +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_shared_library1(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: invalid-soname' in out + # there is an invalid soname here, so no "no-soname" error + assert 'W: no-soname' not in out + + +# shlib-policy-name-error test package +@pytest.mark.parametrize('package', ['binary/libtest2']) +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_shared_library2(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: shlib-policy-name-error' in out + # it doesn't call /sbin/ldconfig + assert 'E: no-ldconfig-symlink' in out + # no ldconfig is not invalid + assert 'E: invalid-ldconfig-symlink' not in out + # the soname is set + assert 'W: no-soname' not in out + + +# invalid-ldconfig-symlink test package +@pytest.mark.parametrize('package', ['binary/libtest3']) +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_invalid_ldconfig_symlink(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: invalid-ldconfig-symlink' in out + # executable doesn't call mktemp, setuid or gethostbyname + assert 'E: call-to-mktemp' not in out + assert 'E: missing-call-to-setgroups-before-setuid' not in out + assert 'W: binary-or-shlib-calls-gethostbyname' not in out + # it's not statically linked either + assert 'E: statically-linked-binary' not in out + + +# valid symlink should not report invalid-ldconfig-symlink +@pytest.mark.parametrize('package', ['binary/libtest4']) +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_not_valid_ldconfig_symlink(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: invalid-ldconfig-symlink' not in out + + +@pytest.mark.parametrize('package', ['binary/multiple_errors']) +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_multiple_errors(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: call-to-mktemp' in out + assert 'E: missing-call-to-setgroups-before-setuid' in out + assert 'W: binary-or-shlib-calls-gethostbyname' in out + + +@pytest.mark.parametrize('package', ['binary/libtest']) +def test_patchable_function_entry_archive(tmp_path, package, binariescheck): + output, test = binariescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: patchable-function-entry-in-archive /usr/lib64/libhello.a' in out + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package(files=['/usr/lib/systemd/system/yast-timesync.service']), +]) +def test_systemd_unit_file(package, binariescheck): + output, test = binariescheck + test.check(package) + out = output.print_results(output.results) + assert 'only-non-binary-in-usr-lib' not in out diff --git a/test/test_build_date.py b/test/test_build_date.py new file mode 100644 index 0000000..8222fab --- /dev/null +++ b/test/test_build_date.py @@ -0,0 +1,35 @@ +import re + +import pytest +from rpmlint.checks.BuildRootAndDateCheck import BuildRootAndDateCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def builddatecheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = BuildRootAndDateCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/builddate']) +def test_build_date_time(tmp_path, package, builddatecheck): + output, test = builddatecheck + test.istoday = re.compile('Jan 1 2019') + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: file-contains-date-and-time /bin/with-datetime' in out + assert 'E: file-contains-current-date /bin/with-date' in out + + +@pytest.mark.parametrize('package', ['binary/bashisms']) +def test_build_date_time_correct(tmp_path, package, builddatecheck): + output, test = builddatecheck + test.istoday = re.compile('Jan 1 2019') + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: file-contains-date-and-time' not in out + assert 'E: file-contains-current-date' not in out diff --git a/test/test_build_root.py b/test/test_build_root.py new file mode 100644 index 0000000..0afbfb1 --- /dev/null +++ b/test/test_build_root.py @@ -0,0 +1,22 @@ +import pytest +from rpmlint.checks.BuildRootAndDateCheck import BuildRootAndDateCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def buildrootcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = BuildRootAndDateCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/buildroot']) +def test_build_root(tmp_path, package, buildrootcheck): + output, test = buildrootcheck + test.prepare_regex('/home/marxin/rpmbuild/BUILDROOT/%{NAME}-%{VERSION}-%{RELEASE}.x86_64') + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: file-contains-buildroot /bin/trace' in out diff --git a/test/test_cli.py b/test/test_cli.py new file mode 100644 index 0000000..2f0c55f --- /dev/null +++ b/test/test_cli.py @@ -0,0 +1,85 @@ +from pathlib import PosixPath + +import pytest +from rpmlint.cli import process_lint_args +from rpmlint.config import Config +from rpmlint.lint import Lint + +from Testing import HAS_CHECKBASHISMS, HAS_DASH + + +@pytest.mark.parametrize('test_arguments', [['-c', 'rpmlint/configs/thisdoesntexist.toml']]) +def test_parsing_non_existing_config_file(test_arguments): + with pytest.raises(SystemExit) as exc: + process_lint_args(test_arguments) + assert exc.value.code == 2 + + +@pytest.mark.parametrize('test_arguments', [['-c', 'rpmlint/configdefaults.toml']]) +def test_parsing_config_file(test_arguments): + parsed = process_lint_args(test_arguments) + + assert len(parsed['config']) == 1 + assert parsed['config'][0] == PosixPath('rpmlint/configdefaults.toml') + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('test_arguments', [['-c', 'configs/openSUSE']]) +def test_parsing_opensuse_conf(test_arguments): + parsed = process_lint_args(test_arguments) + + assert len(parsed['config']) == 7 + assert PosixPath('configs/openSUSE/opensuse.toml') in parsed['config'] + assert PosixPath('configs/openSUSE/licenses.toml') in parsed['config'] + assert PosixPath('configs/openSUSE/pie-executables.toml') in parsed['config'] + + defaultcfg = Config() + lint = Lint(parsed) + default_checks = defaultcfg.configuration['Checks'] + checks = lint.config.configuration['Checks'] + # Verify that all original Checks are enabled and some new are added + for check in default_checks: + assert check in checks + assert len(checks) > len(default_checks) + + # Verify that all scoring keys are a known checks + checks = set(lint.output.error_details.keys()) + checks |= set(defaultcfg.configuration['Descriptions'].keys()) + + score_keys = lint.config.configuration['Scoring'].keys() + for score_key in score_keys: + if score_key.startswith('percent-in-'): + continue + assert score_key in checks + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('test_arguments', [['-c', 'configs/Fedora']]) +def test_parsing_fedora_conf(test_arguments): + parsed = process_lint_args(test_arguments) + + assert len(parsed['config']) == 5 + assert PosixPath('configs/Fedora/fedora.toml') in parsed['config'] + assert PosixPath('configs/Fedora/licenses.toml') in parsed['config'] + assert PosixPath('configs/Fedora/users-groups.toml') in parsed['config'] + + defaultcfg = Config() + lint = Lint(parsed) + default_checks = defaultcfg.configuration['Checks'] + checks = lint.config.configuration['Checks'] + # Verify that all original Checks are enabled and some new are added + for check in default_checks: + assert check in checks + assert len(checks) > len(default_checks) + + # Verify that all scoring keys are a known checks + checks = set(lint.output.error_details.keys()) + checks |= set(defaultcfg.configuration['Descriptions'].keys()) + + score_keys = lint.config.configuration['Scoring'].keys() + for score_key in score_keys: + if score_key.startswith('percent-in-'): + continue + assert score_key in checks diff --git a/test/test_config.py b/test/test_config.py new file mode 100644 index 0000000..a2bb939 --- /dev/null +++ b/test/test_config.py @@ -0,0 +1,137 @@ +from pathlib import Path + +import pytest +from rpmlint.config import Config + +from Testing import get_tested_path, TEST_CONFIG + + +TEST_CONFIG_2 = [get_tested_path('configs/test2.config')] +TEST_CONFIG_FILTERS = [get_tested_path('configs/testfilters.config')] +TEST_LIST1 = [get_tested_path('configs/testlists1.config')] +TEST_LIST2 = [get_tested_path('configs/testlists2.config')] +TEST_OVERRIDE = [get_tested_path('configs/test.override.config')] +TEST_RPMLINTRC = get_tested_path('configs/testing-rpmlintrc') +TEST_BROKEN = [get_tested_path('configs/broken.config')] + + +def test_printing(capsys): + cfg = Config() + cfg.print_config() + out, err = capsys.readouterr() + assert not err + assert out + + +def test_custom_config(capsys): + cfg = Config() + # bullshit config + cfg.find_configs([Path('BULLSHIT')]) + out, err = capsys.readouterr() + assert Path('BULLSHIT') not in cfg.conf_files + assert 'BULLSHIT' in err + # existing config + cfg.find_configs(TEST_CONFIG) + out, err = capsys.readouterr() + assert cfg.conf_files + assert not err + + +def test_broken_config(capsys): + with pytest.raises(SystemExit) as pytest_wrapped_e: + Config(TEST_BROKEN) + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 4 + + +def test_parsing(): + # ugly content variables from old config + bad_crypto_warning = \ + """This application package calls a function to explicitly set crypto ciphers + for SSL/TLS. That may cause the application not to use the system-wide set + cryptographic policy and should be modified in accordance to: + https://fedoraproject.org/wiki/Packaging:CryptoPolicies""" + forbidden_functions = { + 'crypto-policy-non-compliance-openssl': { + 'f_name': 'SSL_CTX_set_cipher_list', + 'description': bad_crypto_warning, + }, + 'crypto-policy-non-compliance-gnutls-1': { + 'f_name': 'gnutls_priority_set_direct', + 'description': bad_crypto_warning, + }, + 'crypto-policy-non-compliance-gnutls-2': { + 'f_name': 'gnutls_priority_init', + 'good_param': 'SYSLOG', + 'description': bad_crypto_warning + }, + } + cfg = Config(TEST_CONFIG) + assert cfg.configuration + assert cfg.configuration['Distribution'] == 'Fedora Project' + assert cfg.configuration['WarnOnFunction'] == forbidden_functions + # default value check + assert cfg.configuration['UseDefaultRunlevels'] is True + + +def test_double_config(): + """ + Load two configs and make sure we properly load all the values + """ + cfg = Config(TEST_CONFIG) + assert len(cfg.conf_files) == 2 + assert cfg.configuration['ExtraMenuNeeds'][0] == 'gnome' + # shovel in another config + cfg.load_config(TEST_CONFIG_2) + assert len(cfg.conf_files) == 3 + assert cfg.configuration['ExtraMenuNeeds'][-1] == 'windows' + assert cfg.configuration['WarnOnFunction']['crypto-policy-non-compliance-openssl']['f_name'] == 'REPLACED' + assert cfg.configuration['WarnOnFunction']['crypto-policy-3']['f_name'] == 'new_blobie' + + +def test_filters(): + """ + Load some filters and make sure we generate nice regexp + """ + cfg = Config(TEST_CONFIG_FILTERS) + assert len(cfg.configuration['Filters']) == 12 + assert cfg.configuration['Filters'][0] == '.*invalid-buildhost.*' + + +def test_list_merging(): + """ + Load two configs and check we loaded up in proper older with + replacing based on TOML syntax + """ + cfg = Config(TEST_LIST1) + assert len(cfg.configuration['Filters']) == 1 + assert cfg.configuration['ValidGroups'][0] == 'bullshitgroup' + cfg.load_config(TEST_LIST2) + assert len(cfg.conf_files) == 3 + assert len(cfg.configuration['Filters']) == 2 + assert len(cfg.configuration['ValidGroups']) == 3 + assert cfg.configuration['ValidGroups'][2] == 'System/Libraries' + cfg.load_config(TEST_OVERRIDE) + assert len(cfg.configuration['ValidGroups']) == 1 + + +def test_badness_functions(): + """ + Test badness settings + """ + cfg = Config(TEST_CONFIG_2) + assert len(cfg.configuration['Scoring']) == 0 + cfg.set_badness('suse-dbus-unauthorized-service', 15) + assert len(cfg.configuration['Scoring']) == 1 + assert cfg.configuration['Scoring']['suse-dbus-unauthorized-service'] == 15 + + +def test_rpmlint_loading(): + """ + Make sure we can load up rpmlintrc file without executing any code + """ + cfg = Config(TEST_CONFIG) + cfg.load_rpmlintrc(TEST_RPMLINTRC) + assert 'arch-independent-package-contains-binary-or-object ' in cfg.configuration['Filters'] + assert len(cfg.configuration['Filters']) == 113 + assert len(cfg.configuration['Scoring']) == 3 diff --git a/test/test_config_files.py b/test/test_config_files.py new file mode 100644 index 0000000..b31819c --- /dev/null +++ b/test/test_config_files.py @@ -0,0 +1,55 @@ +import pytest +from rpm import RPMFILE_CONFIG, RPMFILE_NOREPLACE +from rpmlint.checks.ConfigFilesCheck import ConfigFilesCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_mock_package + + +@pytest.fixture(scope='function', autouse=True) +def configfilescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = ConfigFilesCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files={ + '/etc/conffile1': {'metadata': {'flags': RPMFILE_CONFIG}}, + '/var/conffile2': {'metadata': {'flags': RPMFILE_CONFIG}}, + '/usr/share/conffile3': {'metadata': {'flags': RPMFILE_CONFIG}}, + } +)]) +def test_config_files1(package, configfilescheck): + output, test = configfilescheck + test.check(package) + out = output.print_results(output.results) + assert 'non-etc-or-var-file-marked-as-conffile /usr/share/conffile3' in out + assert 'conffile-without-noreplace-flag /etc/conffile1' in out + assert 'conffile-without-noreplace-flag /var/conffile2' in out + assert 'conffile-without-noreplace-flag /usr/share/conffile3' in out + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package( + files=[ + 'tmp/foo/my.log', + 'tmp/foo2/my.log', + 'etc/logrotate.d/logrotate2.conf', + 'etc/logrotate.d/logrotate.conf', + ] + ), + get_tested_mock_package( + files={ + '/etc/conffile1': {'metadata': {'flags': RPMFILE_CONFIG & RPMFILE_NOREPLACE}}, + '/var/conffile2': {'metadata': {'flags': RPMFILE_CONFIG & RPMFILE_NOREPLACE}}, + } + ) +]) +def test_config_files_correct1(package, configfilescheck): + output, test = configfilescheck + test.check(package) + out = output.print_results(output.results) + assert 'non-etc-or-var-file-marked-as-conffile' not in out + assert 'conffile-without-noreplace-flag' not in out diff --git a/test/test_dbus_policy.py b/test/test_dbus_policy.py new file mode 100644 index 0000000..5289aa5 --- /dev/null +++ b/test/test_dbus_policy.py @@ -0,0 +1,25 @@ +import pytest +from rpmlint.checks.DBusPolicyCheck import DBusPolicyCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def dbuspolicycheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = DBusPolicyCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/dbusrule']) +def test_dbus_policy(tmp_path, package, dbuspolicycheck): + output, test = dbuspolicycheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: dbus-parsing-exception raised an exception: no element found: line 1, column 0 /etc/dbus-1/system.d/noxml.conf' in out + assert 'E: dbus-policy-allow-without-destination <allow send_interface="org.freedesktop.NetworkManager.PPP"/>' in out + assert 'W: dbus-policy-allow-receive <allow receive_sender="foo"/>' in out + assert 'E: dbus-policy-deny-without-destination <deny send_interface="org.freedesktop.NetworkManager.Settings" send_member="ReloadConnections"/>' in out + assert 'E: dbus-policy-missing-allow /etc/dbus-1/system.d/org.freedesktop.NetworkManager2.conf' in out diff --git a/test/test_diff.py b/test/test_diff.py new file mode 100644 index 0000000..9ef2136 --- /dev/null +++ b/test/test_diff.py @@ -0,0 +1,48 @@ +from rpmlint.rpmdiff import Rpmdiff + +from Testing import get_tested_path + + +def test_distribution_tags(): + oldpkg = get_tested_path('binary/mc-4.8.15-10.3.1.x86_64.rpm') + newpkg = get_tested_path('binary/mc-4.8.21-2.1.x86_64.rpm') + ignore = [] + diff = Rpmdiff(oldpkg, newpkg, ignore) + textdiff = diff.textdiff() + # the count always reports one less + assert 231 <= len(textdiff.splitlines()) <= 233 + + ignore.append('T') + ignore.append('5') + ignore.append('S') + diff = Rpmdiff(oldpkg, newpkg, ignore) + textdiff = diff.textdiff() + assert 36 <= len(textdiff.splitlines()) <= 38 + + assert 'added /usr/share/mc/syntax/yaml.syntax' in textdiff + + +def test_exclude(): + oldpkg = get_tested_path('binary/mc-4.8.15-10.3.1.x86_64.rpm') + newpkg = get_tested_path('binary/mc-4.8.21-2.1.x86_64.rpm') + ignore = list('T5S') + + # print(Rpmdiff(oldpkg, newpkg, ignore=ignore).textdiff()) + + for exclude in [], ['/usr/share/mc/ski'], ['/share/mc/skins'], ['skins']: + diff = Rpmdiff(oldpkg, newpkg, ignore, exclude) + textdiff = diff.textdiff() + assert '/usr/share/mc/skins/yadt256.ini' in textdiff + + for exclude in (['/usr/share/mc/skins'], ['/usr/share/*/skins'], + ['/*/*/*/skins']): + diff = Rpmdiff(oldpkg, newpkg, ignore, exclude) + textdiff = diff.textdiff() + assert '/usr/share/mc/skins/yadt256.ini' not in textdiff + assert '/usr/share/mc/syntax/cuda.syntax' in textdiff + + for exclude in ['*.syntax'], ['syntax/cuda.syntax']: + diff = Rpmdiff(oldpkg, newpkg, ignore, exclude) + textdiff = diff.textdiff() + assert '/usr/share/mc/skins/yadt256.ini' in textdiff + assert '/usr/share/mc/syntax/cuda.syntax' not in textdiff diff --git a/test/test_distribution.py b/test/test_distribution.py deleted file mode 100644 index 73819e0..0000000 --- a/test/test_distribution.py +++ /dev/null @@ -1,17 +0,0 @@ -import os - -import DistributionCheck -import Testing - - -class TestDistribution(Testing.OutputTest): - - @classmethod - def setup_class(cls): - cls.check = DistributionCheck.check.check - - def test_distribution_tags(self): - for package in ['ngircd']: - out = self._rpm_test_output(os.path.join('binary', package)) - assert 'invalid-distribution' not in "\n".join(out) - assert 'invalid-vendor' not in "\n".join(out) diff --git a/test/test_doc.py b/test/test_doc.py new file mode 100644 index 0000000..79bf700 --- /dev/null +++ b/test/test_doc.py @@ -0,0 +1,43 @@ +import pytest +from rpmlint.checks.DocCheck import DocCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def doccheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = DocCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/mydoc']) +def test_doccheck(tmp_path, package, doccheck): + output, test = doccheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: executable-docs /usr/share/doc/packages/mydoc/doc.html' in out + assert 'E: executable-docs /usr/share/doc/packages/mydoc/README' in out + assert 'W: package-with-huge-docs 100%' in out + + +@pytest.mark.parametrize('package', ['binary/doc-file-dependency']) +def test_doc_file_dep(tmp_path, package, doccheck): + output, test = doccheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: doc-file-dependency' in out + assert 'W: install-file-in-docs' not in out + + +@pytest.mark.parametrize('package', ['binary/install-file-in-docs']) +def test_install_file_in_docs(tmp_path, package, doccheck): + output, test = doccheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: install-file-in-docs' in out + assert 'E: executable-docs' not in out + assert 'W: package-with-huge-docs 100%' not in out + assert 'W: doc-file-dependency' not in out diff --git a/test/test_duplicates.py b/test/test_duplicates.py new file mode 100644 index 0000000..16d9a36 --- /dev/null +++ b/test/test_duplicates.py @@ -0,0 +1,58 @@ +import pytest +from rpmlint.checks.DuplicatesCheck import DuplicatesCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_mock_package + + +@pytest.fixture(scope='function', autouse=True) +def duplicatescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = DuplicatesCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files={ + '/etc/bar': {'content': 'large enough file', 'metadata': {'mode': 33188}}, + '/etc/bar2': {'content': 'large enough file', 'metadata': {'mode': 33188}}, + '/etc/bar3': {'content': 'large enough file', 'metadata': {'mode': 33188}}, + '/etc/foo': {'content': 'Foo file', 'metadata': {'mode': 33188, 'inode': 10}}, + '/etc/foo2': {'content': 'Foo 2 file', 'metadata': {'mode': 33188, 'flags': 1, 'inode': 5}}, + '/etc/small': {'content': ' \n', 'metadata': {'mode': 33188}}, + '/etc/small2': {'content': ' \n', 'metadata': {'mode': 33188}}, + '/etc/strace1.txt': {'content': 'this is a very large file', 'metadata': {'mode': 33188, 'size': 270509}}, + '/etc/strace2.txt': {'content': 'this is a very large file', 'metadata': {'mode': 33188, 'size': 270509}}, + '/var/foo': {'content': 'Foo file', 'metadata': {'mode': 33188, 'inode': 10}}, + '/var/foo2': {'content': 'Foo 2 file', 'metadata': {'mode': 33188, 'flags': 1, 'inode': 5}} + }, +)]) +def test_duplicates1(package, duplicatescheck): + output, test = duplicatescheck + test.check(package) + out = output.print_results(output.results) + + assert 'E: hardlink-across-partition /var/foo /etc/foo' in out + assert 'E: hardlink-across-config-files /var/foo2 /etc/foo2' in out + assert 'W: files-duplicate /etc/bar3 /etc/bar:/etc/bar2' in out + assert 'W: files-duplicate /etc/strace2.txt /etc/strace1.txt' in out + assert 'W: files-duplicate /etc/small2 /etc/small' not in out + assert 'E: files-duplicated-waste 270543' in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files={ + '/usr/share/bad-crc.zip': {'content': 'this is a zip file', 'metadata': {'mode': 33188, 'flags': 1}}, + '/usr/share/uncompressed.zip': {'content': 'this is an another zip file', 'metadata': {'mode': 33188, 'flags': 1}}, + }, +)]) +def test_duplicates_correct(package, duplicatescheck): + output, test = duplicatescheck + test.check(package) + out = output.print_results(output.results) + + assert 'E: hardlink-across-partition' not in out + assert 'E: hardlink-across-config-files' not in out + assert 'W: files-duplicate' not in out + assert 'E: files-duplicated-waste' not in out diff --git a/test/test_erlang.py b/test/test_erlang.py new file mode 100644 index 0000000..de33d28 --- /dev/null +++ b/test/test_erlang.py @@ -0,0 +1,26 @@ +from importlib.metadata import distribution + +from packaging.version import parse +import pytest +from rpmlint.checks.ErlangCheck import ErlangCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def erlangcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = ErlangCheck(CONFIG, output) + return output, test + + +@pytest.mark.skipif(parse(distribution('pybeam').version) < parse('0.7'), reason='pybeam >= 0.7 required') +@pytest.mark.parametrize('package', ['binary/erlang-test']) +def test_erlang(tmp_path, package, erlangcheck): + output, test = erlangcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: beam-compiled-without-debuginfo /usr/lib/erlang/m.beam' in out + assert 'W: beam-compile-info-missed /usr/lib/erlang/m-no-CInf.beam' in out diff --git a/test/test_files.py b/test/test_files.py index ebd2054..de0f1fc 100644 --- a/test/test_files.py +++ b/test/test_files.py @@ -1,84 +1,160 @@ -import os +import stat import pytest +from rpmlint.checks.FilesCheck import FilesCheck +from rpmlint.checks.FilesCheck import pyc_magic_from_chunk, pyc_mtime_from_chunk +from rpmlint.checks.FilesCheck import python_bytecode_to_script as pbts +from rpmlint.checks.FilesCheck import script_interpreter as se +from rpmlint.filter import Filter -import FilesCheck -from FilesCheck import pyc_magic_from_chunk, pyc_mtime_from_chunk -from FilesCheck import python_bytecode_to_script as pbts -from FilesCheck import script_interpreter as se -import Testing +from Testing import CONFIG, get_tested_mock_package, get_tested_package, get_tested_path -class TestPythonBytecodeToScript(object): +@pytest.fixture(scope='function', autouse=True) +def filescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = FilesCheck(CONFIG, output) + yield output, test - def test_pep3147(self): - assert pbts("/usr/lib64/python3.4/__pycache__/__phello__.foo.cpython-34.pyc") == "/usr/lib64/python3.4/__phello__.foo.py" - assert pbts("/usr/lib64/python3.4/__pycache__/__phello__.foo.cpython-34.pyo") == "/usr/lib64/python3.4/__phello__.foo.py" - def test_py2(self): - assert pbts("/usr/lib/python2.7/site-packages/_pytest/main.pyc") == "/usr/lib/python2.7/site-packages/_pytest/main.py" - assert pbts("/usr/lib/python2.7/site-packages/_pytest/main.pyo") == "/usr/lib/python2.7/site-packages/_pytest/main.py" +@pytest.fixture +def output(filescheck): + output, _test = filescheck + yield output - def test_pep0488(self): - assert pbts("/usr/lib/python3.5/site-packages/__pycache__/pytest.cpython-35.opt-1.pyc") == "/usr/lib/python3.5/site-packages/pytest.py" - assert pbts("/usr/lib/python3.5/site-packages/__pycache__/pytest.cpython-35.opt-2.pyc") == "/usr/lib/python3.5/site-packages/pytest.py" - assert pbts("/usr/lib/python3.5/site-packages/__pycache__/pytest.cpython-35.pyc") == "/usr/lib/python3.5/site-packages/pytest.py" + +@pytest.fixture +def test(filescheck): + _output, test = filescheck + yield test + + +def test_pep3147(): + assert pbts('/usr/lib64/python3.4/__pycache__/__phello__.foo.cpython-34.pyc') == '/usr/lib64/python3.4/__phello__.foo.py' + assert pbts('/usr/lib64/python3.4/__pycache__/__phello__.foo.cpython-34.pyo') == '/usr/lib64/python3.4/__phello__.foo.py' + + +def test_py2(): + assert pbts('/usr/lib/python2.7/site-packages/_pytest/main.pyc') == '/usr/lib/python2.7/site-packages/_pytest/main.py' + assert pbts('/usr/lib/python2.7/site-packages/_pytest/main.pyo') == '/usr/lib/python2.7/site-packages/_pytest/main.py' + + +def test_pep0488(): + assert pbts('/usr/lib/python3.5/site-packages/__pycache__/pytest.cpython-35.opt-1.pyc') == '/usr/lib/python3.5/site-packages/pytest.py' + assert pbts('/usr/lib/python3.5/site-packages/__pycache__/pytest.cpython-35.opt-2.pyc') == '/usr/lib/python3.5/site-packages/pytest.py' + assert pbts('/usr/lib/python3.5/site-packages/__pycache__/pytest.cpython-35.pyc') == '/usr/lib/python3.5/site-packages/pytest.py' def chunk_from_pyc(version, size=16): """Helper to get start of an example pyc file as bytes""" - path = Testing.getTestedPath("pyc/__future__.cpython-{}.pyc".format(version)) + path = get_tested_path(f'pyc/__future__.cpython-{version}.pyc') with open(path, 'rb') as f: return f.read(size) -class TestPythonBytecodeMagic(Testing.OutputTest): +@pytest.mark.parametrize('package', ['binary/unexpanded-macro-files']) +def test_unexpanded_macros(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'unexpanded-macro' in out + + +@pytest.mark.parametrize('package', ['binary/python3-power']) +def test_python_bytecode_magic(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + assert not output.results + out = output.print_results(output.results) + assert 'python-bytecode-wrong-magic-value' not in out - @classmethod - def setup_class(cls): - cls.check = FilesCheck.check.check - def test_python_bytecode_magic(self): - for package in ["python3-power"]: - out = self._rpm_test_output(os.path.join("binary", package)) - assert "python-bytecode-wrong-magic-value" not in "\n".join(out) +@pytest.mark.parametrize('package', ['binary/testdocumentation']) +def test_file_not_utf8_for_compression_algorithms(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'file-not-utf8 /usr/share/doc/packages/testdocumentation/README1.gz' in out + assert 'file-not-utf8 /usr/share/doc/packages/testdocumentation/README2.bz2' in out + assert 'file-not-utf8 /usr/share/doc/packages/testdocumentation/README3.xz' in out - @pytest.mark.parametrize('version, magic', ((36, 3379), (37, 3393))) - def test_pyc_magic_from_chunk(self, version, magic): - chunk = chunk_from_pyc(version) - assert pyc_magic_from_chunk(chunk) == magic +@pytest.mark.parametrize('version, magic', ((36, 3379), (37, 3393))) +def test_pyc_magic_from_chunk(version, magic): + chunk = chunk_from_pyc(version) + assert pyc_magic_from_chunk(chunk) == magic -class TestPythonBytecodeMtime(object): - @pytest.mark.parametrize('version, mtime', ((36, 1513659236), (37, 1519778958))) - def test_pyc_mtime_from_chunk(self, version, mtime): - chunk = chunk_from_pyc(version) - assert pyc_mtime_from_chunk(chunk) == mtime +@pytest.mark.parametrize('version, mtime', ((36, 1513659236), (37, 1519778958))) +def test_pyc_mtime_from_chunk(version, mtime): + chunk = chunk_from_pyc(version) + assert pyc_mtime_from_chunk(chunk) == mtime -class TestDevelFiles(Testing.OutputTest): +@pytest.mark.parametrize('package', ['binary/netmask-debugsource']) +def test_devel_files(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + assert len(output.results) == 5 + out = output.print_results(output.results) + assert 'devel-file-in-non-devel-package' not in out + assert 'incorrect-fsf-address' in out + assert 'no-documentation' in out - @classmethod - def setup_class(cls): - cls.check = FilesCheck.check.check - def test_python_bytecode_magic(self): - for package in ["netmask-debugsource"]: - out = self._rpm_test_output(os.path.join("binary", package)) - assert "devel-file-in-non-devel-package" not in "\n".join(out) +@pytest.mark.parametrize('package', ['binary/makefile-junk']) +def test_makefile_junk(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: makefile-junk /usr/share/Makefile.am' in out + assert out.count('W: makefile-junk') == 1 + + +@pytest.mark.parametrize('package', ['binary/python3-greenlet']) +def test_sphinx_inv_files(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + assert not len(output.results) + + +@pytest.mark.parametrize('package', ['binary/filechecks']) +def test_invalid_package(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: non-ghost-in-run /run/foo' in out + assert 'W: systemd-unit-in-etc /etc/systemd/system/foo' in out + assert 'W: udev-rule-in-etc /etc/udev/rules.d/foo' in out + assert 'W: tmpfiles-conf-in-etc /etc/tmpfiles.d/foo' in out + assert 'E: subdir-in-bin /bin/foo/bar' in out + assert 'W: siteperl-in-perl-module /site_perl/foo' in out + assert 'E: backup-file-in-package /~backup.rej' in out + assert 'E: version-control-internal-file /.gitignore' in out + assert 'E: htaccess-file /.htaccess' in out + assert 'W: manifest-in-perl-module /usr/share/doc/perl-foo/MANIFEST' in out + assert 'E: info-dir-file /usr/info/dir' in out + + +@pytest.mark.parametrize('package', ['binary/tclpackage']) +def test_tcl_package(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: tcl-extension-file /usr/lib64/tcl/pkgIndex.tcl' in out def test_script_interpreter(): - assert se(b"#!/bin/sh\n# Hello world!\n") == ("/bin/sh", "") - assert se(b"#!/bin/bash -e\n") == ("/bin/bash", "-e") - assert se(b"#! /usr/bin/perl -wT \n") == ("/usr/bin/perl", "-wT") - assert se(b"#!/usr/bin/env python3 foo") == ("/usr/bin/env", "python3 foo") - assert se(b"# something here\n#!not a shebang") == (None, "") + assert se(b'#!/bin/sh\n# Hello world!\n') == ('/bin/sh', '') + assert se(b'#!/bin/bash -e\n') == ('/bin/bash', '-e') + assert se(b'#! /usr/bin/perl -wT \n') == ('/usr/bin/perl', '-wT') + assert se(b'#!/usr/bin/env python3 foo') == ('/usr/bin/env', 'python3 foo') + assert se(b'# something here\n#!not a shebang') == (None, '') def test_scm_regex(): - from FilesCheck import scm_regex + from rpmlint.checks.FilesCheck import scm_regex assert scm_regex.search('/foo/CVS/bar') assert scm_regex.search('/foo/RCS/bar') @@ -88,7 +164,7 @@ def test_scm_regex(): def test_lib_regex(): - from FilesCheck import lib_regex + from rpmlint.checks.FilesCheck import lib_regex # true matches assert all( @@ -104,3 +180,123 @@ def test_lib_regex(): '/usr/share/doc/findlib/lib-1.0.so', '/usr/lib64/libvulkan_radeon.so', '/usr/lib64/rsocket/binary',)) + + +@pytest.mark.parametrize('package', ['binary/rust']) +def test_rust_files(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: wrong-script-interpreter /etc/foo.rs' in out + assert 'E: wrong-script-interpreter /etc/bar.rs' not in out + + +@pytest.mark.parametrize('package', ['binary/ngircd']) +def test_distribution_tags(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'manpage-not-compressed' in out + assert 'no-manual-page-for-binary' not in out + assert 'This manual page is not compressed with the bz2 compression' in out + + +@pytest.mark.parametrize('package', ['binary/development']) +def test_provides_devel(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: non-devel-file-in-devel-package /usr/x.typelib' in out + + +@pytest.mark.parametrize('package', ['binary/shlib1']) +def test_shlib1(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'library-without-ldconfig-postin' in out + assert 'library-without-ldconfig-postun' in out + assert 'devel-file-in-non-devel-package' in out + + +@pytest.mark.parametrize('package', ['binary/shlib2-devel']) +def test_shlib2_devel(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'library-without-ldconfig-postin' in out + assert 'library-without-ldconfig-postun' in out + assert 'non-devel-file-in-devel-package' in out + + +@pytest.mark.parametrize('package', ['binary/file-zero-length']) +@pytest.mark.parametrize( + 'filename, show', + [('/usr/lib/emptyfile', True), + ('/usr/lib/nonemptyfile', False), + ('/etc/security/console.apps', False), + ('/usr/lib/.nosearch', False), + ('/usr/lib/python/__init__.py', False), + ('/usr/lib/python/py.typed', False), + ('/usr/lib/python/pypackagefromwheel-0.0.0.dist-info/REQUESTED', False), + ('/usr/lib/ruby/gem.build_complete', False)]) +def test_zero_length_ignore(tmp_path, package, filescheck, filename, show): + output, test = filescheck + pkg = get_tested_package(package, tmp_path) + test.check(pkg) + out = output.print_results(output.results) + assert filename in pkg.files + assert (f'zero-length {filename}' in out) == show + + +@pytest.mark.parametrize('package', ['binary/manual-pages']) +def test_manual_pages(tmp_path, package, filescheck): + output, test = filescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: manual-page-in-subfolder /usr/share/man/man3/foo/bar/baz.3.gz' in out + assert 'W: manpage-not-compressed bz2 /usr/share/man/man1/test.1.zst' in out + assert 'E: bad-manual-page-folder /usr/share/man/man0p/foo.3.gz expected folder: man3' in out + assert 'bad-manual-page-folder /usr/share/man/man3/some.3pm.gz' not in out + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package( + files={ + '/usr/share/package/bin.py': { + 'content': '#!/usr/bin/python3\nprint("python required")', + 'metadata': {'mode': 0o755 | stat.S_IFREG}, + }, + '/usr/bin/testlink': { + 'linkto': '../share/package/bin.py', + }, + }, + header={}, + ), +]) +def test_shebang(package, output, test): + test.check(package) + out = output.print_results(output.results) + assert 'W: symlink-to-binary-with-shebang /usr/bin/testlink' in out + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package( + files={ + '/usr/share/package/bin.py': { + 'content': '#!/usr/bin/python3\nprint("python required")', + 'metadata': {'mode': 0o755 | stat.S_IFREG}, + }, + '/usr/bin/testlink': { + 'linkto': '../share/package/bin.py', + }, + }, + header={ + 'requires': ['/usr/bin/python3'], + }, + ), +]) +def test_shebang_ok(package, output, test): + test.check(package) + out = output.print_results(output.results) + assert 'W: symlink-to-binary-with-shebang /usr/bin/testlink' not in out diff --git a/test/test_filter.py b/test/test_filter.py new file mode 100644 index 0000000..7c5e846 --- /dev/null +++ b/test/test_filter.py @@ -0,0 +1,177 @@ +from pathlib import Path + +from rpmlint.config import Config +from rpmlint.filter import Filter + +from Testing import get_tested_package, get_tested_path + +TEST_CONFIG_FILTERS = [get_tested_path('configs/testfilters.config')] +TEST_RPMLINTRC = get_tested_path('configs/testing-rpmlintrc') +TEST3_RPMLINTRC = get_tested_path('configs/testing3-rpmlintrc') +TEST_PACKAGE = Path('binary', 'ngircd') +TEST_PACKAGE2 = Path('binary', 'tempfiled') +TEST_DESCRIPTIONS = [get_tested_path('configs/descriptions.config')] + + +def test_filters_regexp(): + """ + Load some filters and make sure we generate nice regexp + """ + cfg = Config(TEST_CONFIG_FILTERS) + assert len(cfg.configuration['Filters']) == 12 + assert cfg.configuration['Filters'][0] == '.*invalid-buildhost.*' + + +def test_data_storing(tmp_path): + """ + Load some filters and make sure we generate nice regexp + """ + cfg = Config(TEST_CONFIG_FILTERS) + cfg.load_rpmlintrc(TEST_RPMLINTRC) + result = Filter(cfg) + pkg = get_tested_package(TEST_PACKAGE, tmp_path) + # this should be upgraded to error + result.add_info('I', pkg, 'suse-other-error', '') + assert len(result.results) == 1 + assert result.printed_messages['I'] == 0 + assert result.printed_messages['E'] == 1 + # this should be downgraded + result.add_info('E', pkg, 'suse-dbus-unauthorized-service', '') + assert len(result.results) == 2 + assert result.printed_messages['W'] == 1 + assert result.printed_messages['E'] == 1 + + +def test_data_storing_backward_compat(tmp_path): + """ + Make sure we can load some filters from rpmlintrc that + worked with rpmlint v1. + """ + cfg = Config(TEST_CONFIG_FILTERS) + cfg.load_rpmlintrc(TEST3_RPMLINTRC) + parsed_filters = cfg.rpmlintrc_filters + assert 'no-spaces-in-paren' in parsed_filters + assert 'has-spaces-in-paren' in parsed_filters + assert 'multiple-spaces-in-paren' in parsed_filters + assert 'doublequotes-instead-of-singlequotes' in parsed_filters + + +def test_description_storing(tmp_path): + """ + Test if we can store extra destcriptions and formatting is up par + """ + lorem_formated = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor +incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis +nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore +eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt +in culpa qui officia deserunt mollit anim id est laborum.\n\n""" + cfg = Config(TEST_CONFIG_FILTERS) + result = Filter(cfg) + pkg = get_tested_package(TEST_PACKAGE, tmp_path) + assert len(result.results) == 0 + result.add_info('E', pkg, 'suse-dbus-unauthorized-service', '') + # two options so we check the description is added only once + result.add_info('I', pkg, 'suse-other-error', '/usr/bin/1') + # nothing is populated + assert not result.get_description('suse-other-error') + # add descriptions + result.error_details.update({'suse-other-error': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'}) + assert result.get_description('suse-other-error') == lorem_formated + + +def test_description_from_toml(tmp_path): + """ + Test if description loaded from toml shows up details + """ + cfg = Config(TEST_CONFIG_FILTERS) + result = Filter(cfg) + assert result.get_description('uncompressed-zip') + assert result.get_description('uncompressed-zip') == 'The zip file is not compressed.\n\n' + + +def test_description_from_conf(tmp_path): + """ + Test that descriptions strings are updated from configuration file. + + Load [Descriptions] from TEST_DESCRIPTIONS config file and test that the + rpmlint error details are updated to the new values. + """ + cfg = Config(TEST_DESCRIPTIONS) + result = Filter(cfg) + + assert result.get_description('no-binary', cfg) + assert result.get_description('no-binary', cfg) == \ + 'A new text for no-binary error.\n\n' + + assert result.get_description('no-soname', cfg) + assert result.get_description('no-soname', cfg) == \ + 'A new text for no-soname error.\n\n' + + # At this point, only basic descriptions from "descriptions" directory are + # loaded. "Dynamic" descriptions that are defined directly in the check + # file (e.g. see FHSCheck.py and its fhs_details_dict) are loaded later so + # we can't test it now. It's tested in test_lint.py. + + assert not result.get_description('non-standard-dir-in-usr', cfg) + assert result.get_description('non-standard-dir-in-usr', cfg) != \ + 'A new text for non-standard-dir-in-usr error.\n\n' + + assert not result.get_description('non-standard-dir-in-var', cfg) + assert result.get_description('non-standard-dir-in-var', cfg) != \ + 'A new text for non-standard-dir-in-var error.\n\n' + + +def test_output(tmp_path): + """ + Test the actual output of rpmlint on one file + """ + expected_output = """ngircd.x86_64: I: suse-other-error /usr/bin/1 +ngircd.x86_64: I: suse-other-error /usr/bin/2 +tempfiled.x86_64: E: suse-other-error /usr/bin/3 +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor +incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis +nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore +eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt +in culpa qui officia deserunt mollit anim id est laborum. + +ngircd.x86_64: E: suse-dbus-unauthorized-service\n""" + cfg = Config(TEST_CONFIG_FILTERS) + result = Filter(cfg) + pkg = get_tested_package(TEST_PACKAGE, tmp_path) + pkg2 = get_tested_package(TEST_PACKAGE2, tmp_path) + # here we check if empty detail will not add whitespace + result.add_info('E', pkg, 'suse-dbus-unauthorized-service', '') + # two options so we check the description is added only once + result.add_info('I', pkg, 'suse-other-error', '/usr/bin/1') + result.add_info('I', pkg, 'suse-other-error', '/usr/bin/2') + result.add_info('E', pkg2, 'suse-other-error', '/usr/bin/3') + result.error_details.update({'suse-other-error': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'}) + assert len(result.print_results(result.results).splitlines()) == 4 + result.info = True + assert len(result.print_results(result.results).splitlines()) == 11 + assert result.print_results(result.results) == expected_output + + +def test_filtered_output(tmp_path): + cfg = Config(TEST_CONFIG_FILTERS) + result = Filter(cfg) + pkg = get_tested_package(TEST_PACKAGE, tmp_path) + assert len(result.results) == 0 + result.add_info('E', pkg, 'no-regex', '') + result.add_info('E', pkg, 'no-regex-with-leading-space', '') + result.add_info('E', pkg, 'bad-error', 'details of the error') + result.add_info('E', pkg, 'test-color-error', 'details of the error') + assert len(result.results) == 0 + + +def test_blocked_filters(tmp_path): + key = 'fatal-error' + cfg = Config(TEST_CONFIG_FILTERS) + result = Filter(cfg) + pkg = get_tested_package(TEST_PACKAGE, tmp_path) + assert len(result.results) == 0 + assert key in cfg.configuration['Filters'] + result.add_info('E', pkg, key, '') + assert len(result.results) == 1 diff --git a/test/test_helpers.py b/test/test_helpers.py new file mode 100644 index 0000000..747acf3 --- /dev/null +++ b/test/test_helpers.py @@ -0,0 +1,43 @@ +from rpmlint import helpers + + +def test_warnprint(capsys): + """ + Check we print stuff to stderr + """ + message = 'I am writing to stderr' + helpers.print_warning(message) + out, err = capsys.readouterr() + assert message not in out + assert message in err + + +def test_bytetostr(): + """ + Test bytetostr function + """ + list_items = ( + b'\xc5\xbe\xc3\xad\xc5\xbeala', + 'texty', + ) + item = b'p\xc5\x99\xc3\xad\xc5\xa1ern\xc4\x9b \xc5\xbelu\xc5\xa5ou\xc4\x8dk\xc3\xbd k\xc5\xaf\xc5\x88' + + result = helpers.byte_to_string(item) + assert isinstance(result, str) + assert result == 'příšerně žluťoučký kůň' + + result = helpers.byte_to_string(list_items) + assert isinstance(result, list) + assert result[0] == 'žížala' + + +def test_centering(capsys): + """ + Check wether centered print works + """ + + message = 'Hello there' + helpers.print_centered(message, '*') + out, err = capsys.readouterr() + assert '** Hello there **' in out + assert not err diff --git a/test/test_i18n.py b/test/test_i18n.py new file mode 100644 index 0000000..016cf9f --- /dev/null +++ b/test/test_i18n.py @@ -0,0 +1,49 @@ +import pytest +from rpmlint.checks.I18NCheck import I18NCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_mock_package + + +@pytest.fixture(scope='function', autouse=True) +def i18ncheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = I18NCheck(CONFIG, output) + yield output, test + + +@pytest.fixture +def output(i18ncheck): + output, _test = i18ncheck + yield output + + +@pytest.fixture +def test(i18ncheck): + _output, test = i18ncheck + yield test + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package(files=['/usr/share/locale/xx_ES/LC_MESSAGES/goodvibes.mo']), + get_tested_mock_package(files=['/usr/share/locale/es_XX/LC_MESSAGES/goodvibes.mo']), + get_tested_mock_package(files=['/usr/share/locale/xx/LC_MESSAGES/goodvibes.mo']), +]) +def test_i18n_invalid_lang(package, output, test): + test.check(package) + out = output.print_results(output.results) + assert 'E: invalid-lc-messages-dir' in out + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package(files=['/usr/share/locale/zh/LC_MESSAGES/goodvibes.mo']), + get_tested_mock_package(files=['/usr/share/locale/zh_Hant/LC_MESSAGES/goodvibes.mo']), + get_tested_mock_package(files=['/usr/share/locale/es_ES/LC_MESSAGES/goodvibes.mo']), + get_tested_mock_package(files=['/usr/share/locale/zh_TW/LC_MESSAGES/goodvibes.mo']), + get_tested_mock_package(files=['/usr/share/locale/pt_BR/LC_MESSAGES/goodvibes.mo']), +]) +def test_i18n_valid_lang(package, output, test): + test.check(package) + out = output.print_results(output.results) + assert 'E: invalid-lc-messages-dir' not in out diff --git a/test/test_icon_sizes.py b/test/test_icon_sizes.py new file mode 100644 index 0000000..ad96ff8 --- /dev/null +++ b/test/test_icon_sizes.py @@ -0,0 +1,21 @@ +import pytest +from rpmlint.checks.IconSizesCheck import IconSizesCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def iconsizescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = IconSizesCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/tasque']) +def test_icon_sizes(tmp_path, package, iconsizescheck): + output, test = iconsizescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: wrong-icon-size /usr/share/tasque/icons/hicolor/16x16/status/tasque-note.png expected: 16x16 actual: 22x22' in out diff --git a/test/test_ldd_parser.py b/test/test_ldd_parser.py new file mode 100644 index 0000000..e3fc2a4 --- /dev/null +++ b/test/test_ldd_parser.py @@ -0,0 +1,109 @@ +from pathlib import Path + +import pytest +from rpmlint.checks.BinariesCheck import BinariesCheck +from rpmlint.filter import Filter +from rpmlint.lddparser import LddParser +from rpmlint.pkg import FakePkg, get_magic + +from Testing import CONFIG, get_tested_path, IS_X86_64 + + +@pytest.fixture(scope='function', autouse=True) +def binariescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = BinariesCheck(CONFIG, output) + return output, test + + +def get_full_path(path): + return str(get_tested_path(Path('ldd', path))) + + +def lddparser(path, system_path=None): + if system_path is None: + system_path = path + return LddParser(get_full_path(path), system_path, True) + + +def run_elf_checks(test, pkg, pkgfile): + test._detect_attributes(get_magic(pkgfile.path)) + test.run_elf_checks(pkg, pkgfile) + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_unused_dependency(): + ldd = lddparser('libtirpc.so.3.0.0') + assert not ldd.parsing_failed_reason + assert len(ldd.unused_dependencies) >= 1 + assert 'liXXXsapi_krb5.so.2' in ldd.unused_dependencies + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_undefined_symbol(): + ldd = lddparser('libtirpc.so.3.0.0') + assert not ldd.parsing_failed_reason + assert len(ldd.undefined_symbols) >= 22 + assert 'GSS_C_NT_HOSTBASED_SERVICE' in ldd.undefined_symbols + + +def test_ldd_parser_failure(): + ldd = lddparser('not-existing-file') + assert 'not-existing-file: No such file or directory' in ldd.parsing_failed_reason + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_dependencies(): + ldd = lddparser('libtirpc.so.3.0.0') + assert not ldd.parsing_failed_reason + assert len(ldd.dependencies) == 5 + assert any(d for d in ldd.dependencies if d.startswith('linux-vdso.so.1')) + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_unused_dependency_in_package(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('libtirpc.so.3.0.0'), '/lib64/x.so') + run_elf_checks(test, pkg, pkgfile) + assert not test.readelf_parser.parsing_failed_reason() + assert not test.ldd_parser.parsing_failed_reason + out = output.print_results(output.results) + assert 'E: unused-direct-shlib-dependency ' in out + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_unused_dependency_in_package_for_executable(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('appletviewer'), '/usr/bin/appletviewer') + run_elf_checks(test, pkg, pkgfile) + assert not test.readelf_parser.parsing_failed_reason() + assert not test.ldd_parser.parsing_failed_reason + out = output.print_results(output.results) + assert 'W: unused-direct-shlib-dependency ' in out + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_opt_dependency(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('opt-dependency'), '/bin/opt-dependency') + run_elf_checks(test, pkg, pkgfile) + assert not test.readelf_parser.parsing_failed_reason() + assert not test.ldd_parser.parsing_failed_reason + out = output.print_results(output.results) + assert 'E: linked-against-opt-library /bin/opt-dependency /opt/libfoo.so' in out + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_usr_dependency(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('usr-dependency'), '/bin/usr-dependency') + run_elf_checks(test, pkg, pkgfile) + assert not test.readelf_parser.parsing_failed_reason() + assert not test.ldd_parser.parsing_failed_reason + out = output.print_results(output.results) + assert 'W: linked-against-usr-library /bin/usr-dependency /usr/libfoo.so' in out diff --git a/test/test_lib_dependency.py b/test/test_lib_dependency.py new file mode 100644 index 0000000..3e28075 --- /dev/null +++ b/test/test_lib_dependency.py @@ -0,0 +1,31 @@ +import pytest +from rpmlint.checks.LibraryDependencyCheck import LibraryDependencyCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def libdependencycheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = LibraryDependencyCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/shlib2-devel']) +def test_shlib2_devel(tmp_path, package, libdependencycheck): + output, test = libdependencycheck + test.check(get_tested_package(package, tmp_path)) + test.after_checks() + out = output.print_results(output.results) + assert 'E: no-library-dependency-for /usr/lib/libfoo.so.1' in out + + +@pytest.mark.parametrize('package', ['binary/xrootd-devel']) +def test_missing_depency_on(tmp_path, package, libdependencycheck): + output, test = libdependencycheck + test.check(get_tested_package(package, tmp_path)) + test.after_checks() + out = output.print_results(output.results) + assert 'W: missing-dependency-on' not in out diff --git a/test/test_lint.py b/test/test_lint.py new file mode 100644 index 0000000..202245c --- /dev/null +++ b/test/test_lint.py @@ -0,0 +1,472 @@ +from pathlib import Path + +import pytest +from rpmlint.lint import Lint +from rpmlint.spellcheck import ENCHANT + +from Testing import ( + get_tested_path, HAS_CHECKBASHISMS, HAS_DASH, HAS_ENGLISH_DICTIONARY, HAS_RPMDB, + TEST_CONFIG +) + +TEST_RPMLINTRC = get_tested_path('configs/testing2-rpmlintrc') + +options_preset = { + 'config': TEST_CONFIG, + 'verbose': False, + 'strict': False, + 'permissive': False, + 'print_config': False, + 'explain': '', + 'rpmfile': '', + 'rpmlintrc': False, + 'installed': '', + 'time_report': False, + 'profile': False, + 'ignore_unused_rpmlintrc': False, + 'checks': None +} + +basic_tests = [ + 'AlternativesCheck', + 'AppDataCheck', + 'BinariesCheck', + 'BuildRootAndDateCheck', + 'ConfigFilesCheck', + 'DBusPolicyCheck', + 'DuplicatesCheck', + 'DocCheck', + 'ErlangCheck', + 'FHSCheck', + 'FilesCheck', + 'IconSizesCheck', + 'I18NCheck', + 'LibraryDependencyCheck', + 'LogrotateCheck', + 'MenuCheck', + 'MenuXDGCheck', + 'MixedOwnershipCheck', + 'PkgConfigCheck', + 'PostCheck', + 'PythonCheck', + 'SignatureCheck', + 'SourceCheck', + 'SpecCheck', + 'TagsCheck', + 'ZipCheck', + 'ZyppSyntaxCheck', +] + + +def _remove_except_zip(dictionary): + """ + In order to not lie in coverage redux the test run on the + tests to just ZipCheck which has full coverage + """ + return {'ZipCheck': dictionary['ZipCheck']} + + +def test_cases_loading(): + linter = Lint(options_preset) + assert list(linter.checks.keys()) == basic_tests + + +def test_configoutput(capsys): + additional_options = { + 'print_config': True, + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert out + assert 'Vendor = "Fedora Project"' in out + assert 're.compile' not in out + assert not err + + +def test_time_report(capsys): + additional_options = { + 'time_report': True, + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert out + assert 'Duration' in out + assert 'TOTAL' in out + + +def test_explain_unknown(capsys): + message = ['bullcrap'] + additional_options = { + 'explain': message, + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert 'bullcrap:\nUnknown message' in out + assert not err + + +def test_explain_known(capsys): + message = ['infopage-not-compressed'] + additional_options = { + 'explain': message, + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert 'This info page is not compressed' in out + assert 'Unknown message' not in out + assert not err + + +@pytest.mark.parametrize('configs', [ + # Message defined in configs/Fedora/warn-on-functions.toml + (Path('configs/Fedora/warn-on-functions.toml'), False), + (Path('configs/Fedora/scoring.toml'), True), +]) +def test_explain_known_warn_on_function(capsys, configs): + extraconfig, unknown = configs + message = ['crypto-policy-non-compliance-openssl'] + additional_options = { + 'explain': message, + 'config': [extraconfig], + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + + assert ('Unknown message' in out) == unknown + assert not err + + +def test_explain_with_unknown(capsys): + message = ['infopage-not-compressed', 'blablablabla'] + additional_options = { + 'explain': message, + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert 'This info page is not compressed' in out + assert 'Unknown message' in out + assert not err + + +def test_explain_no_binary_from_cfg(capsys): + """ + Test that 'explain' option can read updated description from configuration. + + Test 'no-binary' error that is defined in CheckBinaries.toml file by + default and then it's overridden to the custom values defined in + 'descriptions.config' file. + """ + additional_options = { + 'config': [get_tested_path('configs/descriptions.config')], + 'explain': ['no-binary'] + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + + # the new string is present and the old one is not + assert 'A new text for no-binary error.' in out + assert 'The package should be of the noarch architecture' not in out + assert not err + + +def test_explain_non_standard_dir_from_cfg(capsys): + """ + Test that 'explain' option can read updated description from configuration. + + Test 'non-standard-dir-in-usr' error that is special because the original + description is not defined in FHSCheck.toml but in FHSCheck.py. Then it's + supposed to be overridden to the custom values defined in + 'descriptions.config' file. + """ + additional_options = { + 'config': [get_tested_path('configs/descriptions.config')], + 'explain': ['non-standard-dir-in-usr'] + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + + assert 'A new text for non-standard-dir-in-usr error.' in out + assert 'Your package is creating a non-standard subdirectory in /usr' not in out + assert not err + + +@pytest.mark.skipif(not ENCHANT, reason='Optional dependency pyenchant not install') +@pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary') +@pytest.mark.parametrize('packages', [Path('test/binary/non-fhs-0-0.x86_64.rpm')]) +def test_descriptions_from_config(capsys, packages): + """ + Test that rpmlint updates 'parametrized' descriptions from configuration. + + We test that "parametrized" errors (non-standard-dir-in-usr + and non-standard-dir-in-var) were overridden by values from + 'descriptions.config' file. + """ + additional_options = { + 'config': [get_tested_path('configs/descriptions.config')], + 'rpmfile': [packages] + } + options_preset['verbose'] = True + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + + assert 'A new text for non-standard-dir-in-usr error.' in out + assert 'A new text for non-standard-dir-in-var error.' in out + + assert 'Your package is creating a non-standard subdirectory in /usr' \ + not in out + assert 'Your package is creating a non-standard subdirectory in /var' \ + not in out + assert not err + + +@pytest.mark.parametrize('packages', [Path('test/source/wrongsrc-0-0.src.rpm')]) +def test_run_single(capsys, packages): + additional_options = { + 'rpmfile': [packages], + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.checks = _remove_except_zip(linter.checks) + linter.run() + out, err = capsys.readouterr() + assert '1 packages and 0 specfiles checked' in out + assert not err + + +@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') +@pytest.mark.parametrize('packages', [Path('test/source/wrongsrc-0-0.src.rpm')]) +def test_run_installed(capsys, packages): + # load up 1 normal path file and 2 installed packages + additional_options = { + 'rpmfile': [packages], + 'installed': ['binutils', 'rpm'], + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.checks = _remove_except_zip(linter.checks) + linter.run() + out, err = capsys.readouterr() + assert '3 packages and 0 specfiles checked' in out + assert not err + + +@pytest.mark.parametrize('packages', [Path('test/binary/ruby2.5-rubygem-rubyzip-testsuite-1.2.1-0.x86_64.rpm')]) +def test_run_strict(capsys, packages): + """ + Test if we convert warning to error + """ + additional_options = { + 'rpmfile': [packages], + 'strict': True, + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.checks = _remove_except_zip(linter.checks) + linter.run() + out, err = capsys.readouterr() + assert 'W: unable-to-read-zip' not in out + assert 'E: unable-to-read-zip' in out + assert not err + + +@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') +def test_run_installed_not_present(capsys): + additional_options = { + 'rpmfile': [], + 'installed': ['non-existing-package'], + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.checks = _remove_except_zip(linter.checks) + linter.run() + out, err = capsys.readouterr() + assert '0 packages and 0 specfiles checked' in out + assert 'there is no installed rpm' in err + assert 'There are no files to process' in err + + +@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') +def test_run_installed_and_no_files(capsys): + additional_options = { + 'rpmfile': [], + 'installed': ['rpm'], + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.checks = _remove_except_zip(linter.checks) + linter.run() + out, err = capsys.readouterr() + assert '1 packages and 0 specfiles checked' in out + assert not err + + +@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') +def test_header_information(capsys): + additional_options = { + 'rpmfile': [], + 'installed': ['python3-rpm'], + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.checks = _remove_except_zip(linter.checks) + linter.run() + out, err = capsys.readouterr() + assert 'packages: 1' in out + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('packages', [list(Path('test').glob('*/*.rpm'))]) +@pytest.mark.parametrize('configs', [list(Path('configs').glob('*/*.toml'))]) +@pytest.mark.no_cover +def test_run_full_rpm(capsys, packages, configs): + number_of_pkgs = len(packages) + additional_options = { + 'rpmfile': packages, + } + options_preset['config'] = configs + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert f'{number_of_pkgs} packages and 0 specfiles checked' in out + # we convert the err as we don't care about errors from missing + # spellchecking dictionaries -> we have to ignore it + err_reduced = [a for a in err.split('\n') if not a.startswith('(none): W: unable to load spellchecking dictionary for') and a != ''] + # also we can find out signatures are wrong because of the other distros + # could've signed it + err_reduced = [a for a in err_reduced if not a.startswith('Error checking signature of')] + assert not err_reduced + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('packages', [list(Path('test/spec').glob('*.spec'))]) +@pytest.mark.parametrize('configs', [list(Path('configs').glob('*/*.toml'))]) +@pytest.mark.no_cover +def test_run_full_specs(capsys, packages, configs): + number_of_pkgs = len(packages) + additional_options = { + 'rpmfile': packages, + } + options_preset['config'] = configs + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert f'0 packages and {number_of_pkgs} specfiles checked' in out + assert not err + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('packages', [Path('test/spec')]) +@pytest.mark.no_cover +def test_run_full_directory(capsys, packages): + assert packages.is_dir() + file_list = [] + for item in packages.iterdir(): + if item.is_file(): + file_list.append(item) + number_of_pkgs = len(file_list) + additional_options = { + 'rpmfile': [packages], + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert f'0 packages and {number_of_pkgs} specfiles checked' in out + assert not err + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +def test_run_empty(capsys): + linter = Lint(options_preset) + linter.run() + out, err = capsys.readouterr() + assert err + assert '0 packages and 0 specfiles checked; 0 errors, 0 warnings' in out + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('packages', [Path('test/rpmlintrc/single')]) +def test_run_rpmlintrc_single_dir(capsys, packages): + additional_options = { + 'rpmfile': [packages], + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert not err + assert 'rpmlintrc:' in out + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('packages', [Path('test/rpmlintrc/multiple')]) +def test_run_rpmlintrc_multiple(capsys, packages): + additional_options = { + 'rpmfile': [packages], + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert 'rpmlintrc:' not in out + assert 'There are multiple items to be loaded for rpmlintrc' in err + assert '0 badness' in out + + +@pytest.mark.skipif(not HAS_CHECKBASHISMS, reason='Optional dependency checkbashisms not installed') +@pytest.mark.skipif(not HAS_DASH, reason='Optional dependency dash not installed') +@pytest.mark.parametrize('packages', [Path('test/rpmlintrc/single/sample.spec')]) +def test_run_rpmlintrc_single_file(capsys, packages): + additional_options = { + 'rpmfile': [packages], + 'rpmlintrc': TEST_RPMLINTRC + } + options = {**options_preset, **additional_options} + linter = Lint(options) + linter.run() + out, err = capsys.readouterr() + assert not err + assert 'rpmlintrc:' in out + assert 'E: unused-rpmlintrc-filter "I am not used"' in out + assert 'E: unused-rpmlintrc-filter "She is not used"' not in out + assert 'no-%build-section' not in out + + +@pytest.mark.skipif(not HAS_RPMDB, reason='No RPM database present') +def test_installed_package(capsys): + additional_options = { + 'installed': ['bzip2'], + 'permissive': True + } + options = {**options_preset, **additional_options} + linter = Lint(options) + retcode = linter.run() + out, err = capsys.readouterr() + assert '1 packages and 0 specfiles checked' in out + assert retcode == 0 diff --git a/test/test_logrotate.py b/test/test_logrotate.py new file mode 100644 index 0000000..7d4759c --- /dev/null +++ b/test/test_logrotate.py @@ -0,0 +1,24 @@ +import pytest +from rpmlint.checks.LogrotateCheck import LogrotateCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def logrotatecheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = LogrotateCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/logrotate']) +def test_logrotate(tmp_path, package, logrotatecheck): + output, test = logrotatecheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: logrotate-log-dir-not-packaged /var/log/myapp' in out + assert 'E: logrotate-duplicate /var/log/myapp' in out + assert 'E: logrotate-user-writable-log-dir /tmp/foo marxin:users 0755' in out + assert 'E: logrotate-user-writable-log-dir /tmp/foo2 root:users2 0777' in out diff --git a/test/test_menuxdg.py b/test/test_menuxdg.py index 36442e9..a8cabad 100644 --- a/test/test_menuxdg.py +++ b/test/test_menuxdg.py @@ -1,17 +1,72 @@ -import os +import pytest +from rpmlint.checks.MenuXDGCheck import MenuXDGCheck +from rpmlint.filter import Filter -import MenuXDGCheck -import Testing +from Testing import CONFIG, get_tested_package, HAS_DESKTOP_FILE_UTILS -class TestMenuXDGParsing(Testing.OutputTest): +@pytest.fixture(scope='function', autouse=True) +def menuxdgcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = MenuXDGCheck(CONFIG, output) + return output, test - @classmethod - def setup_class(cls): - cls.check = MenuXDGCheck.check.check - def test_raises_parse_error(self): - for package in ['menuxdg1']: - out = self._rpm_test_output(os.path.join('binary', package)) - assert 'contains parsing error' in "\n".join(out) - assert ' invalid-desktopfile ' in "\n".join(out) +@pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') +@pytest.mark.parametrize('package', ['binary/menuxdg1']) +def test_raises_parse_error(tmp_path, package, menuxdgcheck): + output, test = menuxdgcheck + test.check(get_tested_package(package, tmp_path)) + assert len(output.results) == 4 + out = output.print_results(output.results) + assert 'contains parsing error' in out + assert ' invalid-desktopfile ' in out + assert 'check with desktop-file-validate' in out + + +@pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') +@pytest.mark.parametrize('package', ['binary/desktopfile-bad-binary']) +def test_without_binary(tmp_path, package, menuxdgcheck): + output, test = menuxdgcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'desktopfile-without-binary' in out + + +@pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') +@pytest.mark.parametrize('package', ['binary/desktopfile-bad-duplicate']) +def test_duplicate(tmp_path, package, menuxdgcheck): + output, test = menuxdgcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'desktopfile-duplicate-section' in out + assert 'invalid-desktopfile' in out + + +@pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') +@pytest.mark.parametrize('package', ['binary/desktopfile-bad-section']) +def test_missing_header(tmp_path, package, menuxdgcheck): + output, test = menuxdgcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'desktopfile-missing-header' in out + assert 'invalid-desktopfile' in out + + +@pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') +@pytest.mark.parametrize('package', ['binary/desktopfile-bad-unicode']) +def test_bad_unicode(tmp_path, package, menuxdgcheck): + output, test = menuxdgcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'non-utf8-desktopfile' in out + + +@pytest.mark.skipif(not HAS_DESKTOP_FILE_UTILS, reason='Optional dependency desktop-file-utils not installed') +@pytest.mark.parametrize('package', ['binary/desktopfile-good']) +def test_good(tmp_path, package, menuxdgcheck): + output, test = menuxdgcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert not out diff --git a/test/test_mixed_ownership.py b/test/test_mixed_ownership.py new file mode 100644 index 0000000..7c998b0 --- /dev/null +++ b/test/test_mixed_ownership.py @@ -0,0 +1,23 @@ +import pytest +from rpmlint.checks.MixedOwnershipCheck import MixedOwnershipCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def mixedownershipcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = MixedOwnershipCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/mixed-ownership']) +def test_mixed_ownership(tmp_path, package, mixedownershipcheck): + output, test = mixedownershipcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'noproblem' not in out + assert 'file-parent-ownership-mismatch Path "/var/lib/badfolder/broken1" owned by "root" is stored in directory owned by "nobody"' in out + assert 'file-parent-ownership-mismatch Path "/var/lib/badfolder/correctperms" owned by "root" is stored in directory owned by "nobody"' in out diff --git a/test/test_objdump_parser.py b/test/test_objdump_parser.py new file mode 100644 index 0000000..5316eb0 --- /dev/null +++ b/test/test_objdump_parser.py @@ -0,0 +1,59 @@ +from pathlib import Path + +import pytest +from rpmlint.checks.BinariesCheck import BinariesCheck +from rpmlint.filter import Filter +from rpmlint.objdumpparser import ObjdumpParser +from rpmlint.pkg import FakePkg, get_magic + +from Testing import CONFIG, get_tested_path, IS_X86_64 + + +@pytest.fixture(scope='function', autouse=True) +def binariescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = BinariesCheck(CONFIG, output) + return output, test + + +def get_full_path(path): + return str(get_tested_path(Path('readelf', path))) + + +def objdumpparser(path, system_path=None): + if system_path is None: + system_path = path + return ObjdumpParser(get_full_path(path), system_path) + + +def run_elf_checks(test, pkg, pkgfile): + test._detect_attributes(get_magic(pkgfile.path)) + test.run_elf_checks(pkg, pkgfile) + + +def test_basic(): + objdump = objdumpparser('executable-stack', '/lib64/executable-stack') + assert not objdump.parsing_failed_reason + assert len(objdump.compile_units) == 5 + first = objdump.compile_units[0] + assert first['name'] == '../sysdeps/x86_64/start.S' + assert first['comp_dir'] == '/home/abuild/rpmbuild/BUILD/glibc-2.29/csu' + assert first['producer'] == 'GNU AS 2.32' + assert first['language'] == '32769\t(MIPS assembler)' + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_executable_stack_package(binariescheck): + output, test = binariescheck + + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('executable-stack'), 'a.out') + run_elf_checks(test, FakePkg('fake'), pkgfile) + out = output.print_results(output.results) + + if 'ldd-failed' in out: + pytest.skip("ldd failed, maybe it's a different architecture") + + assert 'W: missing-mandatory-optflags a.out -fno-PIE -g -Ofast' in out + assert 'E: forbidden-optflags a.out -frounding-math' in out diff --git a/test/test_pam_modules.py b/test/test_pam_modules.py new file mode 100644 index 0000000..78bf33c --- /dev/null +++ b/test/test_pam_modules.py @@ -0,0 +1,21 @@ +import pytest +from rpmlint.checks.PAMModulesCheck import PAMModulesCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def pammodulecheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = PAMModulesCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/pam-module']) +def test_pam_modules(tmp_path, package, pammodulecheck): + output, test = pammodulecheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: pam-unauthorized-module pam-module.so' in out diff --git a/test/test_pkg.py b/test/test_pkg.py new file mode 100644 index 0000000..ff85197 --- /dev/null +++ b/test/test_pkg.py @@ -0,0 +1,21 @@ +import rpm +from rpmlint.pkg import parse_deps, rangeCompare + + +def test_parse_deps(): + for (arg, exp) in ( + ('a, b < 1.0 c = 5:2.0-3 d', + [('a', 0, (None, None, None)), + ('b', rpm.RPMSENSE_LESS, (None, '1.0', None)), + ('c', rpm.RPMSENSE_EQUAL, (5, '2.0', '3')), + ('d', 0, (None, None, None))]), + ): + assert parse_deps(arg) == exp + + +def test_range_compare(): + for (req, prov) in ( + (('foo', rpm.RPMSENSE_LESS, (None, '1.0', None)), + ('foo', rpm.RPMSENSE_EQUAL, (1, '0.5', None))), + ): + assert not rangeCompare(req, prov) diff --git a/test/test_pkgconfig.py b/test/test_pkgconfig.py new file mode 100644 index 0000000..13771f4 --- /dev/null +++ b/test/test_pkgconfig.py @@ -0,0 +1,33 @@ +import pytest +from rpmlint.checks.PkgConfigCheck import PkgConfigCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def pkgconfigcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = PkgConfigCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/pc']) +def test_pkg_config(tmp_path, package, pkgconfigcheck): + output, test = pkgconfigcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: invalid-pkgconfig-file /tmp/pkgconfig/xcb.pc' in out + assert 'E: pkgconfig-invalid-libs-dir /tmp/pkgconfig/xcb.pc Libs: -L/usr/lib' in out + assert 'E: double-slash-in-pkgconfig-path /tmp/pkgconfig/xcb.pc includedir=/usr/include//xyz' in out + + +@pytest.mark.parametrize('package', ['binary/libreiserfscore-devel']) +def test_pkg_config_correct(tmp_path, package, pkgconfigcheck): + output, test = pkgconfigcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: invalid-pkgconfig-file' not in out + assert 'E: pkgconfig-invalid-libs-dir' not in out + assert 'E: double-slash-in-pkgconfig-path' not in out diff --git a/test/test_python.py b/test/test_python.py new file mode 100644 index 0000000..177e08d --- /dev/null +++ b/test/test_python.py @@ -0,0 +1,431 @@ +import pytest +from rpmlint.checks.PythonCheck import PythonCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_mock_package + + +@pytest.fixture(scope='function', autouse=True) +def pythoncheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = PythonCheck(CONFIG, output) + yield output, test + + +@pytest.fixture +def output(pythoncheck): + output, _test = pythoncheck + yield output + + +@pytest.fixture +def test(pythoncheck): + _output, test = pythoncheck + yield test + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files=[ + '/usr/lib/python2.7/site-packages/python-mypackage/doc', + '/usr/lib/python2.7/site-packages/python-mypackage/docs', + '/usr/lib/python3.10/site-packages/python-mypackage/doc', + '/usr/lib/python3.10/site-packages/python-mypackage/docs', + '/usr/lib64/python2.7/site-packages/python-mypackage/doc', + '/usr/lib64/python2.7/site-packages/python-mypackage/docs', + '/usr/lib64/python3.10/site-packages/python-mypackage/doc', + '/usr/lib64/python3.10/site-packages/python-mypackage/docs', + ] +)]) +def test_python_doc_in_package(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-doc-in-package /usr/lib/python2.7/site-packages/python-mypackage/doc' in out + assert 'W: python-doc-in-package /usr/lib/python2.7/site-packages/python-mypackage/docs' in out + assert 'W: python-doc-in-package /usr/lib/python3.10/site-packages/python-mypackage/doc' in out + assert 'W: python-doc-in-package /usr/lib/python3.10/site-packages/python-mypackage/docs' in out + assert 'W: python-doc-in-package /usr/lib64/python2.7/site-packages/python-mypackage/doc' in out + assert 'W: python-doc-in-package /usr/lib64/python2.7/site-packages/python-mypackage/docs' in out + assert 'W: python-doc-in-package /usr/lib64/python3.10/site-packages/python-mypackage/doc' in out + assert 'W: python-doc-in-package /usr/lib64/python3.10/site-packages/python-mypackage/docs' in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files={ + '/usr/lib/python2.7/site-packages/python-mypackage/doc/__init__.py': {'create_dirs': True, 'include_dirs': 2}, + '/usr/lib/python2.7/site-packages/python-mypackage/docs/__init__.py': {'create_dirs': True, 'include_dirs': 1}, + '/usr/lib64/python2.7/site-packages/python-mypackage/doc/__init__.py': {'create_dirs': True, 'include_dirs': 2}, + '/usr/lib64/python2.7/site-packages/python-mypackage/docs/__init__.py': {'create_dirs': True, 'include_dirs': 1}, + '/usr/lib/python3.10/site-packages/python-mypackage/doc/__init__.py': {'create_dirs': True, 'include_dirs': 2}, + '/usr/lib/python3.10/site-packages/python-mypackage/docs/__init__.py': {'create_dirs': True, 'include_dirs': 1}, + '/usr/lib64/python3.10/site-packages/python-mypackage/doc/__init__.py': {'create_dirs': True, 'include_dirs': 2}, + '/usr/lib64/python3.10/site-packages/python-mypackage/docs/__init__.py': {'create_dirs': True, 'include_dirs': 1}, + } +)]) +def test_python_doc_module_in_package(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-doc-in-package /usr/lib/python2.7/site-packages/python-mypackage/doc' not in out + assert 'W: python-doc-in-package /usr/lib/python2.7/site-packages/python-mypackage/docs' not in out + assert 'W: python-doc-in-package /usr/lib/python3.10/site-packages/python-mypackage/doc' not in out + assert 'W: python-doc-in-package /usr/lib/python3.10/site-packages/python-mypackage/docs' not in out + assert 'W: python-doc-in-package /usr/lib64/python2.7/site-packages/python-mypackage/doc' not in out + assert 'W: python-doc-in-package /usr/lib64/python2.7/site-packages/python-mypackage/docs' not in out + assert 'W: python-doc-in-package /usr/lib64/python3.10/site-packages/python-mypackage/doc' not in out + assert 'W: python-doc-in-package /usr/lib64/python3.10/site-packages/python-mypackage/docs' not in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files={ + '/usr/lib/python2.7/site-packages/mydistutilspackage.egg-info': {'content': 'Metadata-Version: 2.1\nName: pythoncheck'}, + '/usr/lib/python3.10/site-packages/mydistutilspackage.egg-info': {'content': 'Metadata-Version: 2.1\nName: pythoncheck'}, + '/usr/lib64/python2.7/site-packages/mydistutilspackage.egg-info': {'content': 'Metadata-Version: 2.1\nName: pythoncheck'}, + '/usr/lib64/python3.10/site-packages/mydistutilspackage.egg-info': {'content': 'Metadata-Version: 2.1\nName: pythoncheck'}, + }, +)]) +def test_python_distutils_egg_info(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'E: python-egg-info-distutils-style /usr/lib/python2.7/site-packages/mydistutilspackage.egg-info' in out + assert 'E: python-egg-info-distutils-style /usr/lib/python3.10/site-packages/mydistutilspackage.egg-info' in out + assert 'E: python-egg-info-distutils-style /usr/lib64/python2.7/site-packages/mydistutilspackage.egg-info' in out + assert 'E: python-egg-info-distutils-style /usr/lib64/python3.10/site-packages/mydistutilspackage.egg-info' in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files=[ + '/usr/lib/python2.7/site-packages/doc', + '/usr/lib/python2.7/site-packages/docs', + '/usr/lib/python3.10/site-packages/doc', + '/usr/lib/python3.10/site-packages/docs', + '/usr/lib64/python2.7/site-packages/doc', + '/usr/lib64/python2.7/site-packages/docs', + '/usr/lib64/python3.10/site-packages/doc', + '/usr/lib64/python3.10/site-packages/docs', + ] +)]) +def test_python_doc_in_site_packages(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'E: python-doc-in-site-packages /usr/lib/python2.7/site-packages/doc' in out + assert 'E: python-doc-in-site-packages /usr/lib/python2.7/site-packages/docs' in out + assert 'E: python-doc-in-site-packages /usr/lib/python3.10/site-packages/doc' in out + assert 'E: python-doc-in-site-packages /usr/lib/python3.10/site-packages/docs' in out + assert 'E: python-doc-in-site-packages /usr/lib64/python2.7/site-packages/doc' in out + assert 'E: python-doc-in-site-packages /usr/lib64/python2.7/site-packages/docs' in out + assert 'E: python-doc-in-site-packages /usr/lib64/python3.10/site-packages/doc' in out + assert 'E: python-doc-in-site-packages /usr/lib64/python3.10/site-packages/docs' in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files=[ + '/usr/lib/python2.7/site-packages/src', + '/usr/lib/python3.10/site-packages/src', + '/usr/lib64/python2.7/site-packages/src', + '/usr/lib64/python3.10/site-packages/src', + ] +)]) +def test_python_src_in_site_packages(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'E: python-src-in-site-packages /usr/lib/python2.7/site-packages/src' in out + assert 'E: python-src-in-site-packages /usr/lib/python3.10/site-packages/src' in out + assert 'E: python-src-in-site-packages /usr/lib64/python2.7/site-packages/src' in out + assert 'E: python-src-in-site-packages /usr/lib64/python3.10/site-packages/src' in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files=[ + '/usr/lib/python2.7/site-packages/test', + '/usr/lib/python2.7/site-packages/tests', + '/usr/lib/python3.10/site-packages/test', + '/usr/lib/python3.10/site-packages/tests', + '/usr/lib64/python2.7/site-packages/test', + '/usr/lib64/python2.7/site-packages/tests', + '/usr/lib64/python3.10/site-packages/test', + '/usr/lib64/python3.10/site-packages/tests', + ] +)]) +def test_python_tests_in_site_packages(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'E: python-tests-in-site-packages /usr/lib/python2.7/site-packages/test' in out + assert 'E: python-tests-in-site-packages /usr/lib/python2.7/site-packages/tests' in out + assert 'E: python-tests-in-site-packages /usr/lib/python3.10/site-packages/test' in out + assert 'E: python-tests-in-site-packages /usr/lib/python3.10/site-packages/tests' in out + assert 'E: python-tests-in-site-packages /usr/lib64/python2.7/site-packages/test' in out + assert 'E: python-tests-in-site-packages /usr/lib64/python2.7/site-packages/tests' in out + assert 'E: python-tests-in-site-packages /usr/lib64/python3.10/site-packages/test' in out + assert 'E: python-tests-in-site-packages /usr/lib64/python3.10/site-packages/tests' in out + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package( + files={ + '/usr/lib/python3.10/site-packages/flit-3.8.0.dist-info/METADATA': { + 'content-path': 'files/python-flit-metadata.txt', + 'create_dirs': True, + }, + }, + header={ + 'requires': [ + 'python-flit_core', + 'python-requests', + 'python-tomli-w', + 'python310-docutils', + ], + }, + ), + get_tested_mock_package( + files={ + '/usr/lib/python3.10/site-packages/jupyter_server_fileid-0.9.0.dist-info/METADATA': { + 'content-path': 'files/python-jupyter_server_fileid-metadata.txt', + 'create_dirs': True + }, + }, + header={ + 'requires': [ + 'python-jupyter-events', + 'python-click', + 'python-jupyter-server', + ], + }, + ), + get_tested_mock_package( + files={ + '/usr/lib/python3.10/site-packages/jupyter_events-0.6.3.dist-info/METADATA': { + 'content-path': 'files/python-jupyter-events-metadata.txt', + 'create_dirs': True + }, + }, + header={ + 'requires': [ + 'python-jsonschema', + 'python-python-json-logger', + 'python-pyyaml', + 'python-rfc3339-validator', + 'python-rfc3986-validator', + 'python-traitlets', + 'python-click', + 'python-rich', + 'python-jupyterlite-sphinx', + 'python-myst-parser', + 'python-pydata-sphinx-theme', + 'python-sphinxcontrib-spelling', + 'python-click', + 'python-coverage', + 'python-pre-commit', + 'python-pytest-asyncio', + 'python-pytest-console-scripts', + 'python-pytest-cov', + 'python-pytest', + 'python-rich', + ], + }, + ), + get_tested_mock_package( + files={ + '/usr/lib/python3.10/site-packages/scikit_build-0.17.2.dist-info/METADATA': { + 'content-path': 'files/python-scikit_build-metadata.txt', + 'create_dirs': True + }, + }, + header={ + 'requires': [ + 'python-distro', + 'python-packaging', + 'python-setuptools', + 'python-wheel', + 'python-tomli', + ], + }, + ), +]) +def test_python_dependencies_metadata(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-missing-require' not in out + assert 'W: python-leftover-require' not in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files={ + '/usr/lib/python3.10/site-packages/icecream-2.1.3-py3.10.egg-info/requires.txt': { + 'content': """ +asttokens>=2.0.1 +colorama>=0.3.9 +executing>=0.3.1 +pygments>=2.2.0 +""", + 'create_dirs': True + }, + }, + header={ + 'requires': [ + 'asttokens>=2.0.1', + 'colorama>=0.3.9', + 'executing>=0.3.1', + 'pygments>=2.2.0', + ], + }, +)]) +def test_python_dependencies_requires(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-missing-require' not in out + assert 'W: python-leftover-require' not in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files={ + '/usr/lib/python3.10/site-packages/icecream-2.1.3-py3.10.egg-info/requires.txt': { + 'content': """ +asttokens>=2.0.1 +colorama>=0.3.9 +executing>=0.3.1 +pygments>=2.2.0 +""", + 'create_dirs': True + }, + }, + header={ + 'requires': [ + 'asttokens>=2.0.1', + 'executing>=0.3.1', + 'pygments>=2.2.0', + ], + }, +)]) +def test_python_dependencies_missing_requires(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-missing-require' in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files={ + '/usr/lib/python3.10/site-packages/flit-3.8.0.dist-info/METADATA': { + 'content-path': 'files/python-flit-metadata.txt', + 'create_dirs': True + }, + }, + header={ + 'requires': [ + 'python3-flit-core', + 'python3-requests', + 'python3-tomli-w', + ], + }, +)]) +def test_python_dependencies_missing_metadata(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-missing-require' in out + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package( + files={ + '/usr/lib/python3.10/site-packages/icecream-2.1.3-py3.10.egg-info/requires.txt': { + 'content': """ +asttokens>=2.0.1 +colorama>=0.3.9 +executing>=0.3.1 +pygments>=2.2.0 +""", + 'create_dirs': True + }, + }, + header={ + 'requires': [ + 'python3-asttokens >= 2.0.1', + 'python3-colorama >= 0.3.9', + 'python3-executing >= 0.3.1', + 'python3-poetry', + 'python3-pygments >= 2.2.0', + ], + }, + ), + get_tested_mock_package( + files={ + '/usr/lib/python3.10/site-packages/flit-3.8.0.dist-info/METADATA': { + 'content-path': 'files/python-flit-metadata.txt', + 'create_dirs': True + }, + }, + header={ + 'requires': [ + 'python3-docutils', + 'python3-flit-core', + 'python3-poetry', + 'python3-requests', + 'python3-tomli-w', + ], + }, + ), +]) +def test_python_dependencies_leftover(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-leftover-require' in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files=[ + '/usr/lib/python3.9/site-packages/blinker/__pycache__/base.cpython-310.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/base.cpython-39.opt-1.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/base.cpython-39.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/__init__.cpython-310.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/__init__.cpython-39.opt-1.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/__init__.cpython-39.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_saferef.cpython-310.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_saferef.cpython-39.opt-1.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_saferef.cpython-39.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_utilities.cpython-310.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_utilities.cpython-39.opt-1.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_utilities.cpython-39.pyc', + ] +)]) +def test_python_pyc_multiple_versions(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-pyc-multiple-versions expected: 310' in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + files=[ + '/usr/lib/python3.9/site-packages/blinker/__pycache__/base.cpython-39.opt-1.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/base.cpython-39.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/__init__.cpython-39.opt-1.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/__init__.cpython-39.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_saferef.cpython-39.opt-1.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_saferef.cpython-39.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_utilities.cpython-39.opt-1.pyc', + '/usr/lib/python3.9/site-packages/blinker/__pycache__/_utilities.cpython-39.pyc', + ] +)]) +def test_python_pyc_single_version(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-pyc-multiple-versions' not in out + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package(files=['/usr/share/doc/packages/python-blinker-doc/.doctrees']), + get_tested_mock_package(files=['/usr/lib/python3.11/site-packages/python-blinker/.doctrees']), +]) +def test_python_sphinx_doctrees_leftover_warn(package, output, test): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-sphinx-doctrees-leftover' in out + + +@pytest.mark.parametrize('package', [ + get_tested_mock_package(files=['/usr/lib/python3.11/site-packages/python-blinker/doctrees.py']), + get_tested_mock_package(files=['/usr/share/doc/packages/python-blinker-doc/doctrees']), + get_tested_mock_package(files=['/usr/share/doc/packages/python-blinker-doc/.doctrees.html']), +]) +def test_python_sphinx_doctrees_leftover_nowarn(package, output, test): + test.check(package) + out = output.print_results(output.results) + assert 'W: python-sphinx-doctrees-leftover' not in out diff --git a/test/test_readelf_parser.py b/test/test_readelf_parser.py new file mode 100644 index 0000000..28df231 --- /dev/null +++ b/test/test_readelf_parser.py @@ -0,0 +1,292 @@ +from pathlib import Path +import re + +import pytest +from rpmlint.checks.BinariesCheck import BinariesCheck +from rpmlint.filter import Filter +from rpmlint.pkg import FakePkg, get_magic +from rpmlint.pkgfile import PkgFile +from rpmlint.readelfparser import ReadelfParser + +from Testing import CONFIG, get_tested_path, HAS_32BIT_GLIBC, IS_I686, IS_X86_64 + + +@pytest.fixture(scope='function', autouse=True) +def binariescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = BinariesCheck(CONFIG, output) + return output, test + + +def get_full_path(path): + return str(get_tested_path(Path('readelf', path))) + + +def readelfparser(path, system_path=None): + if system_path is None: + system_path = path + return ReadelfParser(get_full_path(path), system_path) + + +def run_elf_checks(test, pkg, pkgfile): + test._detect_attributes(get_magic(pkgfile.path)) + test.run_elf_checks(pkg, pkgfile) + + +def test_empty_archive(): + readelf = readelfparser('empty-archive.a') + assert len(readelf.section_info.elf_files) == 0 + assert len(readelf.symbol_table_info.functions) == 0 + + +def test_simple_archive(): + readelf = readelfparser('main.a') + assert readelf.is_archive + assert len(readelf.section_info.elf_files) == 1 + elf_file = readelf.section_info.elf_files[0] + assert len(elf_file) == 11 + assert elf_file[0].name == '.text' + assert elf_file[0].size == 21 + assert readelf.symbol_table_info.functions == {'main'} + assert len(list(readelf.symbol_table_info.get_functions_for_regex(re.compile('mai.')))) == 1 + + +def test_program_header_parsing(): + readelf = readelfparser('nested-function') + assert len(readelf.program_header_info.headers) == 11 + h0 = readelf.program_header_info.headers[0] + assert h0.name == 'PHDR' + assert h0.flags == 'R' + h9 = readelf.program_header_info.headers[9] + assert h9.name == 'GNU_STACK' + assert h9.flags == 'RWE' + + +def test_dynamic_section_parsing(): + readelf = readelfparser('libutil-2.29.so', '/lib64/libutil-2.29.so') + assert readelf.is_shlib + assert not readelf.is_archive + sections = readelf.dynamic_section_info.sections + assert len(sections) == 30 + assert sections[0].key == 'NEEDED' + assert sections[0].value == 'Shared library: [libc.so.6]' + assert readelf.dynamic_section_info['SYMTAB'] == ['0x4c8'] + assert readelf.dynamic_section_info['NULL'] == ['0x0'] + assert readelf.dynamic_section_info.soname == 'libutil.so.1' + assert len(readelf.dynamic_section_info.needed) == 1 + assert readelf.dynamic_section_info.needed[0] == 'libc.so.6' + + +def test_rpath(): + readelf = readelfparser('rpath-lib.so', '/lib64/rpath-lib.so') + assert readelf.is_shlib + assert not readelf.is_archive + assert len(readelf.dynamic_section_info.runpaths) == 1 + assert '/tmp/termcap.so.4' in readelf.dynamic_section_info.runpaths + + +def test_lto_bytecode(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('lto-object.o'), 'x.a') + run_elf_checks(test, pkg, pkgfile) + assert not test.readelf_parser.parsing_failed_reason() + out = output.print_results(output.results) + assert 'lto-bytecode' in out + + +def test_lto_archive_text(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('stripped-lto.a'), 'x.a') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'E: lto-no-text-in-archive' in out + assert 'E: static-library-without-debuginfo' in out + + +def test_stripped_archive(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('stripped-archive.a'), 'x.a') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'E: static-library-without-symtab' in out + + +def test_lto_archive_text_function_sections(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('function-sections.a'), 'x.a') + run_elf_checks(test, pkg, pkgfile) + assert 'E: lto-no-text-in-archive' not in output.print_results(output.results) + + +def test_lto_archive_init_array(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('libbsd-ctor.a'), 'x.a') + run_elf_checks(test, pkg, pkgfile) + assert 'E: lto-no-text-in-archive' not in output.print_results(output.results) + + +def test_lto_archive_preinit_array(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('libclang_rt.asan-preinit-x86_64.a'), 'x.a') + run_elf_checks(test, pkg, pkgfile) + assert 'E: lto-no-text-in-archive' not in output.print_results(output.results) + + +def test_lto_archive_with_only_data(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('only-data.a'), 'x.a') + run_elf_checks(test, pkg, pkgfile) + assert 'E: lto-no-text-in-archive' not in output.print_results(output.results) + + +def test_archive_with_debuginfo(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('archive-with-debuginfo.a'), 'x.a') + run_elf_checks(test, pkg, pkgfile) + assert 'E: static-library-without-debuginfo' not in output.print_results(output.results) + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_executable_stack(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('executable-stack'), '/lib64/my/a.out') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + + if 'ldd-failed' in out: + pytest.skip("ldd failed, maybe it's a different architecture") + + assert 'E: executable-stack /lib64/my/a.out' in out + + +def test_readelf_failure(): + readelf = readelfparser('not-existing-file') + assert 'No such file' in readelf.parsing_failed_reason() + + +def test_readelf_failure_in_package(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('not-existing.so'), '/lib64/not-existing.so') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'readelf-failed /lib64/not-existing.so' in out + + +def test_readelf_single_error_message(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('small_archive.a'), '/lib64/small_archive.a') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + filtered = [line for line in out.splitlines() if 'Not an ELF file' in line] + assert len(filtered) == 1 + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_no_soname(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('no-soname.so'), '/lib64/no-soname.so') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'no-soname /lib64/no-soname.so' in out + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_invalid_soname(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('invalid-soname.so'), '/lib64/invalid-soname.so') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'invalid-soname /lib64/invalid-soname.so' in out + assert 'E: shlib-with-non-pic-code /lib64/invalid-soname.so' not in out + + +@pytest.mark.skipif(not IS_I686 and (not IS_X86_64 or not HAS_32BIT_GLIBC), reason='i686 glibc only') +def test_non_pic_code_library(binariescheck): + output, test = binariescheck + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('non-pic-shared-m32.so'), '/usr/lib/non-pic-shared-m32.so') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'E: shlib-with-non-pic-code' in out + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_no_ldconfig_symlink(binariescheck): + output, test = binariescheck + + with FakePkg('libfake') as pkg: + pkgfile = pkg.add_file(get_full_path('libutil-2.29.so'), '/lib64/libutil-2.29.so') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'no-ldconfig-symlink /lib64/libutil-2.29.so' in out + assert 'E: shlib-policy-name-error SONAME: libutil.so.1 (/lib64/libutil-2.29.so), expected package suffix: 1' in out + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_call_mktemp(binariescheck): + output, test = binariescheck + + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('call-mktemp'), '/bin/call-mktemp') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'E: call-to-mktemp /bin/call-mktemp' in out + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_call_setgroups(binariescheck): + output, test = binariescheck + + with FakePkg('fake') as pkg: + pkgfile = PkgFile('/bin/call-setgroups') + pkgfile.path = get_full_path('call-setgroups') + pkg.files[pkgfile.name] = pkgfile + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'E: missing-call-to-setgroups-before-setuid /bin/call-setgroups' in out + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_call_gethostbyname(binariescheck): + output, test = binariescheck + + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('hostname'), '/usr/bin/hostname') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'W: binary-or-shlib-calls-gethostbyname' in out + + +def test_bca_files(binariescheck): + output, test = binariescheck + + with FakePkg('fake') as pkg: + pkgfile = pkg.add_file(get_full_path('libkleeRuntimeFreeStanding.bca'), '/usr/lib64/klee/runtime/libkleeRuntimeFreeStanding.bca') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'E: ' not in out + + +@pytest.mark.skipif(not IS_X86_64, reason='x86-64 only') +def test_shlib_policy_name_error(binariescheck): + output, test = binariescheck + + with FakePkg('libgame') as pkg: + pkgfile = pkg.add_file(get_full_path('libgame.so'), '/lib64/libgame.so') + run_elf_checks(test, pkg, pkgfile) + out = output.print_results(output.results) + assert 'libgame: E: shlib-policy-name-error SONAME: libgame2-1.9.so.10.0.0 (/lib64/libgame.so), expected package suffix: 1_9-10_0_0' in out diff --git a/test/test_scl.py b/test/test_scl.py deleted file mode 100644 index edb9342..0000000 --- a/test/test_scl.py +++ /dev/null @@ -1,220 +0,0 @@ -import os -import sys - -import SCLCheck -import Testing - - -# add rpmlint-scl, rpmlint and rpmlint/tools to PATH -# also add rpmlint-scl/tools, so this keeps working once merged with rpmlint -for directory in ['../rpmlint/tools', '../rpmlint', '../tools', '..']: - sys.path.insert(0, os.path.join(os.path.dirname(__file__), directory)) - - -class TestSCLBasic(Testing.OutputTest): - '''Basic tests of Software Collections checks''' - - @classmethod - def setup_class(cls): - cls.check = SCLCheck.check.check - cls.check_spec = SCLCheck.check.check_spec - - def test_nonscl_spec_silent(self): - '''SCL check on non-SCL spec has to be silent''' - assert not self._spec_test_output('spec/SpecCheck') - - def test_nonscl_binary_silent(self): - ''' - SCL check on non-SCL binary RPM has to be silent even with - suspicious filename - ''' - assert not self._rpm_test_output('binary/python3-power') - - def test_bunch_of_scl_source_rpms(self): - ''' - A bunch of testing source RPM packages using SCL - Assuming they are all OK and except silent output - While adding more checks, this might change - ''' - for package in ['nodejs010-1', 'nodejs010-nodejs-0.10.3', - 'nodejs010-nodejs-forever']: - assert not self._rpm_test_output(os.path.join('source', package)) - - def test_bunch_of_scl_binary_rpms(self): - ''' - A bunch of testing binary RPM packages using SCL - Assuming they are all OK and except silent output - While adding more checks, this might change - ''' - for package in ['nodejs010-runtime', 'nodejs010-nodejs-0.10.3', - 'nodejs010-nodejs-oauth']: - assert not self._rpm_test_output(os.path.join('binary', package)) - - def test_correct_nodejs(self): - '''Tests probably correct nodejs.spec and nodejs010.spec''' - assert not self._spec_test_output('spec/nodejs-good') - assert not self._spec_test_output('spec/nodejs010') - - def test_undeclared(self): - '''Tests SCL specs without %scl definition or %scl_package calls''' - for spec in ['nodejs010', 'nodejs']: - out = self._spec_test_output('spec/%s-undeclared' % spec) - assert len(out) == 1 - assert 'undeclared-scl' in out[0] - - -class TestSCLMain(Testing.OutputTest): - '''Tests of Software Collections main package checks''' - - @classmethod - def setup_class(cls): - cls.check = SCLCheck.check.check - cls.check_spec = SCLCheck.check.check_spec - - def test_nobuild(self): - '''Tests SCL metapackage without build subpackage''' - out = self._spec_test_output('spec/nodejs010-nobuild') - assert len(out) == 1 - assert 'no-build-in-scl-metapackage' in out[0] - - def test_noruntime(self): - '''Tests SCL metapackage without runtime subpackage''' - out = self._spec_test_output('spec/nodejs010-noruntime') - assert len(out) == 2 - out = '\n'.join(out) - assert 'no-runtime-in-scl-metapackage' in out - assert 'scl-main-metapackage-contains-files' in out - - def test_missing_requires(self): - '''Tests SCL metapackage without scl-utils-build (B)Rs''' - out = self._spec_test_output('spec/nodejs010-missing-requires') - assert len(out) == 2 - out = '\n'.join(out) - assert 'scl-metapackage-without-scl-utils-build-br' in out - assert 'scl-build-without-requiring-scl-utils-build' in out - - def test_alien_subpackage(self): - '''Tests SCL metapackage with extra subpackage''' - for diff in ['', '-n']: - out = self._spec_test_output( - 'spec/nodejs010-alien-subpackage' + diff) - assert len(out) == 1 - assert 'weird-subpackage-in-scl-metapackage' in out[0] - assert 'hehe' in out[0] - - def test_nosclinstall(self): - '''Tests SCL metapackage that doesn't call %scl_install''' - out = self._spec_test_output('spec/nodejs010-nosclinstall') - assert len(out) == 1 - assert 'scl-metapackage-without-%scl_install' in out[0] - - def test_noarch(self): - '''Tests noarch SCL metapackages (not) containing %{_libdir}''' - assert not self._spec_test_output('spec/nodejs010-noarch-good') - out = self._spec_test_output('spec/nodejs010-noarch-libdir') - assert len(out) == 1 - assert 'noarch-scl-metapackage-with-libdir' in out[0] - - def test_badfiles(self): - '''Tests SCL metapackage %files section checks''' - out = self._spec_test_output('spec/nodejs010-badfiles') - assert len(out) == 3 - out = '\n'.join(out) - assert 'scl-main-metapackage-contains-files' in out - assert 'scl-runtime-package-without-%scl_files' in out - assert 'scl-build-package-without-rpm-macros' in out - - -class TestSCLSource(Testing.OutputTest): - '''Tests of Software Collections enabled package spec checks''' - - @classmethod - def setup_class(cls): - cls.check = SCLCheck.check.check - cls.check_spec = SCLCheck.check.check_spec - - def test_no_pkg_name(self): - '''Tests SCL spec without pkg_name definition''' - out = self._spec_test_output('spec/nodejs-no-pkg_name') - assert len(out) == 1 - assert 'missing-pkg_name-definition' in out[0] - - def test_name_without_prefix(self): - '''Tests SCL spec without prefixed name''' - out = self._spec_test_output('spec/nodejs-name-without-prefix') - assert len(out) == 1 - assert 'name-without-scl-prefix' in out[0] - - def test_name_with_prefix_without_condition(self): - ''' - Tests SCL spec with prefixed name without condition in scl_prefix macro - ''' - out = self._spec_test_output( - 'spec/nodejs-name-with-noncondition-prefix') - assert len(out) == 1 - assert 'scl-prefix-without-condition' in out[0] - - def test_conflicts_without_prefix(self): - '''Tests SCL spec with nonprefixed conflicts''' - out = self._spec_test_output('spec/nodejs-conflicts-without-prefix') - assert len(out) == 1 - assert 'obsoletes-or-conflicts-without-scl-prefix' in out[0] - - def test_provides_without_prefix(self): - '''Tests SCL spec with nonprefixed conflicts''' - out = self._spec_test_output('spec/nodejs-provides-without-prefix') - assert len(out) == 1 - assert 'provides-without-scl-prefix' in out[0] - - def test_main_package_without_scl_require(self): - ''' - Tests SCL spec where the main package doesn't require anything - from collection - ''' - out = self._spec_test_output('spec/nodejs-norequire') - assert len(out) == 1 - assert 'doesnt-require-scl-runtime-or-other-scl-package' in out[0] - - def test_n_supbackage_without_prefix(self): - ''' - Tests SCL spec where a subpackage uses -n and doesn't start with - SCL prefix - ''' - out = self._spec_test_output('spec/nodejs-n-noprefix') - assert len(out) == 1 - assert 'subpackage-with-n-without-scl-prefix' in out[0] - - def test_setup_without_n(self): - '''Tests SCL spec where setup doesn't use -n option''' - out = self._spec_test_output('spec/nodejs-setup-no-n') - assert len(out) == 1 - assert 'scl-setup-without-n' in out[0] - - -class TestSCLBinary(Testing.OutputTest): - '''Tests of Software Collections binary RPMs''' - - @classmethod - def setup_class(cls): - cls.check = SCLCheck.check.check - cls.check_spec = SCLCheck.check.check_spec - - def test_scl_name_screwed_up(self): - ''' - SCL check on SCL package that differs it's name from scl tree folder - ''' - out = self._rpm_test_output('binary/nodejs110-nodejs-oauth') - assert len(out) == 1 - assert 'scl-name-screwed-up' in out[0] - - def test_scl_forbidden_folders(self): - '''SCL check on SCL package that has files in forbidden folders''' - out = self._rpm_test_output('binary/outside-nodejs010-nodejs-oauth') - assert len(out) == 1 - assert 'file-outside-of-scl-tree' in out[0] - - def test_scl_macros_outside_of_build(self): - '''SCL check on SCL package that has files in forbidden folders''' - out = self._rpm_test_output('binary/macros-nodejs010-nodejs-oauth') - assert len(out) == 1 - assert 'scl-rpm-macros-outside-of-build' in out[0] diff --git a/test/test_shlib_policy.py b/test/test_shlib_policy.py new file mode 100644 index 0000000..3538922 --- /dev/null +++ b/test/test_shlib_policy.py @@ -0,0 +1,37 @@ +import pytest +from rpmlint.checks.SharedLibraryPolicyCheck import SharedLibraryPolicyCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def slpcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = SharedLibraryPolicyCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/libtest1']) +def test_shlib_policy_wrong_name(tmp_path, package, slpcheck): + output, test = slpcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: shlib-unversioned-lib libtest.so.1x' in out + + +@pytest.mark.parametrize('package', ['binary/libslp-missing-suffix']) +def test_shlib_policy_missing_suffix(tmp_path, package, slpcheck): + output, test = slpcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: shlib-policy-excessive-dependency libsparta.so.2' in out + + +@pytest.mark.parametrize('package', ['binary/libslp1234']) +def test_shlib_policy_errors(tmp_path, package, slpcheck): + output, test = slpcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: shlib-fixed-dependency libsparta.so.2 = 1.23' in out diff --git a/test/test_signature.py b/test/test_signature.py new file mode 100644 index 0000000..18b45d2 --- /dev/null +++ b/test/test_signature.py @@ -0,0 +1,51 @@ +import pytest +from rpmlint.checks.SignatureCheck import SignatureCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def signaturecheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = SignatureCheck(CONFIG, output) + return output, test + + +# The signature was stripped via "rpmsign --delsign <package>" +@pytest.mark.parametrize('package', ['binary/no-signature']) +def test_no_signature(tmp_path, package, signaturecheck): + output, test = signaturecheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: no-signature' in out + assert 'E: unknown-key' not in out + assert 'E: invalid-signature' not in out + + +# The test rpm was signed with gpg key created for this purpose that is not +# imported in rpm db and therefore unknown-key error should be thrown +@pytest.mark.parametrize('package', ['binary/unknown-key']) +def test_unknown_key(tmp_path, package, signaturecheck): + output, test = signaturecheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: unknown-key 31fdc502' in out + assert 'E: no-signature' not in out + assert 'E: invalid-signature' not in out + + +# The test rpm hello-2.0-1.x86_64-signed.rpm was taken from +# https://github.com/rpm-software-management/rpm/blob/master/tests/data/RPMS/ +# and then the signature was corrupted by running "dd if=/dev/zero +# of=hello-2.0-1.x86_64-signed.rpm conv=notrunc bs=1 seek=264 count=6 +# 2> /dev/null" +@pytest.mark.parametrize('package', ['binary/hello']) +def test_invalid_signature(tmp_path, package, signaturecheck): + output, test = signaturecheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: invalid-signature' in out + assert 'E: no-signature' not in out + assert 'E: unknown-key' not in out diff --git a/test/test_sources.py b/test/test_sources.py index 0437511..29c19a3 100644 --- a/test/test_sources.py +++ b/test/test_sources.py @@ -1,16 +1,41 @@ -import os +import pytest +from rpmlint.checks.SourceCheck import SourceCheck +from rpmlint.filter import Filter -import SourceCheck -import Testing +from Testing import CONFIG, get_tested_package -class TestSourceCheck(Testing.OutputTest): +@pytest.fixture(scope='function', autouse=True) +def sourcescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = SourceCheck(CONFIG, output) + return output, test - @classmethod - def setup_class(cls): - cls.check = SourceCheck.check.check - def test_inconsistent_file_extension(self): - for package in ['wrongsrc']: - out = self._rpm_test_output(os.path.join('source', package)) - assert 'inconsistent-file-extension' in "\n".join(out) +@pytest.mark.parametrize('package', ['source/wrongsrc']) +def test_extension_and_permissions(tmp_path, package, sourcescheck): + output, test = sourcescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + + assert len(output.results) == 1 + + assert 'inconsistent-file-extension' in out + assert 'name extension indicates a different compression format' in out + + assert 'strange-permission' not in out + assert 'a file should have' not in out + + +@pytest.mark.parametrize('package', ['source/not-compressed-multi-spec']) +def test_compression_and_multispec(tmp_path, package, sourcescheck): + output, test = sourcescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + + assert 'source-not-compressed' in out + assert 'source archive or file in your package is not compressed' in out + + assert 'multiple-specfiles' in out + assert 'package contains multiple spec files' in out diff --git a/test/test_speccheck.py b/test/test_speccheck.py new file mode 100644 index 0000000..a322c5d --- /dev/null +++ b/test/test_speccheck.py @@ -0,0 +1,1193 @@ +import re + +import pytest +from rpmlint.checks.SpecCheck import SpecCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package, get_tested_spec_package + + +@pytest.fixture(scope='function', autouse=True) +def speccheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = SpecCheck(CONFIG, output) + yield output, test + + +@pytest.fixture +def output(speccheck): + output, _test = speccheck + yield output + + +@pytest.fixture +def test(speccheck): + _output, test = speccheck + yield test + + +def test_check_include(tmp_path, speccheck): + output, test = speccheck + test.check_source(get_tested_package('source/CheckInclude', tmp_path)) + out = output.print_results(output.results) + assert "specfile-error can't parse specfile" not in out + assert 'no-buildroot-tag' in out + assert 'E: specfile-error error: query of specfile' not in out + + +@pytest.mark.parametrize('package', [ + 'spec/SpecCheck2', + 'spec/SpecCheck3', + 'spec/SpecCheckPatch', + 'spec/intltool', +]) +def test_patch_not_applied(package, speccheck): + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'patch-not-applied' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck']) +def test_distribution_tags(package, speccheck): + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'patch-not-applied Patch3' in out + assert not re.search(r'patch-not-applied Patch\b', out) + assert not re.search('patch-not-applied Patch[0124567]', out) + assert 'libdir-macro-in-noarch-package' not in out + assert len(re.findall('macro-in-comment', out)) == 1 + assert 'unversioned-explicit-provides unversioned-provides' in out + assert 'unversioned-explicit-provides versioned-provides' not in out + assert 'unversioned-explicit-provides /' not in out + assert 'unversioned-explicit-obsoletes unversioned-obsoletes' in out + assert 'unversioned-explicit-obsoletes versioned-obsoletes' not in out + assert 'unversioned-explicit-obsoletes /' not in out + assert 'setup-not-quiet' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck4']) +def test_forbidden_controlchars_found(package, speccheck): + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: forbidden-controlchar-found Requires:' in out + assert 'E: forbidden-controlchar-found Provides:' in out + assert 'E: forbidden-controlchar-found Obsoletes:' in out + assert 'E: forbidden-controlchar-found Conflicts:' in out + assert 'E: forbidden-controlchar-found %changelog:' in out + + +@pytest.mark.parametrize('package', ['source/no-spec-file']) +def test_check_no_spec_file(tmp_path, package, speccheck): + """Test if spec file is not found inside RPM metadata.""" + output, test = speccheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: no-spec-file' in out + + +@pytest.mark.parametrize('package', ['source/CheckInclude']) +def test_check_no_spec_file_not_applied(tmp_path, package, speccheck): + """Test if there is no spec file inside RPM metadata.""" + output, test = speccheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: no-spec-file' not in out + + +@pytest.mark.parametrize('package', ['spec/non-utf8-spec-file']) +def test_check_non_utf8_spec_file(package, speccheck): + """Test if specfile does not have UTF-8 character encoding.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: non-utf8-spec-file' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_non_utf8_spec_file_not_applied(package, speccheck): + """Test if specfile has UTF-8 character encoding.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: non-utf8-spec-file' not in out + + +@pytest.mark.parametrize('package', ['source/invalid-spec-name']) +def test_check_invalid_spec_name(tmp_path, package, speccheck): + """Test if specfile name does not matches the ('Name: ') tag.""" + output, test = speccheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: invalid-spec-name' in out + + +@pytest.mark.parametrize('package', ['source/CheckInclude']) +def test_check_invalid_spec_name_not_applied(tmp_path, package, speccheck): + """Test if specfile has specfile name as ('Name: ') tag.""" + output, test = speccheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: invalid-spec-name' not in out + + +@pytest.mark.parametrize('package', ['spec/non-break-space']) +def test_check_non_break_space(package, speccheck): + """Test if specfile has a nbsp character.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: non-break-space' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_non_break_space_not_applied(package, speccheck): + """Test if specfile does not have any nbsp character.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: non-break-space' not in out + + +@pytest.mark.parametrize('package', ['spec/rpm-buildroot-usage']) +def test_check_rpm_buildroot_usage_under_prep(package, speccheck): + """Test if specfile has buildroot macro under %prep.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: rpm-buildroot-usage' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_rpm_buildroot_usage_under_build(package, speccheck): + """Test if specfile has buildroot macro under %build.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: rpm-buildroot-usage' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_rpm_buildroot_usage_not_applied(package, speccheck): + """Test if specfile does not have buildroot macro inside specfile.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: rpm-buildroot-usage' not in out + + +@pytest.mark.parametrize('package', ['spec/rpm-buildroot-usage-shell-var']) +def test_check_rpm_buildroot_usage_shell_var(package, speccheck): + """Test detection of $RPM_BUILD_ROOT shell variable in %prep/%build""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: rpm-buildroot-usage %prep' not in out + assert out.count('E: rpm-buildroot-usage %build') == 2 + + +@pytest.mark.parametrize('package', ['spec/make-check-outside-check-section']) +def test_check_make_check_outside_check_section(package, speccheck): + """Test if specfile has `make check` outside %check.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: make-check-outside-check-section' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_make_check_inside_check_section(package, speccheck): + """Test if specfile has `make check` inside all the required + ('check', 'changelog', 'package', 'description') section. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: make-check-outside-check-section' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_make_check_outside_not_applied(package, speccheck): + """Test if specfile does not have any `make check`.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: make-check-outside-check-section' not in out + + +@pytest.mark.parametrize('package', ['spec/setup-not-quiet']) +def test_check_setup_not_quiet(package, speccheck): + """Test if specfile does not have %setup -q macro.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: setup-not-quiet' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_setup_is_quiet(package, speccheck): + """Test if specfile has a %setup -q macro.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: setup-not-quiet' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_setup_not_quiet_not_applied(package, speccheck): + """Test if specfile does not have a setup macro.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: setup-not-quiet' not in out + + +@pytest.mark.parametrize('package', ['spec/setup-not-in-prep']) +def test_check_setup_not_in_prep(package, speccheck): + """Test if specfile does not have %setup inside %prep.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: setup-not-in-prep' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_setup_inside_prep(package, speccheck): + """Test if specfile has %setup inside %prep.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: setup-not-in-prep' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_setup_not_in_prep_not_applied(package, speccheck): + """Test if specfile has no %setup.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: setup-not-in-prep' not in out + + +@pytest.mark.parametrize('package', ['spec/%autopatch-not-in-prep']) +def test_check_autopatch_not_in_prep(package, speccheck): + """Test if specfile does not have %autopatch inside %prep.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: %autopatch-not-in-prep' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_autopatch_in_prep(package, speccheck): + """Test if specfile has %autopatch inside %prep.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: %autopatch-not-in-prep' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_autopatch_not_in_prep_not_applied(package, speccheck): + """Test if specfile has no %autopatch.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: %autopatch-not-in-prep' not in out + + +@pytest.mark.parametrize('package', ['spec/%autosetup-not-in-prep']) +def test_check_autosetup_not_in_prep(package, speccheck): + """Test if specfile does not have %autosetup inside %prep.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: %autosetup-not-in-prep' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_autosetup_inside_prep(package, speccheck): + """Test if specfile has %autosetup in %prep.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: %autosetup-not-in-prep' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_autosetup_not_in_prep_not_applied(package, speccheck): + """Test if specfile has no %autosetup.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: %autosetup-not-in-prep' not in out + + +@pytest.mark.parametrize('package', ['spec/use-of-RPM-SOURCE-DIR']) +def test_check_use_of_rpm_source_dir(package, speccheck): + """Test if specfile consist of $RPM_SOURCE_DIR or %{_sourcedir}.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: use-of-RPM_SOURCE_DIR' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_use_of_rsd_not_applied(package, speccheck): + """rsd: RPM_SOURCE_DIR + Test if specfile does not consist of $RPM_SOURCE_DIR or %{_sourcedir}. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: use-of-RPM_SOURCE_DIR' not in out + + +@pytest.mark.parametrize('package', ['spec/configure-without-libdir-spec']) +def test_check_configure_without_libdir_spec(package, speccheck): + """Test if specfile does not have options augmented with --libdir.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: configure-without-libdir-spec' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_cwls_not_applied(package, speccheck): + """cwls: configure-without-libdir-spec + Test if specfile has options augmented with --libdir. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: configure-without-libdir-spec' not in out + + +@pytest.mark.parametrize('package', ['spec/hardcoded-library-path']) +def test_check_hardcoded_library_path(package, speccheck): + """Test if specfile has hardcoded library path.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: hardcoded-library-path' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_hclp_not_applied(package, speccheck): + """hclp: hardcoded library path + Test if specfile does not have hardcoded library path. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: hardcoded-library-path' not in out + + +@pytest.mark.parametrize('package', ['spec/obsolete-tag']) +def test_check_obsolete_tag(package, speccheck): + """Test if specfile has obsolete-tag as Copyright or Serial.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: obsolete-tag 2' in out + assert 'W: obsolete-tag Something' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_obsolete_tag_not_applied(package, speccheck): + """Test if specfile does not have obsolete-tag as Copyright or Serial.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: obsolete-tag' not in out + + +@pytest.mark.parametrize('package', ['spec/hardcoded-path-in-buildroot-tag']) +def test_check_hardcoded_path_in_buildroot_tag(package, speccheck): + """Test if specfile has hardoded path in buildroot tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: hardcoded-path-in-buildroot-tag /usr/bin/bash' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_hpbt_not_applied(package, speccheck): + """hpbt: hardcoded-path-in-buildroot-tag + Test if specfile does not have hardoded path in buildroot tag. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: hardcoded-path-in-buildroot-tag %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)' not in out + + +@pytest.mark.parametrize('package', ['spec/buildarch-instead-of-exclusivearch-tag']) +def test_check_buildarch_instead_of_exclusivearch_tag(package, speccheck): + """Test if specfile has BuildArch has any architecture beside noarch.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: buildarch-instead-of-exclusivearch-tag x86_64' in out + assert 'E: buildarch-instead-of-exclusivearch-tag i586' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_biet_not_applied(package, speccheck): + """biet: buildarch-instead-of-exclusivearch-tag + Test if specfile has BuildArch with noarch. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: buildarch-instead-of-exclusivearch-tag noarch' not in out + + +@pytest.mark.parametrize('package', ['spec/hardcoded-packager-tag']) +def test_check_hardcoded_packager_tag(package, speccheck): + """Test if specfile has hardcoded packager tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: hardcoded-packager-tag Someone' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_hardcoded_packager_tag_not_applied(package, speccheck): + """Test if specfile does not have hardcoded packager tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: hardcoded-packager-tag' not in out + + +@pytest.mark.parametrize('package', ['spec/hardcoded-prefix-tag']) +def test_check_hardcoded_prefix_tag(package, speccheck): + """Test if specfile has hardcoded prefix tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: hardcoded-prefix-tag' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_hardcoded_prefix_tag_not_applied(package, speccheck): + """Test if specfile does not have hardcoded prefix tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: hardcoded-prefix-tag' not in out + + +@pytest.mark.parametrize('package', ['spec/prereq_use']) +def test_check_prereq_use(package, speccheck): + """Test if specfile has tags such as PreReq(pre) + or PreReq(post). + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: prereq-use none' in out + assert 'E: prereq-use none_other' in out + + +@pytest.mark.parametrize('package', ['spec/patch-not-applied']) +def test_check_prereq_use_not_found(package, speccheck): + """Test if specfile has no PreReq tag value.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: prereq-use' not in out + + +@pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) +def test_check_prereq_use_not_applied(package, speccheck): + """Test if specfile has no PreReq tag value.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: prereq-use' not in out + + +@pytest.mark.parametrize('package', ['spec/buildprereq-use']) +def test_check_buildprereq_use(package, speccheck): + """Test if specfile has buildprereq tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: buildprereq-use Something' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_buildprereq_use_not_applied(package, speccheck): + """Test if specfile does not have buildprereq tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: buildprereq-use' not in out + + +@pytest.mark.parametrize('package', ['spec/forbidden-controlchar-found']) +def test_check_forbidden_controlchar_found(package, speccheck): + """Test if specfile has forbidden controlchar + in various parts of specfile. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: forbidden-controlchar-found' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_forbidden_controlchar_not_found(package, speccheck): + """Test if specfile does not have forbidden controlchar + in various parts of specfile. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: forbidden-controlchar-found' not in out + + +@pytest.mark.parametrize('package', ['spec/comparison-operator-in-deptoken']) +def test_check_coid(package, speccheck): + """coid: comparison-operator-in-deptoken + Test if specfile has comparison operator(>, =) in deptoken. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: comparison-operator-in-deptoken something>2.0' in out + assert 'W: comparison-operator-in-deptoken Something>1.0' in out + assert 'W: comparison-operator-in-deptoken Something=2.0' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_coid_found(package, speccheck): + """coid: comparison-operator-in-deptoken + Test if specfile has comparison operator(<, <=) in deptoken. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: comparison-operator-in-deptoken Someotherthing<1.0' in out + assert 'W: comparison-operator-in-deptoken Someotherthing<=2.0' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_coid_is_found(package, speccheck): + """coid: comparison-operator-in-deptoken + Test if specfile has comparison operator(==, >=) in deptoken. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: comparison-operator-in-deptoken Oneanotherthing>=1.0' in out + assert 'W: comparison-operator-in-deptoken Onelastthing==2.0' in out + assert 'W: comparison-operator-in-deptoken source-for-second-rpm' not in out + + +@pytest.mark.parametrize('package', ['spec/%autopatch-not-in-prep']) +def test_check_coid_is_found_with_single_space(package, speccheck): + """coid: comparison-operator-in-deptoken + Test if specfile has comparison operator(>=, <=) with single space in deptoken. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: comparison-operator-in-deptoken Someotherthinwithsinglespace<=' in out + + +@pytest.mark.parametrize('package', ['spec/%autosetup-not-in-prep']) +def test_check_coid_is_found_with_double_space(package, speccheck): + """coid: comparison-operator-in-deptoken + Test if specfile has comparison operator(>=, <=) with single space in deptoken. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: comparison-operator-in-deptoken /Something' not in out + + +@pytest.mark.parametrize('package', ['spec/unversioned-explicit-version']) +def test_check_unversioned_explicit_version(package, speccheck): + """Test if specfile has Provides: tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: unversioned-explicit-provides someones-something=%{version}' in out + + +@pytest.mark.parametrize('package', ['spec/%autosetup-not-in-prep']) +def test_check_unversioned_explicit_version_not_found(package, speccheck): + """Test if specfile has Provides: /something tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: unversioned-explicit-provides /something' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_uev_not_applied(package, speccheck): + """uev: unversioned-explicit-version + 1. Test if specfile does not have Provides: tag. + 2. Test if specfile does not have Obsoletes: tag. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: unversioned-explicit-provides' not in out + + +@pytest.mark.parametrize('package', ['spec/unversioned-explicit-obsoletes']) +def test_check_unversioned_explicit_obsoletes(package, speccheck): + """Test if specfile has Obsoletes: tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: unversioned-explicit-obsoletes Something' in out + + +@pytest.mark.parametrize('package', ['spec/%autopatch-not-in-prep']) +def test_check_unversioned_explicit_obsoletes_not_found(package, speccheck): + """Test if specfile has correct Obsoletes: tag + and Provides: tag in specfile. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: unversioned-explicit-obsoletes %{name} <= %{version}' not in out + assert 'W: unversioned-explicit-obsoletes %{name} = %{version}' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_ueo_not_found(package, speccheck): + """ueo: unversioned-explicit-obsoletes + Test if specfile does has Obsoletes: /something. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: unversioned-explicit-obsoletes /something' not in out + + +@pytest.mark.parametrize('package', ['spec/macro-in-changelog']) +def test_check_macro_in_changelog(package, speccheck): + """Test if specfile has macro in %changelog.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: macro-in-%changelog' in out + + +@pytest.mark.parametrize('package', ['spec/macro-in-changelog-autochangelog']) +def test_check_autochangelog(package, speccheck): + """Test usage of %autochangelog macro.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: macro-in-%changelog' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_macro_in_changelog_not_found(package, speccheck): + """Test if specfile has macro in %changelog + consisting of %%foo or %+foo or %.foo. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: macro-in-%changelog' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_macro_in_changelog_not_applied(package, speccheck): + """Test if specfile does not have macro in %changelog.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: macro-in-%changelog' not in out + + +@pytest.mark.parametrize('package', ['spec/libdir-macro-in-noarch-package']) +def test_check_libdir_macro_in_noarch_package(package, speccheck): + """Test if specfile has _libdir macro in noarch package.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: libdir-macro-in-noarch-package' in out + + +@pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) +def test_check_lmnp_not_applied(package, speccheck): + """lmnp: libdir-macro-in-noarch-package + Test if specfile does not have _libdir macro in noarch package. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: libdir-macro-in-noarch-package' not in out + + +@pytest.mark.parametrize('package', ['spec/deprecated-grep']) +def test_check_deprecated_grep(package, speccheck): + """Test if specfile has direct use of grep or egrep or fgrep.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: deprecated-grep' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_deprecated_grep_not_found(package, speccheck): + """Test if specfile has grep with -F or -E inside + package, changelog, depscription, files macro + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: deprecated-grep' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_deprecated_grep_not_applied(package, speccheck): + """Test if specfile has no use of egrep/fgrep or egrep/fgrep with -E or -F.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: deprecated-grep' not in out + + +# TODO: Add test for non-standard-Group + + +@pytest.mark.parametrize('package', ['spec/macro-in-comment']) +def test_check_macro_in_comment(package, speccheck): + """Test if specfile has macro in comment.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: macro-in-comment' in out + + +@pytest.mark.parametrize('package', ['spec/%autosetup-not-in-prep']) +def test_check_macro_in_comment_not_found(package, speccheck): + """Test if specfile has comment in macro.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: macro-in-comment' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_macro_in_comment_not_applied(package, speccheck): + """Test if specfile does not have macro inside a comment.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: macro-in-comment' not in out + + +@pytest.mark.parametrize('package', ['spec/no-buildroot-tag']) +def test_check_no_build_root_tag(package, speccheck): + """Test if specfile does not have BuildRoot tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: no-buildroot-tag' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_no_build_root_tag_not_applied(package, speccheck): + """Test if specfile has BuildRoot tag.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: no-buildroot-tag' not in out + + +@pytest.mark.parametrize('package', ['spec/no-%%%s-section']) +def test_check_no_essential_section(package, speccheck): + """Test for no-%%%s-section check + Test if specfile does not have essential section tag. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: no-%prep-section' in out + assert 'W: no-%install-section' in out + assert 'W: no-%build-section' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_no_essential_section_not_applied(package, speccheck): + """Test for no-%%%s-section check + Test if specfile has all essential section tag. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: no-%prep-section' not in out + assert 'W: no-%install-section' not in out + assert 'E: superfluous-%clean-section' in out + assert 'W: no-%build-section' not in out + + +@pytest.mark.parametrize('package', ['spec/more-than-one-%changelog']) +def test_check_more_than_one_changelog_section(package, speccheck): + """Test if specfile has more than one changelog section.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: more-than-one-%changelog-section' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_mtocs_not_applied(package, speccheck): + """mtocs: more-than-one-%changelog-section + Test if specfile does not have more than one changelog section. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: more-than-one-%changelog-section' not in out + + +@pytest.mark.parametrize('package', ['spec/lib-package-without-%mklibname']) +def test_check_lib_package_without_mklibname(package, speccheck): + """Test if specfile has lib pacakge without %mklibname.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: lib-package-without-%mklibname' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_lpwm_not_applied(package, speccheck): + """lpwm: lib-package-without-%mklibname + Test if specfile does not have lib pacakge without %mklibname.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'E: lib-package-without-%mklibname' not in out + + +@pytest.mark.parametrize('package', ['spec/depscript-without-disabling-depgen']) +def test_check_depscript_without_disabling_depgen(package, speccheck): + """Test if specfile has define __find_provides/requires.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: depscript-without-disabling-depgen' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_depscript_without_disabling_depgen_not_found(package, speccheck): + """Test if specfile has define __find_provides/requires with + %define _use_internal_dependency_generator set to 0. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: depscript-without-disabling-depgen' not in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_dwdd_not_applied(package, speccheck): + """dwdd: depscript-without-disabling-depgen + Test if specfile does not have define __find_provides/requires.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: depscript-without-disabling-depgen' not in out + + +@pytest.mark.parametrize('package', ['spec/patch-fuzz-is-changed']) +def test_check_patch_fuzz_is_changed(package, speccheck): + """Test if specfile has internal/default patch fuzz value changed as + %define _default_patch_fuzz >= 0. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: patch-fuzz-is-changed' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheckTemp']) +def test_check_patch_fuzz_is_changed_not_found(package, speccheck): + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: patch-fuzz-is-changed' not in out + + +@pytest.mark.parametrize('package', ['spec/macro-in-comment']) +def test_check_patch_fuzz_is_changed_not_applied(package, speccheck): + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: patch-fuzz-is-changed' not in out + + +@pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) +def test_check_mixed_use_of_spaces_and_tabs(package, speccheck): + """Test if specfile has mix use of space and tabs.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: mixed-use-of-spaces-and-tabs' in out + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_mixed_use_of_spaces_and_tabs_not_applied(package, speccheck): + """Test if specfile does not have mix use of space and tabs.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: mixed-use-of-spaces-and-tabs' not in out + + +@pytest.mark.parametrize('package', ['spec/%ifarch-applied-patch']) +def test_check_ifarch_applied_patch(package, speccheck): + """Test if specfile has no patch in %ifarch block.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: %ifarch-applied-patch' in out + + +@pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) +def test_check_ifarch_applied_not_enforced(package, speccheck): + """Test if specfile has patch in %ifarch block.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: %ifarch-applied-patch' not in out + + +@pytest.mark.parametrize('package', ['spec/patch-not-applied']) +def test_check_patch_not_applied(package, speccheck): + """Test if specfile does not have all patch applied.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: patch-not-applied' in out + + +@pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) +def test_check_patch_not_found(package, speccheck): + """Test if specfile have all patch applied by %autopatch.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: patch-not-applied' not in out + + +@pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) +def test_check_patch_not_applied_not_enforced(package, speccheck): + """Test if specfile has all patch applied.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: patch-not-applied' not in out + + +# TODO: Add specfile-error test. + + +@pytest.mark.parametrize('package', ['spec/SpecCheck2']) +def test_check_invalid_url(package, speccheck): + """Test if specfile has invalid url.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: invalid-url' in out + + +@pytest.mark.parametrize('package', ['spec/mixed-use-of-spaces-and-tabs']) +def test_check_invalid_url_not_applied(package, speccheck): + """Test if specfile does not have invalid url.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: invalid-url' not in out + + +@pytest.mark.parametrize('package', ['spec/python-setup-test']) +def test_python_setup_test(package, speccheck): + """Test if specfile has deprecated use of 'setup.py test'.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: python-setup-test' in out + + +@pytest.mark.parametrize('package', ['spec/python-module-def']) +def test_python_module_definition(package, speccheck): + """Test if python_module macro is defined in the spec file.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: python-module-def' in out + + +@pytest.mark.parametrize('package', [ + 'spec/SpecCheck', + 'spec/SpecCheck2', + 'spec/SpecCheck3', + 'spec/SpecCheck4', +]) +def test_python_module_definition_not_present(package, speccheck): + """Test if python_module macro warning is not shown if the macro is not + defined in the spec file. + """ + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: python-module-def' not in out + + +@pytest.mark.parametrize('package', [ + 'spec/python-sitelib-glob', + 'spec/python-sitearch-glob', +]) +def test_python_sitelib_glob(package, speccheck): + """Test if %{python_sitelib}/* is present in %files section.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: python-sitelib-glob-in-files' in out + + +@pytest.mark.parametrize('package', ['spec/python-sitelib']) +def test_python_sitelib(package, speccheck): + """Test if %{python_sitelib}/* is present in %files section.""" + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'W: python-sitelib-glob-in-files' not in out + + +@pytest.mark.parametrize('package', [ + 'spec/null-char-last', + 'spec/null-char-first', +]) +def test_null_char(package, speccheck): + output, test = speccheck + pkg = get_tested_spec_package(package) + test.check_spec(pkg) + out = output.print_results(output.results) + assert 'forbidden-controlchar-found' in out + + +@pytest.mark.parametrize('package', [ + get_tested_spec_package('spec/MacroInComment'), +]) +def test_special_comments(package, output, test): + test.check_spec(package) + out = output.print_results(output.results) + assert 'W: macro-in-comment %configure' in out + assert 'W: macro-in-comment %{name}' not in out diff --git a/test/test_spellchecking.py b/test/test_spellchecking.py new file mode 100644 index 0000000..f557db4 --- /dev/null +++ b/test/test_spellchecking.py @@ -0,0 +1,87 @@ +import pytest +import rpmlint.spellcheck + +from Testing import HAS_CZECH_DICTIONARY, HAS_ENGLISH_DICTIONARY + + +def get_suggestions(suggestion): + suggestion = suggestion.split(' -> ')[-1] + return sorted(suggestion.split(', ')) + + +@pytest.mark.skipif(not rpmlint.spellcheck.ENCHANT, reason='Missing enchant bindings') +@pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary') +def test_spelldict(capsys): + """ + Check we can init dictionary spellchecker + """ + spell = rpmlint.spellcheck.Spellcheck() + spell._init_checker() + out, err = capsys.readouterr() + assert not out + assert not err + assert 'unable to load spellchecking dictionary' not in err + + spell._init_checker('not-existing-language') + out, err = capsys.readouterr() + assert not out + assert 'unable to load spellchecking dictionary' in err + + assert 'en_US' in spell._enchant_checkers + assert spell._enchant_checkers['en_US'] is not None + assert 'not-existing-language' not in spell._enchant_checkers + + +@pytest.mark.skipif(not rpmlint.spellcheck.ENCHANT, reason='Missing enchant bindings') +@pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary') +@pytest.mark.skipif(not HAS_CZECH_DICTIONARY, reason='Missing Czech dictionary') +def test_spellchecking(): + """ + Check if we can test the spelling + """ + spell = rpmlint.spellcheck.Spellcheck() + + # correct text + text = 'I swear this text is proper English' + result = spell.spell_check(text, 'Description({}):') + assert not result + + # english 2 typos + text = "I don't think tihs tetx is correct English" + result = spell.spell_check(text, 'Description({}):') + assert len(result) == 2 + assert result['tihs'].startswith('Description(en_US): tihs -> ') + assert get_suggestions(result['tihs']) == ['hits', 'this', 'ties'] + + # different language, one typo + text = 'Příčerně žluťoučký kůň' + result = spell.spell_check(text, 'Summary({}):', 'cs_CZ') + assert len(result) == 1 + assert result['Příčerně'].startswith('Summary(cs_CZ): Příčerně -> ') + assert get_suggestions(result['Příčerně']) == ['Příčeně', 'Příčetně', 'Příšerně'] + + # non-existing language, should return nothing: + text = 'Weird english text' + result = spell.spell_check(text, 'Summary({}):', 'de_CZ') + assert not result + + +@pytest.mark.skipif(not rpmlint.spellcheck.ENCHANT, reason='Missing enchant bindings') +@pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary') +def test_pkgname_spellchecking(): + spell = rpmlint.spellcheck.Spellcheck() + + pkgname = 'python-squeqe' + text = "This package is squeqe's framework helper" + result = spell.spell_check(text, 'Description({}):', 'en_US', pkgname) + assert not result + + +@pytest.mark.skipif(not rpmlint.spellcheck.ENCHANT, reason='Missing enchant bindings') +def test_ignorelist_spellchecking(): + spell = rpmlint.spellcheck.Spellcheck() + + ignore = ['wrod', 'žížala'] + text = 'This package should not have any typos in wrod or žíŽala' + result = spell.spell_check(text, 'Description({}):', ignored_words=ignore) + assert not result diff --git a/test/test_sysvinitonsystemd.py b/test/test_sysvinitonsystemd.py new file mode 100644 index 0000000..65be0ae --- /dev/null +++ b/test/test_sysvinitonsystemd.py @@ -0,0 +1,31 @@ +import pytest +from rpmlint.checks.SysVInitOnSystemdCheck import SysVInitOnSystemdCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def sysvcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = SysVInitOnSystemdCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/init']) +def test_sysv_init_on_systemd_check(tmp_path, package, sysvcheck): + output, test = sysvcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: obsolete-insserv-requirement' in out + assert 'E: deprecated-init-script weekly.script' in out + assert 'E: deprecated-boot-script boot.script' in out + + +@pytest.mark.parametrize('package', ['binary/rc-links']) +def test_overshadowing_of_initscript(tmp_path, package, sysvcheck): + output, test = sysvcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: systemd-shadowed-initscript bar' in out diff --git a/test/test_tags.py b/test/test_tags.py index 4df4eef..663aab1 100644 --- a/test/test_tags.py +++ b/test/test_tags.py @@ -1,21 +1,441 @@ -import os +import pytest +from rpmlint.checks.TagsCheck import TagsCheck +from rpmlint.filter import Filter +import rpmlint.spellcheck -import TagsCheck -import Testing +from Testing import CONFIG, get_tested_package +from Testing import HAS_ENGLISH_DICTIONARY, HAS_FRENCH_DICTIONARY -class TestTags(Testing.OutputTest): +@pytest.fixture(scope='function', autouse=True) +def tagscheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = TagsCheck(CONFIG, output) + return output, test - @classmethod - def setup_class(cls): - cls.check = TagsCheck.check.check - def test_unexpanded_macros(self): - for package in ['unexpanded1']: - out = self._rpm_test_output(os.path.join('binary', package)) - assert 'unexpanded-macro Recommends' in "\n".join(out) - assert 'unexpanded-macro Provides' in "\n".join(out) - assert 'unexpanded-macro Conflicts' in "\n".join(out) - assert 'unexpanded-macro Suggests' in "\n".join(out) - assert 'unexpanded-macro Obsoletes' in "\n".join(out) - assert 'unexpanded-macro Enhances' in "\n".join(out) +@pytest.mark.parametrize('package', ['binary/unexpanded1']) +def test_unexpanded_macros(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'unexpanded-macro Recommends' in out + assert 'unexpanded-macro Provides' in out + assert 'unexpanded-macro Conflicts' in out + assert 'unexpanded-macro Suggests' in out + assert 'unexpanded-macro Obsoletes' in out + assert 'unexpanded-macro Enhances' in out + + +@pytest.mark.parametrize('package', ['binary/self']) +def test_self_provides(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: useless-provides self' in out + + +@pytest.mark.parametrize('package', ['binary/fuse-common']) +def test_useless_provides_only_versions(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: useless-provides self' not in out + + +@pytest.mark.parametrize('package', ['binary/foo-devel']) +def test_development_package(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: devel-package-with-non-devel-group Games' in out + + +@pytest.mark.parametrize('package', ['binary/missingprovides']) +def test_missing_provides(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: no-pkg-config-provides' in out + + +@pytest.mark.parametrize('package', ['binary/invalid-exception']) +def test_invalid_license_exception(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: invalid-license-exception sparta' in out + + +@pytest.mark.parametrize('package', ['binary/valid-exception']) +def test_valid_license_exception(tmp_path, package, tagscheck): + CONFIG.info = True + CONFIG.configuration['ValidLicenseExceptions'] = ['389-exception'] + output = Filter(CONFIG) + test = TagsCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: invalid-license-exception' not in out + + +@pytest.mark.parametrize('package', ['source/valid-exception-in-grouping']) +def test_valid_license_exception_in_grouping(tmp_path, package, tagscheck): + CONFIG.info = True + CONFIG.configuration['ValidLicenses'] = ['BSD-3-Clause', 'GPL-2.0-only'] + CONFIG.configuration['ValidLicenseExceptions'] = ['Qt-GPL-exception-1.0'] + output = Filter(CONFIG) + test = TagsCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: invalid-license-exception' not in out + + +@pytest.mark.parametrize('package', ['source/valid-exception-begin-grouping']) +def test_valid_license_exception_begin_grouping(tmp_path, package, tagscheck): + CONFIG.info = True + CONFIG.configuration['ValidLicenses'] = ['BSD-3-Clause', 'GPL-2.0-only'] + CONFIG.configuration['ValidLicenseExceptions'] = ['Qt-GPL-exception-1.0'] + output = Filter(CONFIG) + test = TagsCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: invalid-license-exception' not in out + + +@pytest.mark.parametrize('package', ['binary/xtables-addons-kmp-default']) +def test_forbidden_controlchar_found_requires(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: forbidden-controlchar-found Requires:' in out + + +@pytest.mark.parametrize('package', ['binary/ruby2.6-rubygem-fast_gettext']) +def test_forbidden_controlchar_found_changelog(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: forbidden-controlchar-found %changelog' in out + + +@pytest.mark.parametrize('package', ['binary/SpecCheck4']) +def test_forbidden_controlchar_found(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: forbidden-controlchar-found Requires:' in out + assert 'E: forbidden-controlchar-found Provides:' in out + assert 'E: forbidden-controlchar-found Obsoletes:' in out + assert 'E: forbidden-controlchar-found Conflicts:' in out + assert 'E: forbidden-controlchar-found %changelog :' in out + + +@pytest.mark.parametrize('package', ['binary/unexpanded-macro-exp']) +def test_check_unexpanded_macro(tmp_path, package, tagscheck): + """Test if a package has an unexpanded macro in it's specfile.""" + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: unexpanded-macro Packager %ppc' in out + assert 'W: unexpanded-macro Group %ppc' in out + assert 'W: unexpanded-macro Provides /something%ppc %ppc' in out + assert 'W: unexpanded-macro Conflicts something:%ppc %ppc' in out + assert 'W: unexpanded-macro Supplements packageand(python-gobject:%{gdk_real_package})%ppc %ppc' in out + assert 'W: unexpanded-macro Suggests /%ppc %ppc' in out + assert 'W: unexpanded-macro Enhances /%ppc %ppc' in out + assert 'W: unexpanded-macro Recommends /%ppc %ppc' in out + assert 'W: unexpanded-macro Supplements packageand(python-gobject:%{gdk_real_package})%ppc %{gdk_real_package}' in out + assert 'W: unexpanded-macro Supplements packageand(python-gobject:%{gdk_real_package})%ppc %ppc' in out + + +@pytest.mark.parametrize('package', ['binary/invalid-version']) +def test_check_errors(tmp_path, package, tagscheck): + """Test package for check invalid-version.""" + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package has a Version: tag with pre/alpha/beta suffixes in it's specfile + assert 'E: invalid-version 0pre' in out + + +# TODO: Add a test for no-changelogname-tag not in out + + +@pytest.mark.parametrize('package', ['binary/summary-warning']) +def test_check_summary_warning(tmp_path, package, tagscheck): + """Test package for check + - in out, + summary-too-long, summary-has-leading-spaces, + description-shorter-than-summary, + - not in out + invalid-version, unexpanded-macro. + """ + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if package has a summary longer than 80 characters + assert 'E: summary-too-long' in out + # Test if package has leading space at the beginning of the summary + # where non-breaking space is used (U+00A0). + assert b'E: summary-has-leading-spaces \xc2\xa0\xc2\xa0lorem'.decode() in out + # Test if package has a shorter description than Summary + assert 'W: description-shorter-than-summary' in out + # Test if a package has a Version: tag + # without any pre/alpha/beta suffixes in it's specfile. + assert 'E: invalid-version' not in out + # Test if a package does not have an unexpanded + # macro in it's specfile. + assert 'W: unexpanded-macro' not in out + assert 'E: no-group-tag' in out + + +@pytest.mark.parametrize('package', ['binary/no-url-tag']) +def test_check_warning(tmp_path, package, tagscheck): + """Test if a package contains the warning for + summary-not-capitalized, summary-ended-with-dot, + no-url-tag.""" + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if package Summary does not start with a capital letter + assert 'W: summary-not-capitalized no-url-tag warning.' in out + # Test if a package Summary ends with a period + assert 'W: summary-ended-with-dot no-url-tag warning.' in out + # Test if a package does not contain a Url: tag + assert 'W: no-url-tag' in out + + +@pytest.mark.parametrize('package', ['binary/invalid-la-file']) +def test_check_errors_not_found(tmp_path, package, tagscheck): + """Test packages for checks + summary-too-long, summary-not-capitalized, + summary-ended-with-dot, summary-has-leading-spaces, + no-url-tag, description-shorter-than-summary. + """ + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package Summary is not longer than 80 characters + assert 'W: summary-too-long' not in out + # Test if Summary starts with a capitalized word + assert 'W: summary-not-capitalized' not in out + # Test if a package Summary does not end with a period + assert 'W: summary-ended-with-dot' not in out + # Test if a package Summary does not begin with a leading space + assert 'W: summary-has-leading-spaces' not in out + # Test if a package contains a Url: tag + assert 'W: no-url-tag' not in out + # Test if package no shorter description than Summary + assert 'W: description-shorter-than-summary' not in out + + +@pytest.mark.parametrize('package', ['binary/misc-warnings']) +def test_check_misc_warning(tmp_path, package, tagscheck): + """Test package for check tag-in-description, + name-repeated-in-summary, invalid-url.""" + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package has a tag such as Name: in the description + assert 'W: tag-in-description Name:' in out + # Test if a package has it's name repeated in the summary tag + assert 'W: name-repeated-in-summary misc-warnings' in out + # Test if a package has a invalid url value in the URL: tag + assert 'W: invalid-url URL so;mething.' in out + + +@pytest.mark.parametrize('package', ['binary/misc-no-warnings']) +def test_check_misc_warning_not_found(tmp_path, package, tagscheck): + """Test package for check not in out + tag-in-description, name-repeated-in-summary, invalid-url.""" + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package does not have a tag in description + assert 'W: tag-in-description' not in out + # Test if a package does not have a name repeated in the summary tag value + assert 'W: name-repeated-in-summary' not in out + # Test if a package does not have a invalid url in the URL tag value + assert 'W: invalid-url' not in out + + +@pytest.mark.parametrize('package', ['binary/invalid-dependency']) +def test_check_invalid_dependency(tmp_path, package, tagscheck): + """Test if a package has + invalid-dependency, no-description-tag, unreasonable-epoch.""" + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package has a Epoch tag value greater than 99 + assert 'W: unreasonable-epoch 100' in out + # Test if a package has Requires: starts with /usr/local + assert 'E: invalid-dependency /usr/local/something' in out + # Test if a package has empty or no description tag + assert 'E: no-description-tag' in out + + +@pytest.mark.parametrize('package', ['binary/random-exp']) +def test_package_random_warnings(tmp_path, package, tagscheck): + """Test if a package has check, + - in out, + obsolete-not-provided + description-line-too-long, + - not in out, + invalid-dependency, + unreasonable-epoch, + no-description-tag, + self-obsoletion.""" + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package that was obsoleted is still provided + # in newer package to avoid unnecessary dependency breakage + assert 'W: obsolete-not-provided' in out + # Test if a package has a description longer than 79 characters + assert 'E: description-line-too-long This is ridiculously long description that has no meaning but is used to test the check description-line-too-long.' in out + # Test if a package has a Requires tag value start with + # string other than /usr/local/ + assert 'E: invalid-dependency' not in out + # Test if a package has a Epoch tag value less than or equal to 99 + assert 'W: unreasonable-epoch' not in out + # Test if a package has a non-empty description tag + assert 'W: no-description-tag' not in out + # Test if a package does not have itself in Obsoletes: tag value + assert 'W: self-obsoletion' not in out + + +@pytest.mark.parametrize('package', ['binary/random-devel']) +def test_package_random_exp(tmp_path, package, tagscheck): + """Test if a package check, + - in out, + self-obsoletion, + - not in out, + obsolete-not-provided, + description-line-too-long, + devel-dependency.""" + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package obsoletes itself i.e. Obsoletes: random-devel + assert 'W: self-obsoletion random-devel obsoletes random-devel' in out + # Test if a obsoleted package has been provided to + # to avoid unnecessary breakage i.e. Requires: python + assert 'W: obsolete-not-provided' not in out + # Test if a package has a description line not greater 79 characters + assert 'E: description-line-too-long' not in out + # Test if a package is a *-devel package and requires a devel dependency + assert 'W: devel-dependency' not in out + + +@pytest.mark.parametrize('package', ['binary/requires-on-release']) +def test_check_requires_on_release(tmp_path, package, tagscheck): + """Test if a package check, + - in out, + requires-on-release.""" + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package requires specific release of another package + assert 'W: requires-on-release baz = 2.1-1' in out + + +@pytest.mark.parametrize('package', ['binary/invalid-license']) +def test_check_invalid_license(tmp_path, package, tagscheck): + """Test if a package check, + - in out, + invalid-license, + - not in out, + requires-on-release.""" + CONFIG.configuration['ValidLicenses'] = ['MIT'] + output = Filter(CONFIG) + test = TagsCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package has a License: tag value different from + # ValidLicense = [] list in configuration + assert 'W: invalid-license Apache License' in out + # Test if a package does not Requires: a specific version of a package + assert 'W: requires-on-release' not in out + + +@pytest.mark.parametrize('package', ['binary/not-standard-release-extension']) +def test_package_not_std_release_extension(tmp_path, package, tagscheck): + """Test if package has check, + - in out, + not-standard-release-extension + - not in out, + invalid-license.""" + CONFIG.configuration['ReleaseExtension'] = 'hello$' + CONFIG.configuration['ValidLicenses'] = ['Apache-2.0 License'] + output = Filter(CONFIG) + test = TagsCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package has a ReleaseExtension regex does not match with the Release: tag value expression + # i.e. Release tag value must not match regex expression 'hello$' + assert 'W: not-standard-release-extension 1.1' in out + # Test if a package does have the same License value as defined in the ValidLicense in configdefaults + assert 'W: invalid-license Apache-2.0 License' not in out + + +@pytest.mark.parametrize('package', ['binary/non-standard-group']) +def test_check_non_standard_group(tmp_path, package, tagscheck): + """Test if a package has check, + - in out, + non-standard-group + - not in out, + not-standard-release-extension.""" + CONFIG.configuration['ValidGroups'] = ['Devel/Something'] + CONFIG.configuration['ReleaseExtension'] = '0' + output = Filter(CONFIG) + test = TagsCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package has a different Group: tag value than ValidGroups = [] + assert 'W: non-standard-group non/standard/group' in out + # Test if a package matches the Release tag regex + assert 'not-standard-release-extension 0' not in out + + +@pytest.mark.parametrize('package', ['binary/dev-dependency']) +def test_package_dev_dependency(tmp_path, package, tagscheck): + """Test if a package check, + - in out, + devel-dependency, + - not in out, + non-standard-group.""" + CONFIG.configuration['ValidGroups'] = ['Devel/Something'] + output = Filter(CONFIG) + test = TagsCheck(CONFIG, output) + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # Test if a package is not a devel package itself but requires a devel dependency + assert 'E: devel-dependency glibc-devel' in out + # Test if a package does not have a Group tag + assert 'W: non-standard-group Devel/Something' not in out + + +@pytest.mark.parametrize('package', ['binary/summary-on-multiple-lines']) +def test_summary_on_multiple_lines(tmp_path, package, tagscheck): + # Test if a package has summary on multiple lines. + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: summary-on-multiple-lines' in out + + +@pytest.mark.skipif(not rpmlint.spellcheck.ENCHANT, reason='Missing enchant bindings') +@pytest.mark.skipif(not HAS_ENGLISH_DICTIONARY, reason='Missing English dictionary') +@pytest.mark.skipif(not HAS_FRENCH_DICTIONARY, reason='Missing French dictionary') +@pytest.mark.parametrize('package', [ + 'binary/spellingerrors-default', + 'binary/spellingerrors-lang', + 'binary/spellingerrors-lang2', + 'binary/spellingerrors-lang3', +]) +def test_description_spelling_error(tmp_path, package, tagscheck): + output, test = tagscheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: spelling-error' in out diff --git a/test/test_tmp_files.py b/test/test_tmp_files.py new file mode 100644 index 0000000..b43d913 --- /dev/null +++ b/test/test_tmp_files.py @@ -0,0 +1,49 @@ +import pytest +from rpmlint.checks.TmpFilesCheck import TmpFilesCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def tmpfilescheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = TmpFilesCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/tempfiled']) +def test_tmpfiles(tmp_path, package, tmpfilescheck): + output, test = tmpfilescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + + assert 'W: pre-with-tmpfile-creation ' not in out + assert 'W: post-without-tmpfile-creation /usr/lib/tmpfiles.d/krb5.conf' in out + assert 'W: tmpfile-not-in-filelist /var/lib/kerberos' in out + assert 'W: tmpfile-not-regular-file /usr/lib/tmpfiles.d/symlink.conf' in out + + +@pytest.mark.parametrize('package', ['binary/systemd-tmpfiles']) +def test_tmpfiles2(tmp_path, package, tmpfilescheck): + output, test = tmpfilescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + + assert 'W: pre-with-tmpfile-creation /usr/lib/tmpfiles.d/systemd-tmpfiles.conf' in out + assert 'W: post-without-tmpfile-creation' in out + assert 'W: tmpfile-not-in-filelist /run/my_new_directory' in out + assert 'W: tmpfile-not-regular-file' not in out + + +@pytest.mark.parametrize('package', ['binary/systemd-tmpfiles_correct']) +def test_tmpfiles_correct(tmp_path, package, tmpfilescheck): + output, test = tmpfilescheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + + assert 'W: pre-with-tmpfile-creation' not in out + assert 'W: post-without-tmpfile-creation' not in out + assert 'W: tmpfile-not-regular-file' not in out + assert 'W: tmpfile-not-in-filelist' not in out diff --git a/test/test_xinetd.py b/test/test_xinetd.py new file mode 100644 index 0000000..25bf520 --- /dev/null +++ b/test/test_xinetd.py @@ -0,0 +1,21 @@ +import pytest +from rpmlint.checks.XinetdDepCheck import XinetdDepCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def xinetdcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = XinetdDepCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/needxinetd']) +def test_xinetd(tmp_path, package, xinetdcheck): + output, test = xinetdcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'E: obsolete-xinetd-requirement' in out diff --git a/test/test_zip.py b/test/test_zip.py new file mode 100644 index 0000000..21d8b3c --- /dev/null +++ b/test/test_zip.py @@ -0,0 +1,59 @@ +import pytest +from rpmlint.checks.ZipCheck import ZipCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_package + + +@pytest.fixture(scope='function', autouse=True) +def zipcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = ZipCheck(CONFIG, output) + return output, test + + +@pytest.mark.parametrize('package', ['binary/bad-crc-uncompressed']) +def test_bad_crc_and_compression(tmp_path, package, zipcheck): + output, test = zipcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + + assert 'bad-crc-in-zip' in out + assert 'zip fails the CRC check' in out + + assert 'uncompressed-zip' in out + assert 'zip file is not compressed' in out + + +@pytest.mark.parametrize('package', ['binary/asm']) +def test_classpath_and_index(tmp_path, package, zipcheck): + output, test = zipcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'class-path-in-manifest' in out + assert 'jar contains a hardcoded Class-Path' in out + + assert 'jar-not-indexed' in out + assert 'jar file is not indexed' in out + + +@pytest.mark.parametrize('package', ['binary/ruby2.5-rubygem-rubyzip-testsuite']) +def test_zip1(tmp_path, package, zipcheck): + output, test = zipcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + # these are PW protected not broken so do not error about them + assert 'W: unable-to-read-zip' in out + # there is a zip with no crc errors + assert 'bad-crc-in-zip' not in out + # there is zip with only 0 size files, which is not "uncompressed" + assert 'uncompressed-zip' not in out + + +@pytest.mark.parametrize('package', ['binary/texlive-codepage-doc']) +def test_zip2(tmp_path, package, zipcheck): + output, test = zipcheck + test.check(get_tested_package(package, tmp_path)) + out = output.print_results(output.results) + assert 'W: unable-to-read-zip' in out diff --git a/test/test_zypp_syntax.py b/test/test_zypp_syntax.py new file mode 100644 index 0000000..02334b3 --- /dev/null +++ b/test/test_zypp_syntax.py @@ -0,0 +1,77 @@ +import pytest +from rpmlint.checks.ZyppSyntaxCheck import ZyppSyntaxCheck +from rpmlint.filter import Filter + +from Testing import CONFIG, get_tested_mock_package + + +@pytest.fixture(scope='function', autouse=True) +def zyppsyntaxcheck(): + CONFIG.info = True + output = Filter(CONFIG) + test = ZyppSyntaxCheck(CONFIG, output) + return output, test + + +@pytest.fixture +def output(zyppsyntaxcheck): + output, _test = zyppsyntaxcheck + yield output + + +@pytest.fixture +def test(zyppsyntaxcheck): + _output, test = zyppsyntaxcheck + yield test + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + header={ + 'requires': [ + 'rpmlib(CompressedFileNames) <= 3.0.4-1', + 'rpmlib(FileDigests) <= 4.6.0-1', + 'rpmlib(PayloadFilesHavePrefix) <= 4.0-1', + 'rpmlib(PayloadIsXz) <= 5.2-1', + 'rpmlib(RichDependencies) <= 4.12.0-1', + ], + 'supplements': ['packageand(c:d)'], + 'recommends': ['packageand(a:b)'], + 'suggests': ['(a and b)'], + 'conflicts': [ + 'bee', + 'otherproviders(yast2_theme)', + ], + }, +)]) +def test_packageand(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'suse-zypp-packageand packageand(c:d)' in out + assert 'suse-zypp-packageand packageand(a:b)' in out + assert '(a and b)' not in out + assert 'bee' not in out + assert 'suse-zypp-otherproviders otherproviders(yast2_theme)' in out + + +@pytest.mark.parametrize('package', [get_tested_mock_package( + header={ + 'requires': [ + 'rpmlib(CompressedFileNames) <= 3.0.4-1', + 'rpmlib(FileDigests) <= 4.6.0-1', + 'rpmlib(PayloadFilesHavePrefix) <= 4.0-1', + 'rpmlib(PayloadIsXz) <= 5.2-1', + 'rpmlib(RichDependencies) <= 4.12.0-1', + ], + 'supplements': ['(c and d)'], + 'recommends': ['b'], + 'suggests': ['(a and b)'], + 'conflicts': [ + 'bee', + ], + }, +)]) +def test_packageand_ok(package, test, output): + test.check(package) + out = output.print_results(output.results) + assert 'suse-zypp-packageand' not in out + assert 'suse-zypp-otherproviders' not in out diff --git a/tools/Testing.py b/tools/Testing.py deleted file mode 100644 index 2b90127..0000000 --- a/tools/Testing.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- - -import glob -import os -import tempfile - -import Pkg - - -def _testpath(): - return os.environ.get( - 'TESTPATH', - os.path.join(os.path.dirname(__file__), "..", "test") - ) - - -TEST_CONFIG = os.path.join(_testpath(), "test.config") -with open(TEST_CONFIG) as f: - exec(compile(f.read(), TEST_CONFIG, 'exec')) - -currently_testing = 0 -output = [] - - -def isTest(): - return currently_testing - - -def startTest(): - global currently_testing - global output - output = [] - currently_testing = 1 - - -def addOutput(s): - global output - output.append(s) - - -def getOutput(): - global output - return output - - -def getTestedPath(path): - return os.path.join(_testpath(), path) - - -def getTestedPackage(name): - pkg_path = glob.glob(getTestedPath(name) + "-*.rpm")[0] - return Pkg.Pkg(pkg_path, tempfile.gettempdir()) - - -def getTestedSpecPackage(name): - pkg_path = glob.glob(getTestedPath(name) + ".spec")[0] - return Pkg.FakePkg(pkg_path) - - -class OutputTest(object): - - check = None - check_spec = None - - def _rpm_test_output(self, rpm, check=None): - with getTestedPackage(rpm) as pkg: - startTest() - (check or self.check)(pkg) - return getOutput() - - def _spec_test_output(self, spec, check=None): - with getTestedSpecPackage(spec) as pkg: - startTest() - # call check_spec() directly, as check() doesn't work with - # getTestedSpecPackage() - (check or self.check_spec)(pkg, pkg.name) - return getOutput() diff --git a/tools/generate-fedora-users-groups.py b/tools/generate-fedora-users-groups.py new file mode 100755 index 0000000..cd7c620 --- /dev/null +++ b/tools/generate-fedora-users-groups.py @@ -0,0 +1,39 @@ +#!/usr/bin/python3 +""" This script is used to generate values for + config/Fedora/configs/Fedora/users-groups.toml +""" + +import datetime +import os +import re + +users = set() +groups = set() +uidgid_regex = re.compile(r'^\s*(\S+)\s+(-|\d+)\s+(-|\d+|\(\d+\))\s') +for uidgid_file in ['/usr/share/doc/setup/uidgid']: + if os.path.exists(uidgid_file): + with open(uidgid_file) as fobj: + try: + for line in fobj.read().strip().splitlines(): + if line.startswith('#'): + continue + res = uidgid_regex.search(line) + if res: + name = res.group(1) + if res.group(2) != '-': + users.add(name) + if res.group(3) != '-' and '(' not in res.group(3): + groups.add(name) + del res + del line + finally: + fobj.close() + del fobj + +name = '/'.join(__file__.split('/')[-2:]) +date = datetime.datetime.utcnow().strftime('%Y-%m-%d') +print(f'# generated by {name} on {date}\n') + +print(f'StandardUsers = {sorted(users)}\n') + +print(f'StandardGroups = {sorted(groups)}') diff --git a/tools/generate-isocodes.py b/tools/generate-isocodes.py index cbb1e43..3ccc9ae 100755 --- a/tools/generate-isocodes.py +++ b/tools/generate-isocodes.py @@ -11,34 +11,42 @@ import sys from urllib.request import urlopen -iso_3166_1_url = os.environ.get("ISO_3166_1_URL", "http://anonscm.debian.org/cgit/pkg-isocodes/iso-codes.git/plain/data/iso_3166-1.json") -iso_639_3_url = os.environ.get("ISO_639_3_URL", "http://anonscm.debian.org/cgit/pkg-isocodes/iso-codes.git/plain/data/iso_639-3.json") +iso_3166_1_url = os.environ.get('ISO_3166_1_URL', 'https://salsa.debian.org/iso-codes-team/iso-codes/raw/main/data/iso_3166-1.json') +iso_639_3_url = os.environ.get('ISO_639_3_URL', 'https://salsa.debian.org/iso-codes-team/iso-codes/raw/main/data/iso_639-3.json') +iso_639_2_url = os.environ.get('ISO_639_2_URL', 'https://salsa.debian.org/iso-codes-team/iso-codes/raw/main/data/iso_639-2.json') +iso_15924_url = os.environ.get('ISO_15924_URL', 'https://salsa.debian.org/iso-codes-team/iso-codes/raw/main/data/iso_15924.json') langs = set() countries = set() # country codes (2 letters) with urlopen(iso_3166_1_url) as f: - data = json.load(codecs.getreader("utf-8")(f)) - for entry in data["3166-1"]: - countries.add(entry["alpha_2"]) + data = json.load(codecs.getreader('utf-8')(f)) + for entry in data['3166-1']: + countries.add(entry['alpha_2']) # language codes (2 or 3 letters, 3 only for ones we don't have 2-letter one) with urlopen(iso_639_3_url) as f: - data = json.load(codecs.getreader("utf-8")(f)) - for entry in data["639-3"]: - langs.add(entry.get("alpha_2") or entry["alpha_3"]) - -# Note that we are not pprint()ing the set directly because with -# Python 3 it results in curly brace set initializers that are not -# compatible with Python 2.6, do it with set([...]) instead. - -print("# Generated with %s" % sys.argv[0]) -print("") -print("LANGUAGES = set(") -pprint(sorted(langs)) -print(")") -print("") -print("COUNTRIES = set(") -pprint(sorted(countries)) -print(")") + data = json.load(codecs.getreader('utf-8')(f)) + for entry in data['639-3']: + langs.add(entry.get('alpha_2') or entry['alpha_3']) +# Need to check iso-639-2 for collective language codes not in iso-639-3 +with urlopen(iso_639_2_url) as f: + data = json.load(codecs.getreader('utf-8')(f)) + for entry in data['639-2']: + entry_code = entry.get('alpha_2') or entry['alpha_3'] + if entry_code not in langs: + langs.add(entry_code) +# ISO 15924, Codes for the representation of names of scripts +with urlopen(iso_15924_url) as f: + data = json.load(codecs.getreader('utf-8')(f)) + for entry in data['15924']: + countries.add(entry['alpha_4']) + +print(f'# Generated with {sys.argv[0]}') +print('') +print('LANGUAGES = \\') +pprint(langs) +print('') +print('COUNTRIES = \\') +pprint(countries) diff --git a/tools/generate-suse-licenses.py b/tools/generate-suse-licenses.py new file mode 100755 index 0000000..7feb6a9 --- /dev/null +++ b/tools/generate-suse-licenses.py @@ -0,0 +1,44 @@ +#!/usr/bin/python3 + +import os + +import requests + +SUSE_EXCEPTIONS = """ +AGPL-3.0 AGPL-3.0+ GFDL-1.1 GFDL-1.1+ GFDL-1.2 GFDL-1.2+ GFDL-1.3 GFDL-1.3+ GPL-3.0-with-GCC-exception \ +GPL-2.0-with-classpath-exception GPL-2.0-with-font-exception SUSE-LGPL-2.1+-with-GCC-exception SUSE-NonFree \ +GPL-1.0+ GPL-1.0 GPL-2.0+ GPL-2.0 GPL-3.0+ GPL-3.0 LGPL-2.0 LGPL-2.0+ LGPL-2.1+ LGPL-2.1 LGPL-3.0+ LGPL-3.0 +""" +IGNORED_FOR_PLUS = (' with ', '-with-', ' or ') + +licenses = requests.get('https://raw.githubusercontent.com/openSUSE/obs-service-format_spec_file/master/licenses_changes.txt') +licenses_exceptions = requests.get('https://raw.githubusercontent.com/openSUSE/obs-service-format_spec_file/master/licenses_exceptions.txt') + +with open('../configs/openSUSE/licenses.toml', 'w') as wfile: + script_name = os.path.basename(__file__) + wfile.write('# Generated with %s script:\n' % script_name) + wfile.write('ValidLicenses = [\n') + added = set() + for line in licenses.text.splitlines(): + if line == 'First line': + continue + name = line.strip().split('\t')[0] + if name not in added: + wfile.write(f' "{name}",\n') + added.add(name) + + if not name.endswith('+') and not any(i in name.lower() for i in IGNORED_FOR_PLUS): + plus_name = f'{name}+' + if plus_name not in added: + wfile.write(f' "{plus_name}",\n') + added.add(plus_name) + wfile.write(' # SUSE EXCEPTIONS\n') + for name in SUSE_EXCEPTIONS.strip().split(' '): + wfile.write(f' "{name}",\n') + wfile.write(']\n\n') + + # Licenses Exceptions + wfile.write('ValidLicenseExceptions = [\n') + for line in licenses_exceptions.text.splitlines(): + wfile.write(f' "{line}",\n') + wfile.write(']\n') |