Skip to content

Revert "improve encoding handling for setup.cfg" #1656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 1 addition & 34 deletions setuptools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
import functools
import distutils.core
import distutils.filelist
import re
from distutils.errors import DistutilsOptionError
from distutils.util import convert_path
from fnmatch import fnmatchcase

from ._deprecation_warning import SetuptoolsDeprecationWarning

from setuptools.extern.six import PY3, string_types
from setuptools.extern.six import PY3
from setuptools.extern.six.moves import filter, map

import setuptools.version
Expand Down Expand Up @@ -163,37 +161,6 @@ def __init__(self, dist, **kw):
_Command.__init__(self, dist)
vars(self).update(kw)

def _ensure_stringlike(self, option, what, default=None):
val = getattr(self, option)
if val is None:
setattr(self, option, default)
return default
elif not isinstance(val, string_types):
raise DistutilsOptionError("'%s' must be a %s (got `%s`)"
% (option, what, val))
return val

def ensure_string_list(self, option):
r"""Ensure that 'option' is a list of strings. If 'option' is
currently a string, we split it either on /,\s*/ or /\s+/, so
"foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
["foo", "bar", "baz"].
"""
val = getattr(self, option)
if val is None:
return
elif isinstance(val, string_types):
setattr(self, option, re.split(r',\s*|\s+', val))
else:
if isinstance(val, list):
ok = all(isinstance(v, string_types) for v in val)
else:
ok = False
if not ok:
raise DistutilsOptionError(
"'%s' must be a list of strings (got %r)"
% (option, val))

def reinitialize_command(self, command, reinit_subcommands=0, **kw):
cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
vars(cmd).update(kw)
Expand Down
126 changes: 4 additions & 122 deletions setuptools/dist.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
__all__ = ['Distribution']

import io
import sys
import re
import os
import warnings
Expand All @@ -11,12 +9,9 @@
import distutils.core
import distutils.cmd
import distutils.dist
from distutils.errors import DistutilsOptionError
from distutils.util import strtobool
from distutils.debug import DEBUG
from distutils.fancy_getopt import translate_longopt
import itertools


from collections import defaultdict
from email import message_from_file

Expand All @@ -36,8 +31,8 @@
from setuptools import windows_support
from setuptools.monkey import get_unpatched
from setuptools.config import parse_configuration
from .unicode_utils import detect_encoding
import pkg_resources
from .py36compat import Distribution_parse_config_files

__import__('setuptools.extern.packaging.specifiers')
__import__('setuptools.extern.packaging.version')
Expand Down Expand Up @@ -337,7 +332,7 @@ def check_packages(dist, attr, value):
_Distribution = get_unpatched(distutils.core.Distribution)


class Distribution(_Distribution):
class Distribution(Distribution_parse_config_files, _Distribution):
"""Distribution with support for features, tests, and package data

This is an enhanced version of 'distutils.dist.Distribution' that
Expand Down Expand Up @@ -561,125 +556,12 @@ def _clean_req(self, req):
req.marker = None
return req

def _parse_config_files(self, filenames=None):
"""
Adapted from distutils.dist.Distribution.parse_config_files,
this method provides the same functionality in subtly-improved
ways.
"""
from setuptools.extern.six.moves.configparser import ConfigParser

# Ignore install directory options if we have a venv
if six.PY3 and sys.prefix != sys.base_prefix:
ignore_options = [
'install-base', 'install-platbase', 'install-lib',
'install-platlib', 'install-purelib', 'install-headers',
'install-scripts', 'install-data', 'prefix', 'exec-prefix',
'home', 'user', 'root']
else:
ignore_options = []

ignore_options = frozenset(ignore_options)

if filenames is None:
filenames = self.find_config_files()

if DEBUG:
self.announce("Distribution.parse_config_files():")

parser = ConfigParser()
for filename in filenames:
with io.open(filename, 'rb') as fp:
encoding = detect_encoding(fp)
if DEBUG:
self.announce(" reading %s [%s]" % (
filename, encoding or 'locale')
)
reader = io.TextIOWrapper(fp, encoding=encoding)
(parser.read_file if six.PY3 else parser.readfp)(reader)
for section in parser.sections():
options = parser.options(section)
opt_dict = self.get_option_dict(section)

for opt in options:
if opt != '__name__' and opt not in ignore_options:
val = parser.get(section, opt)
opt = opt.replace('-', '_')
opt_dict[opt] = (filename, val)

# Make the ConfigParser forget everything (so we retain
# the original filenames that options come from)
parser.__init__()

# If there was a "global" section in the config file, use it
# to set Distribution options.

if 'global' in self.command_options:
for (opt, (src, val)) in self.command_options['global'].items():
alias = self.negative_opt.get(opt)
try:
if alias:
setattr(self, alias, not strtobool(val))
elif opt in ('verbose', 'dry_run'): # ugh!
setattr(self, opt, strtobool(val))
else:
setattr(self, opt, val)
except ValueError as msg:
raise DistutilsOptionError(msg)

def _set_command_options(self, command_obj, option_dict=None):
"""
Set the options for 'command_obj' from 'option_dict'. Basically
this means copying elements of a dictionary ('option_dict') to
attributes of an instance ('command').

'command_obj' must be a Command instance. If 'option_dict' is not
supplied, uses the standard option dictionary for this command
(from 'self.command_options').

(Adopted from distutils.dist.Distribution._set_command_options)
"""
command_name = command_obj.get_command_name()
if option_dict is None:
option_dict = self.get_option_dict(command_name)

if DEBUG:
self.announce(" setting options for '%s' command:" % command_name)
for (option, (source, value)) in option_dict.items():
if DEBUG:
self.announce(" %s = %s (from %s)" % (option, value,
source))
try:
bool_opts = [translate_longopt(o)
for o in command_obj.boolean_options]
except AttributeError:
bool_opts = []
try:
neg_opt = command_obj.negative_opt
except AttributeError:
neg_opt = {}

try:
is_string = isinstance(value, six.string_types)
if option in neg_opt and is_string:
setattr(command_obj, neg_opt[option], not strtobool(value))
elif option in bool_opts and is_string:
setattr(command_obj, option, strtobool(value))
elif hasattr(command_obj, option):
setattr(command_obj, option, value)
else:
raise DistutilsOptionError(
"error in %s: command '%s' has no such option '%s'"
% (source, command_name, option))
except ValueError as msg:
raise DistutilsOptionError(msg)

def parse_config_files(self, filenames=None, ignore_option_errors=False):
"""Parses configuration files from various levels
and loads configuration.

"""
self._parse_config_files(filenames=filenames)
_Distribution.parse_config_files(self, filenames=filenames)

parse_configuration(self, self.command_options,
ignore_option_errors=ignore_option_errors)
Expand Down
82 changes: 82 additions & 0 deletions setuptools/py36compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import sys
from distutils.errors import DistutilsOptionError
from distutils.util import strtobool
from distutils.debug import DEBUG


class Distribution_parse_config_files:
"""
Mix-in providing forward-compatibility for functionality to be
included by default on Python 3.7.

Do not edit the code in this class except to update functionality
as implemented in distutils.
"""
def parse_config_files(self, filenames=None):
from configparser import ConfigParser

# Ignore install directory options if we have a venv
if sys.prefix != sys.base_prefix:
ignore_options = [
'install-base', 'install-platbase', 'install-lib',
'install-platlib', 'install-purelib', 'install-headers',
'install-scripts', 'install-data', 'prefix', 'exec-prefix',
'home', 'user', 'root']
else:
ignore_options = []

ignore_options = frozenset(ignore_options)

if filenames is None:
filenames = self.find_config_files()

if DEBUG:
self.announce("Distribution.parse_config_files():")

parser = ConfigParser(interpolation=None)
for filename in filenames:
if DEBUG:
self.announce(" reading %s" % filename)
parser.read(filename)
for section in parser.sections():
options = parser.options(section)
opt_dict = self.get_option_dict(section)

for opt in options:
if opt != '__name__' and opt not in ignore_options:
val = parser.get(section,opt)
opt = opt.replace('-', '_')
opt_dict[opt] = (filename, val)

# Make the ConfigParser forget everything (so we retain
# the original filenames that options come from)
parser.__init__()

# If there was a "global" section in the config file, use it
# to set Distribution options.

if 'global' in self.command_options:
for (opt, (src, val)) in self.command_options['global'].items():
alias = self.negative_opt.get(opt)
try:
if alias:
setattr(self, alias, not strtobool(val))
elif opt in ('verbose', 'dry_run'): # ugh!
setattr(self, opt, strtobool(val))
else:
setattr(self, opt, val)
except ValueError as msg:
raise DistutilsOptionError(msg)


if sys.version_info < (3,):
# Python 2 behavior is sufficient
class Distribution_parse_config_files:
pass


if False:
# When updated behavior is available upstream,
# disable override here.
class Distribution_parse_config_files:
pass
2 changes: 2 additions & 0 deletions setuptools/tests/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def run_setup_py(cmd, pypath=None, path=None,
cmd, stdout=_PIPE, stderr=_PIPE, shell=shell, env=env,
)

if isinstance(data_stream, tuple):
data_stream = slice(*data_stream)
data = proc.communicate()[data_stream]
except OSError:
return 1, ''
Expand Down
70 changes: 70 additions & 0 deletions setuptools/tests/test_build_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
from setuptools.dist import Distribution
from setuptools.extension import Extension

from . import environment
from .files import build_files
from .textwrap import DALS


class TestBuildExt:
def test_get_ext_filename(self):
Expand Down Expand Up @@ -43,3 +47,69 @@ def test_abi3_filename(self):
assert res.endswith('eggs.pyd')
else:
assert 'abi3' in res


def test_build_ext_config_handling(tmpdir_cwd):
files = {
'setup.py': DALS(
"""
from setuptools import Extension, setup
setup(
name='foo',
version='0.0.0',
ext_modules=[Extension('foo', ['foo.c'])],
)
"""),
'foo.c': DALS(
"""
#include "Python.h"

#if PY_MAJOR_VERSION >= 3

static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"foo",
NULL,
0,
NULL,
NULL,
NULL,
NULL,
NULL
};

#define INITERROR return NULL

PyMODINIT_FUNC PyInit_foo(void)

#else

#define INITERROR return

void initfoo(void)

#endif
{
#if PY_MAJOR_VERSION >= 3
PyObject *module = PyModule_Create(&moduledef);
#else
PyObject *module = Py_InitModule("extension", NULL);
#endif
if (module == NULL)
INITERROR;
#if PY_MAJOR_VERSION >= 3
return module;
#endif
}
"""),
'setup.cfg': DALS(
"""
[build]
build-base = foo_build
"""),
}
build_files(files)
code, output = environment.run_setup_py(
cmd=['build'], data_stream=(0, 2),
)
assert code == 0, '\nSTDOUT:\n%s\nSTDERR:\n%s' % output
Loading