ยปCore Development>Code coverage>Lib/packaging/config.py

Python code coverage for Lib/packaging/config.py

#countcontent
1n/a"""Utilities to find and read config files used by packaging."""
2n/a
3n/aimport os
4n/aimport sys
5n/aimport logging
6n/a
7n/afrom shlex import split
8n/afrom configparser import RawConfigParser
9n/afrom packaging import logger
10n/afrom packaging.errors import PackagingOptionError
11n/afrom packaging.compiler.extension import Extension
12n/afrom packaging.util import (check_environ, iglob, resolve_name, strtobool,
13n/a split_multiline)
14n/afrom packaging.compiler import set_compiler
15n/afrom packaging.command import set_command
16n/afrom packaging.markers import interpret
17n/a
18n/a
19n/adef _check_name(name, packages):
20n/a if '.' not in name:
21n/a return
22n/a parts = name.split('.')
23n/a parent = '.'.join(parts[:-1])
24n/a if parent not in packages:
25n/a # we could log a warning instead of raising, but what's the use
26n/a # of letting people build modules they can't import?
27n/a raise PackagingOptionError(
28n/a 'parent package for extension %r not found' % name)
29n/a
30n/a
31n/adef _pop_values(values_dct, key):
32n/a """Remove values from the dictionary and convert them as a list"""
33n/a vals_str = values_dct.pop(key, '')
34n/a if not vals_str:
35n/a return
36n/a fields = []
37n/a # the line separator is \n for setup.cfg files
38n/a for field in vals_str.split('\n'):
39n/a tmp_vals = field.split('--')
40n/a if len(tmp_vals) == 2 and not interpret(tmp_vals[1]):
41n/a continue
42n/a fields.append(tmp_vals[0])
43n/a # Get bash options like `gcc -print-file-name=libgcc.a` XXX bash options?
44n/a vals = split(' '.join(fields))
45n/a if vals:
46n/a return vals
47n/a
48n/a
49n/adef _rel_path(base, path):
50n/a # normalizes and returns a lstripped-/-separated path
51n/a base = base.replace(os.path.sep, '/')
52n/a path = path.replace(os.path.sep, '/')
53n/a assert path.startswith(base)
54n/a return path[len(base):].lstrip('/')
55n/a
56n/a
57n/adef get_resources_dests(resources_root, rules):
58n/a """Find destinations for resources files"""
59n/a destinations = {}
60n/a for base, suffix, dest in rules:
61n/a prefix = os.path.join(resources_root, base)
62n/a for abs_base in iglob(prefix):
63n/a abs_glob = os.path.join(abs_base, suffix)
64n/a for abs_path in iglob(abs_glob):
65n/a resource_file = _rel_path(resources_root, abs_path)
66n/a if dest is None: # remove the entry if it was here
67n/a destinations.pop(resource_file, None)
68n/a else:
69n/a rel_path = _rel_path(abs_base, abs_path)
70n/a rel_dest = dest.replace(os.path.sep, '/').rstrip('/')
71n/a destinations[resource_file] = rel_dest + '/' + rel_path
72n/a return destinations
73n/a
74n/a
75n/aclass Config:
76n/a """Class used to work with configuration files"""
77n/a def __init__(self, dist):
78n/a self.dist = dist
79n/a self.setup_hooks = []
80n/a
81n/a def run_hooks(self, config):
82n/a """Run setup hooks in the order defined in the spec."""
83n/a for hook in self.setup_hooks:
84n/a hook(config)
85n/a
86n/a def find_config_files(self):
87n/a """Find as many configuration files as should be processed for this
88n/a platform, and return a list of filenames in the order in which they
89n/a should be parsed. The filenames returned are guaranteed to exist
90n/a (modulo nasty race conditions).
91n/a
92n/a There are three possible config files: packaging.cfg in the
93n/a Packaging installation directory (ie. where the top-level
94n/a Packaging __inst__.py file lives), a file in the user's home
95n/a directory named .pydistutils.cfg on Unix and pydistutils.cfg
96n/a on Windows/Mac; and setup.cfg in the current directory.
97n/a
98n/a The file in the user's home directory can be disabled with the
99n/a --no-user-cfg option.
100n/a """
101n/a files = []
102n/a check_environ()
103n/a
104n/a # Where to look for the system-wide Packaging config file
105n/a sys_dir = os.path.dirname(sys.modules['packaging'].__file__)
106n/a
107n/a # Look for the system config file
108n/a sys_file = os.path.join(sys_dir, "packaging.cfg")
109n/a if os.path.isfile(sys_file):
110n/a files.append(sys_file)
111n/a
112n/a # What to call the per-user config file
113n/a if os.name == 'posix':
114n/a user_filename = ".pydistutils.cfg"
115n/a else:
116n/a user_filename = "pydistutils.cfg"
117n/a
118n/a # And look for the user config file
119n/a if self.dist.want_user_cfg:
120n/a user_file = os.path.join(os.path.expanduser('~'), user_filename)
121n/a if os.path.isfile(user_file):
122n/a files.append(user_file)
123n/a
124n/a # All platforms support local setup.cfg
125n/a local_file = "setup.cfg"
126n/a if os.path.isfile(local_file):
127n/a files.append(local_file)
128n/a
129n/a if logger.isEnabledFor(logging.DEBUG):
130n/a logger.debug("using config files: %s", ', '.join(files))
131n/a return files
132n/a
133n/a def _convert_metadata(self, name, value):
134n/a # converts a value found in setup.cfg into a valid metadata
135n/a # XXX
136n/a return value
137n/a
138n/a def _read_setup_cfg(self, parser, cfg_filename):
139n/a cfg_directory = os.path.dirname(os.path.abspath(cfg_filename))
140n/a content = {}
141n/a for section in parser.sections():
142n/a content[section] = dict(parser.items(section))
143n/a
144n/a # global setup hooks are called first
145n/a if 'global' in content:
146n/a if 'setup_hooks' in content['global']:
147n/a setup_hooks = split_multiline(content['global']['setup_hooks'])
148n/a
149n/a # add project directory to sys.path, to allow hooks to be
150n/a # distributed with the project
151n/a sys.path.insert(0, cfg_directory)
152n/a try:
153n/a for line in setup_hooks:
154n/a try:
155n/a hook = resolve_name(line)
156n/a except ImportError as e:
157n/a logger.warning('cannot find setup hook: %s',
158n/a e.args[0])
159n/a else:
160n/a self.setup_hooks.append(hook)
161n/a self.run_hooks(content)
162n/a finally:
163n/a sys.path.pop(0)
164n/a
165n/a metadata = self.dist.metadata
166n/a
167n/a # setting the metadata values
168n/a if 'metadata' in content:
169n/a for key, value in content['metadata'].items():
170n/a key = key.replace('_', '-')
171n/a if metadata.is_multi_field(key):
172n/a value = split_multiline(value)
173n/a
174n/a if key == 'project-url':
175n/a value = [(label.strip(), url.strip())
176n/a for label, url in
177n/a [v.split(',') for v in value]]
178n/a
179n/a if key == 'description-file':
180n/a if 'description' in content['metadata']:
181n/a msg = ("description and description-file' are "
182n/a "mutually exclusive")
183n/a raise PackagingOptionError(msg)
184n/a
185n/a filenames = value.split()
186n/a
187n/a # concatenate all files
188n/a value = []
189n/a for filename in filenames:
190n/a # will raise if file not found
191n/a with open(filename) as description_file:
192n/a value.append(description_file.read().strip())
193n/a # add filename as a required file
194n/a if filename not in metadata.requires_files:
195n/a metadata.requires_files.append(filename)
196n/a value = '\n'.join(value).strip()
197n/a key = 'description'
198n/a
199n/a if metadata.is_metadata_field(key):
200n/a metadata[key] = self._convert_metadata(key, value)
201n/a
202n/a if 'files' in content:
203n/a files = content['files']
204n/a self.dist.package_dir = files.pop('packages_root', None)
205n/a
206n/a files = dict((key, split_multiline(value)) for key, value in
207n/a files.items())
208n/a
209n/a self.dist.packages = []
210n/a
211n/a packages = files.get('packages', [])
212n/a if isinstance(packages, str):
213n/a packages = [packages]
214n/a
215n/a for package in packages:
216n/a if ':' in package:
217n/a dir_, package = package.split(':')
218n/a self.dist.package_dir[package] = dir_
219n/a self.dist.packages.append(package)
220n/a
221n/a self.dist.py_modules = files.get('modules', [])
222n/a if isinstance(self.dist.py_modules, str):
223n/a self.dist.py_modules = [self.dist.py_modules]
224n/a self.dist.scripts = files.get('scripts', [])
225n/a if isinstance(self.dist.scripts, str):
226n/a self.dist.scripts = [self.dist.scripts]
227n/a
228n/a self.dist.package_data = {}
229n/a # bookkeeping for the loop below
230n/a firstline = True
231n/a prev = None
232n/a
233n/a for line in files.get('package_data', []):
234n/a if '=' in line:
235n/a # package name -- file globs or specs
236n/a key, value = line.split('=')
237n/a prev = self.dist.package_data[key.strip()] = value.split()
238n/a elif firstline:
239n/a # invalid continuation on the first line
240n/a raise PackagingOptionError(
241n/a 'malformed package_data first line: %r (misses "=")' %
242n/a line)
243n/a else:
244n/a # continuation, add to last seen package name
245n/a prev.extend(line.split())
246n/a
247n/a firstline = False
248n/a
249n/a self.dist.data_files = []
250n/a for data in files.get('data_files', []):
251n/a data = data.split('=')
252n/a if len(data) != 2:
253n/a continue
254n/a key, value = data
255n/a values = [v.strip() for v in value.split(',')]
256n/a self.dist.data_files.append((key, values))
257n/a
258n/a # manifest template
259n/a self.dist.extra_files = files.get('extra_files', [])
260n/a
261n/a resources = []
262n/a for rule in files.get('resources', []):
263n/a glob, destination = rule.split('=', 1)
264n/a rich_glob = glob.strip().split(' ', 1)
265n/a if len(rich_glob) == 2:
266n/a prefix, suffix = rich_glob
267n/a else:
268n/a assert len(rich_glob) == 1
269n/a prefix = ''
270n/a suffix = glob
271n/a if destination == '<exclude>':
272n/a destination = None
273n/a resources.append(
274n/a (prefix.strip(), suffix.strip(), destination.strip()))
275n/a self.dist.data_files = get_resources_dests(
276n/a cfg_directory, resources)
277n/a
278n/a ext_modules = self.dist.ext_modules
279n/a for section_key in content:
280n/a # no str.partition in 2.4 :(
281n/a labels = section_key.split(':')
282n/a if len(labels) == 2 and labels[0] == 'extension':
283n/a values_dct = content[section_key]
284n/a if 'name' in values_dct:
285n/a raise PackagingOptionError(
286n/a 'extension name should be given as [extension: name], '
287n/a 'not as key')
288n/a name = labels[1].strip()
289n/a _check_name(name, self.dist.packages)
290n/a ext_modules.append(Extension(
291n/a name,
292n/a _pop_values(values_dct, 'sources'),
293n/a _pop_values(values_dct, 'include_dirs'),
294n/a _pop_values(values_dct, 'define_macros'),
295n/a _pop_values(values_dct, 'undef_macros'),
296n/a _pop_values(values_dct, 'library_dirs'),
297n/a _pop_values(values_dct, 'libraries'),
298n/a _pop_values(values_dct, 'runtime_library_dirs'),
299n/a _pop_values(values_dct, 'extra_objects'),
300n/a _pop_values(values_dct, 'extra_compile_args'),
301n/a _pop_values(values_dct, 'extra_link_args'),
302n/a _pop_values(values_dct, 'export_symbols'),
303n/a _pop_values(values_dct, 'swig_opts'),
304n/a _pop_values(values_dct, 'depends'),
305n/a values_dct.pop('language', None),
306n/a values_dct.pop('optional', None),
307n/a **values_dct))
308n/a
309n/a def parse_config_files(self, filenames=None):
310n/a if filenames is None:
311n/a filenames = self.find_config_files()
312n/a
313n/a logger.debug("Distribution.parse_config_files():")
314n/a
315n/a parser = RawConfigParser()
316n/a
317n/a for filename in filenames:
318n/a logger.debug(" reading %s", filename)
319n/a parser.read(filename, encoding='utf-8')
320n/a
321n/a if os.path.split(filename)[-1] == 'setup.cfg':
322n/a self._read_setup_cfg(parser, filename)
323n/a
324n/a for section in parser.sections():
325n/a if section == 'global':
326n/a if parser.has_option('global', 'compilers'):
327n/a self._load_compilers(parser.get('global', 'compilers'))
328n/a
329n/a if parser.has_option('global', 'commands'):
330n/a self._load_commands(parser.get('global', 'commands'))
331n/a
332n/a options = parser.options(section)
333n/a opt_dict = self.dist.get_option_dict(section)
334n/a
335n/a for opt in options:
336n/a if opt == '__name__':
337n/a continue
338n/a val = parser.get(section, opt)
339n/a opt = opt.replace('-', '_')
340n/a
341n/a if opt == 'sub_commands':
342n/a val = split_multiline(val)
343n/a if isinstance(val, str):
344n/a val = [val]
345n/a
346n/a # Hooks use a suffix system to prevent being overriden
347n/a # by a config file processed later (i.e. a hook set in
348n/a # the user config file cannot be replaced by a hook
349n/a # set in a project config file, unless they have the
350n/a # same suffix).
351n/a if (opt.startswith("pre_hook.") or
352n/a opt.startswith("post_hook.")):
353n/a hook_type, alias = opt.split(".")
354n/a hook_dict = opt_dict.setdefault(
355n/a hook_type, (filename, {}))[1]
356n/a hook_dict[alias] = val
357n/a else:
358n/a opt_dict[opt] = filename, val
359n/a
360n/a # Make the RawConfigParser forget everything (so we retain
361n/a # the original filenames that options come from)
362n/a parser.__init__()
363n/a
364n/a # If there was a "global" section in the config file, use it
365n/a # to set Distribution options.
366n/a if 'global' in self.dist.command_options:
367n/a for opt, (src, val) in self.dist.command_options['global'].items():
368n/a alias = self.dist.negative_opt.get(opt)
369n/a try:
370n/a if alias:
371n/a setattr(self.dist, alias, not strtobool(val))
372n/a elif opt == 'dry_run': # FIXME ugh!
373n/a setattr(self.dist, opt, strtobool(val))
374n/a else:
375n/a setattr(self.dist, opt, val)
376n/a except ValueError as msg:
377n/a raise PackagingOptionError(msg)
378n/a
379n/a def _load_compilers(self, compilers):
380n/a compilers = split_multiline(compilers)
381n/a if isinstance(compilers, str):
382n/a compilers = [compilers]
383n/a for compiler in compilers:
384n/a set_compiler(compiler.strip())
385n/a
386n/a def _load_commands(self, commands):
387n/a commands = split_multiline(commands)
388n/a if isinstance(commands, str):
389n/a commands = [commands]
390n/a for command in commands:
391n/a set_command(command.strip())