• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rpm-software-management / rpmlint / 27743531775

18 Jun 2026 07:23AM UTC coverage: 88.143% (+0.2%) from 87.933%
27743531775

push

github

6096 of 6916 relevant lines covered (88.14%)

3.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.22
/rpmlint/config.py
1
import os
4✔
2
from pathlib import Path
4✔
3
import re
4✔
4
import sys
4✔
5

6
from rpmlint.helpers import print_warning
4✔
7
try:
4✔
8
    import tomllib
4✔
9
except ImportError:
×
10
    import tomli as tomllib
×
11
import tomli_w
4✔
12
from xdg.BaseDirectory import xdg_config_dirs
4✔
13

14

15
class Config:
4✔
16
    """
17
    Load and parse rpmlint configuration.
18

19
    Configuration files are written in toml and should be placed in one of
20
    the XDG_CONFIG_DIRS directory or passed as "config" argument directly.
21

22
    By default it loads configdefaults.toml and all default locations and
23
    initializes basic testing layout for the rpmlint binary. Based on the
24
    opening order 'newer' configuration takes precedence over already
25
    existing one.
26
    """
27

28
    re_filter = re.compile(r'^\s*addFilter\s*\(\s*r?[\"\'](.*)[\"\']\s*\)')
4✔
29
    re_badness = re.compile(r'\s*setBadness\s*\([\'\"](.*)[\'\"],\s*[\'\"]?(\d+)[\'\"]?\)')
4✔
30
    config_defaults = Path(__file__).parent / 'configdefaults.toml'
4✔
31

32
    def __init__(self, config=None):
4✔
33
        """
34
        Initialize basic options and load rpmlint configuration.
35

36
        Args:
37
            config: A list of paths of configuration file(s) passed by user in
38
            command line.
39
        """
40
        # ordered list of configuration files we loaded
41
        # useful when debugging where from we got all the config options
42
        self.conf_files = []
4✔
43
        # Configuration content parsed from the toml configuration file
44
        self.configuration = None
4✔
45
        # List of rpmlintrc filters
46
        self.rpmlintrc_filters = []
4✔
47
        # whether to print more information or not
48
        self.info = False
4✔
49
        # whether to treat all messages as errors or not
50
        self.strict = False
4✔
51
        # whether to treat individual errors as non-fatal
52
        self.permissive = False
4✔
53

54
        # find configuration files and load them
4✔
55
        self.find_configs(config)
56
        self.load_config()
57
        # if loading of the configuration failed -> fall back only to defaults
4✔
58
        if not self.configuration:
4✔
59
            # reset the configs only to defaults
60
            self.conf_files = [self.config_defaults]
4✔
61
            self.load_config()
62

×
63
    def find_configs(self, config=None):
×
64
        """
65
        Find and store paths to all config files.
4✔
66

67
        It searches for default configuration, files in XDG_CONFIG_DIRS and
68
        user defined configuration (argument "config"). All configuration
69
        file paths found are then stored in self.conf_files variable.
70
        XDG_CONFIG_DIRS contains preference-ordered set of base directories
71
        to search for configuration files. Users can override it by their
72
        own configuration file (config parameter) and then that is
73
        added too.
74
        """
75
        # first load up the file that contains defaults
76
        self.conf_files.append(self.config_defaults)
77

78
        # Skip auto-loading when running under PYTEST
4✔
79
        if not os.environ.get('PYTEST_XDIST_TESTRUNUID') and not os.environ.get('CONFIG_DISABLE_AUTOLOADING'):
80
            # Then load up config directories on system
81
            for directory in reversed(xdg_config_dirs):
4✔
82
                confdir = Path(directory) / 'rpmlint'
83
                if confdir.is_dir():
×
84
                    # load all configs in the folders
×
85
                    confopts = sorted(confdir.glob('*toml'))
×
86
                    self.conf_files += confopts
87

×
88
        # As a last item load up the user configuration
×
89
        if config:
90
            for path in config:
91
                if path.exists():
4✔
92
                    # load this only if it really exist
4✔
93
                    self.conf_files.append(path)
4✔
94
                else:
95
                    print_warning(f'(none): W: error locating user requested configuration: {path}')
4✔
96

97
    def _merge_dictionaries(self, dest, source, override):
4✔
98
        """
99
        Merge in place dest dictionary for values in source in recursive way.
4✔
100
        If override is set to True, override instead of merging.
101
        """
102
        for k, v in source.items():
103
            vdest = dest.get(k)
104
            if isinstance(vdest, dict) and isinstance(v, dict):
4✔
105
                self._merge_dictionaries(vdest, v, override)
4✔
106
            else:
4✔
107
                if isinstance(vdest, list) and not override:
4✔
108
                    for item in v:
109
                        if item not in vdest:
4✔
110
                            vdest.append(item)
4✔
111
                else:
4✔
112
                    dest[k] = v
4✔
113

114
    def _is_override_config(self, config_file):
4✔
115
        return '.override.' in config_file.name
116

4✔
117
    def _sort_config_files(self, config_file):
4✔
118
        """
119
        Sort config files in the following order:
4✔
120
        configdefaults.toml, normal configs, *.override.* configs
121
        """
122
        if config_file == self.config_defaults:
123
            return 0
124
        if not self._is_override_config(config_file):
4✔
125
            return 1
4✔
126
        return 2
4✔
127

4✔
128
    def load_config(self, config=None):
4✔
129
        """
130
        Load the configuration files and append it to local dictionary.
4✔
131

132
        It's stored in self.configuration with the content of already loaded
133
        options.
134
        """
135
        if config:
136
            # just add the new config at the end of the list, someone injected
137
            # config file to us
4✔
138
            for path in config:
139
                if path not in self.conf_files and path.exists():
140
                    self.conf_files.append(path)
4✔
141

4✔
142
        cfg = {}
4✔
143
        # sort self.conf_files as we print list of loaded configuration files
144
        self.conf_files = sorted(self.conf_files, key=self._sort_config_files)
4✔
145
        for cf in self.conf_files:
146
            try:
4✔
147
                with open(cf, 'rb') as f:
4✔
148
                    toml_config = tomllib.load(f)
4✔
149
                self._merge_dictionaries(cfg, toml_config, self._is_override_config(cf))
4✔
150
            except tomllib.TOMLDecodeError as terr:
4✔
151
                print_warning(f'(none): E: fatal error while parsing configuration file {cf}: {terr}')
4✔
152
                sys.exit(4)
4✔
153
        self.configuration = cfg
4✔
154

4✔
155
    def load_rpmlintrc(self, rpmlintrc_file):
4✔
156
        """
157
        Load existing rpmlintrc files.
4✔
158

159
        Only setBadness and addFilter are processed.
160
        """
161

162
        rpmlintrc_lines = rpmlintrc_file.read_text().splitlines()
163
        filters = []
164
        for line in rpmlintrc_lines:
4✔
165
            m = self.re_filter.match(line)
4✔
166
            if m:
4✔
167
                filters.append(m.group(1))
4✔
168
            m = self.re_badness.match(line)
4✔
169
            if m:
4✔
170
                self.configuration['Scoring'].update({m.group(1): m.group(2)})
4✔
171

4✔
172
        self.configuration['Filters'] += filters
4✔
173
        self.rpmlintrc_filters = filters
174

4✔
175
    def print_config(self):
4✔
176
        """Print the current state of the configuration."""
177
        if self.configuration:
4✔
178
            print(tomli_w.dumps(self.configuration))
179

4✔
180
    def set_badness(self, result, badness):
4✔
181
        """Set specific badness for some result."""
182
        self.configuration['Scoring'][result] = badness
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc