diff --git a/changelog.d/3609.change.rst b/changelog.d/3609.change.rst new file mode 100644 index 0000000000..5ed5cc2512 --- /dev/null +++ b/changelog.d/3609.change.rst @@ -0,0 +1 @@ +Merge with pypa/distutils@d82d926 including support for DIST_EXTRA_CONFIG in pypa/distutils#177. diff --git a/docs/deprecated/distutils/configfile.rst b/docs/deprecated/distutils/configfile.rst index 328936fb40..2a0fbb31d8 100644 --- a/docs/deprecated/distutils/configfile.rst +++ b/docs/deprecated/distutils/configfile.rst @@ -36,7 +36,8 @@ consequences: :file:`setup.py` * installers can override anything in :file:`setup.cfg` using the command-line - options to :file:`setup.py` + options to :file:`setup.py` or by pointing :envvar:`DIST_EXTRA_CONFIG` + to another configuration file The basic syntax of the configuration file is simple: diff --git a/setuptools/_distutils/dist.py b/setuptools/_distutils/dist.py index 0406ab19cb..1dc25fe541 100644 --- a/setuptools/_distutils/dist.py +++ b/setuptools/_distutils/dist.py @@ -7,6 +7,8 @@ import sys import os import re +import pathlib +import contextlib from email import message_from_file try: @@ -322,47 +324,40 @@ def find_config_files(self): should be parsed. The filenames returned are guaranteed to exist (modulo nasty race conditions). - There are three possible config files: distutils.cfg in the - Distutils installation directory (ie. where the top-level - Distutils __inst__.py file lives), a file in the user's home - directory named .pydistutils.cfg on Unix and pydistutils.cfg - on Windows/Mac; and setup.cfg in the current directory. - - The file in the user's home directory can be disabled with the - --no-user-cfg option. + There are multiple possible config files: + - distutils.cfg in the Distutils installation directory (i.e. + where the top-level Distutils __inst__.py file lives) + - a file in the user's home directory named .pydistutils.cfg + on Unix and pydistutils.cfg on Windows/Mac; may be disabled + with the ``--no-user-cfg`` option + - setup.cfg in the current directory + - a file named by an environment variable """ - files = [] check_environ() + files = [str(path) for path in self._gen_paths() if path.is_file()] - # Where to look for the system-wide Distutils config file - sys_dir = os.path.dirname(sys.modules['distutils'].__file__) + if DEBUG: + self.announce("using config files: %s" % ', '.join(files)) - # Look for the system config file - sys_file = os.path.join(sys_dir, "distutils.cfg") - if os.path.isfile(sys_file): - files.append(sys_file) + return files - # What to call the per-user config file - if os.name == 'posix': - user_filename = ".pydistutils.cfg" - else: - user_filename = "pydistutils.cfg" + def _gen_paths(self): + # The system-wide Distutils config file + sys_dir = pathlib.Path(sys.modules['distutils'].__file__).parent + yield sys_dir / "distutils.cfg" - # And look for the user config file + # The per-user config file + prefix = '.' * (os.name == 'posix') + filename = prefix + 'pydistutils.cfg' if self.want_user_cfg: - user_file = os.path.join(os.path.expanduser('~'), user_filename) - if os.path.isfile(user_file): - files.append(user_file) + yield pathlib.Path('~').expanduser() / filename # All platforms support local setup.cfg - local_file = "setup.cfg" - if os.path.isfile(local_file): - files.append(local_file) + yield pathlib.Path('setup.cfg') - if DEBUG: - self.announce("using config files: %s" % ', '.join(files)) - - return files + # Additional config indicated in the environment + with contextlib.suppress(TypeError): + yield pathlib.Path(os.getenv("DIST_EXTRA_CONFIG")) def parse_config_files(self, filenames=None): # noqa: C901 from configparser import ConfigParser diff --git a/setuptools/_distutils/tests/test_dist.py b/setuptools/_distutils/tests/test_dist.py index ddfaf92167..a943832620 100644 --- a/setuptools/_distutils/tests/test_dist.py +++ b/setuptools/_distutils/tests/test_dist.py @@ -8,6 +8,7 @@ import unittest.mock as mock import pytest +import jaraco.path from distutils.dist import Distribution, fix_help_options from distutils.cmd import Command @@ -18,6 +19,9 @@ from distutils import log +pydistutils_cfg = '.' * (os.name == 'posix') + 'pydistutils.cfg' + + class test_dist(Command): """Sample distutils extension command.""" @@ -97,26 +101,26 @@ def test_venv_install_options(self, request): fakepath = '/somedir' - with open(TESTFN, "w") as f: - print( - ( - "[install]\n" - "install-base = {0}\n" - "install-platbase = {0}\n" - "install-lib = {0}\n" - "install-platlib = {0}\n" - "install-purelib = {0}\n" - "install-headers = {0}\n" - "install-scripts = {0}\n" - "install-data = {0}\n" - "prefix = {0}\n" - "exec-prefix = {0}\n" - "home = {0}\n" - "user = {0}\n" - "root = {0}" - ).format(fakepath), - file=f, - ) + jaraco.path.build( + { + TESTFN: f""" + [install] + install-base = {fakepath} + install-platbase = {fakepath} + install-lib = {fakepath} + install-platlib = {fakepath} + install-purelib = {fakepath} + install-headers = {fakepath} + install-scripts = {fakepath} + install-data = {fakepath} + prefix = {fakepath} + exec-prefix = {fakepath} + home = {fakepath} + user = {fakepath} + root = {fakepath} + """, + } + ) # Base case: Not in a Virtual Environment with mock.patch.multiple(sys, prefix='/a', base_prefix='/a'): @@ -157,12 +161,14 @@ def test_venv_install_options(self, request): def test_command_packages_configfile(self, request, clear_argv): sys.argv.append("build") request.addfinalizer(functools.partial(os.unlink, TESTFN)) - f = open(TESTFN, "w") - try: - print("[global]", file=f) - print("command_packages = foo.bar, splat", file=f) - finally: - f.close() + jaraco.path.build( + { + TESTFN: """ + [global] + command_packages = foo.bar, splat + """, + } + ) d = self.create_distribution([TESTFN]) assert d.get_command_packages() == ["distutils.command", "foo.bar", "splat"] @@ -240,30 +246,15 @@ def test_announce(self): with pytest.raises(ValueError): dist.announce(args, kwargs) - def test_find_config_files_disable(self): + def test_find_config_files_disable(self, temp_home): # Ticket #1180: Allow user to disable their home config file. - temp_home = self.mkdtemp() - if os.name == 'posix': - user_filename = os.path.join(temp_home, ".pydistutils.cfg") - else: - user_filename = os.path.join(temp_home, "pydistutils.cfg") - - with open(user_filename, 'w') as f: - f.write('[distutils]\n') - - def _expander(path): - return temp_home + jaraco.path.build({pydistutils_cfg: '[distutils]\n'}, temp_home) - old_expander = os.path.expanduser - os.path.expanduser = _expander - try: - d = Distribution() - all_files = d.find_config_files() + d = Distribution() + all_files = d.find_config_files() - d = Distribution(attrs={'script_args': ['--no-user-cfg']}) - files = d.find_config_files() - finally: - os.path.expanduser = old_expander + d = Distribution(attrs={'script_args': ['--no-user-cfg']}) + files = d.find_config_files() # make sure --no-user-cfg disables the user cfg file assert len(all_files) - 1 == len(files) @@ -271,7 +262,7 @@ def _expander(path): @pytest.mark.usefixtures('save_env') @pytest.mark.usefixtures('save_argv') -class MetadataTestCase(support.TempdirManager): +class TestMetadata(support.TempdirManager): def format_metadata(self, dist): sio = io.StringIO() dist.metadata.write_pkg_file(sio) @@ -456,41 +447,20 @@ def test_long_description(self): meta = meta.replace('\n' + 8 * ' ', '\n') assert long_desc in meta - def test_custom_pydistutils(self): - # fixes #2166 - # make sure pydistutils.cfg is found - if os.name == 'posix': - user_filename = ".pydistutils.cfg" - else: - user_filename = "pydistutils.cfg" - - temp_dir = self.mkdtemp() - user_filename = os.path.join(temp_dir, user_filename) - f = open(user_filename, 'w') - try: - f.write('.') - finally: - f.close() - - try: - dist = Distribution() - - # linux-style - if sys.platform in ('linux', 'darwin'): - os.environ['HOME'] = temp_dir - files = dist.find_config_files() - assert user_filename in files - - # win32-style - if sys.platform == 'win32': - # home drive should be found - os.environ['USERPROFILE'] = temp_dir - files = dist.find_config_files() - assert user_filename in files, '{!r} not found in {!r}'.format( - user_filename, files - ) - finally: - os.remove(user_filename) + def test_custom_pydistutils(self, temp_home): + """ + pydistutils.cfg is found + """ + jaraco.path.build({pydistutils_cfg: ''}, temp_home) + config_path = temp_home / pydistutils_cfg + + assert str(config_path) in Distribution().find_config_files() + + def test_extra_pydistutils(self, monkeypatch, tmp_path): + jaraco.path.build({'overrides.cfg': ''}, tmp_path) + filename = tmp_path / 'overrides.cfg' + monkeypatch.setenv('DIST_EXTRA_CONFIG', str(filename)) + assert str(filename) in Distribution().find_config_files() def test_fix_help_options(self): help_tuples = [('a', 'b', 'c', 'd'), (1, 2, 3, 4)] @@ -498,9 +468,10 @@ def test_fix_help_options(self): assert fancy_options[0] == ('a', 'b', 'c') assert fancy_options[1] == (1, 2, 3) - def test_show_help(self): + def test_show_help(self, request): # smoke test, just makes sure some help is displayed - self.addCleanup(log.set_threshold, log._global_log.threshold) + reset_log = functools.partial(log.set_threshold, log._global_log.threshold) + request.addfinalizer(reset_log) dist = Distribution() sys.argv = [] dist.help = 1 diff --git a/setuptools/_distutils/tests/test_util.py b/setuptools/_distutils/tests/test_util.py index 605b0d40b7..070a277069 100644 --- a/setuptools/_distutils/tests/test_util.py +++ b/setuptools/_distutils/tests/test_util.py @@ -136,17 +136,16 @@ def _join(*path): # XXX platforms to be covered: mac def test_check_environ(self): - util._environ_checked = 0 + util.check_environ.cache_clear() os.environ.pop('HOME', None) check_environ() assert os.environ['PLAT'] == get_platform() - assert util._environ_checked == 1 @pytest.mark.skipif("os.name != 'posix'") def test_check_environ_getpwuid(self): - util._environ_checked = 0 + util.check_environ.cache_clear() os.environ.pop('HOME', None) import pwd @@ -159,7 +158,7 @@ def test_check_environ_getpwuid(self): check_environ() assert os.environ['HOME'] == '/home/distutils' - util._environ_checked = 0 + util.check_environ.cache_clear() os.environ.pop('HOME', None) # bpo-10496: Catch pwd.getpwuid() error diff --git a/setuptools/_distutils/util.py b/setuptools/_distutils/util.py index d95992ec99..4763202b67 100644 --- a/setuptools/_distutils/util.py +++ b/setuptools/_distutils/util.py @@ -11,6 +11,8 @@ import subprocess import sys import sysconfig +import functools + from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError from distutils.dep_util import newer from distutils.spawn import spawn @@ -170,9 +172,7 @@ def change_root(new_root, pathname): raise DistutilsPlatformError(f"nothing known about platform '{os.name}'") -_environ_checked = 0 - - +@functools.lru_cache() def check_environ(): """Ensure that 'os.environ' has all the environment variables we guarantee that users can use in config files, command-line options, @@ -181,10 +181,6 @@ def check_environ(): PLAT - description of the current platform, including hardware and OS (see 'get_platform()') """ - global _environ_checked - if _environ_checked: - return - if os.name == 'posix' and 'HOME' not in os.environ: try: import pwd @@ -198,8 +194,6 @@ def check_environ(): if 'PLAT' not in os.environ: os.environ['PLAT'] = get_platform() - _environ_checked = 1 - def subst_vars(s, local_vars): """