From: JinWang An Date: Mon, 27 Mar 2023 08:02:56 +0000 (+0900) Subject: Imported Upstream version 65.4.1 X-Git-Tag: upstream/65.4.1^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=92b5a10be6824317c6bb810754b8c17812ef6e59;p=platform%2Fupstream%2Fpython-setuptools.git Imported Upstream version 65.4.1 --- diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 66973fe..aeee9a8 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 65.4.0 +current_version = 65.4.1 commit = True tag = True diff --git a/.github/workflows/ci-sage.yml b/.github/workflows/ci-sage.yml index 2a91934..f3fe851 100644 --- a/.github/workflows/ci-sage.yml +++ b/.github/workflows/ci-sage.yml @@ -37,6 +37,9 @@ on: push: tags: - '*' + pull_request: + paths: + - .github/workflows/ci-sage.yml workflow_dispatch: # Allow to run manually @@ -45,19 +48,6 @@ env: DIST_PREREQ: python3 # Name of this project in the Sage distribution SPKG: setuptools - # Sage distribution packages to build - TARGETS_PRE: build/make/Makefile - TARGETS: setuptools pyzmq - TARGETS_OPTIONAL: build/make/Makefile - # Standard setting: Test the current beta release of Sage: - SAGE_REPO: sagemath/sage - SAGE_REF: develop - # Test with the branch from https://trac.sagemath.org/ticket/33288 - # This may provide hotfixes for the CI that have not been merged into - # the sage develop branch yet. - SAGE_TRAC_GIT: https://github.com/sagemath/sagetrac-mirror.git - SAGE_TICKET: 33288 - REMOVE_PATCHES: "*" jobs: @@ -85,68 +75,23 @@ jobs: path: upstream name: upstream - docker: - runs-on: ubuntu-latest + linux: + # https://github.com/sagemath/sage/blob/develop/.github/workflows/docker.yml + # Use branch of ticket https://trac.sagemath.org/ticket/33288 + uses: sagemath/sagetrac-mirror/.github/workflows/docker.yml@u/mkoeppe/setuptools_ci_target + with: + # Sage distribution packages to build + targets: setuptools pyzmq + # Standard setting: Test the current beta release of Sage: + sage_repo: sagemath/sage + sage_ref: develop + upstream_artifact: upstream + sage_trac_git: https://github.com/sagemath/sagetrac-mirror.git + # Test with the branch from https://trac.sagemath.org/ticket/33288 + # This may provide hotfixes for the CI that have not been merged into + # the sage develop branch yet. + sage_trac_ticket: 33288 + # We prefix the image name with the SPKG name ("setuptools-") to avoid the error + # 'Package "sage-docker-..." is already associated with another repository.' + docker_push_repository: ghcr.io/${{ github.repository }}/setuptools- needs: [dist] - strategy: - fail-fast: false - max-parallel: 32 - matrix: - tox_system_factor: [ubuntu-trusty-toolchain-gcc_9, ubuntu-xenial-toolchain-gcc_9, ubuntu-bionic, ubuntu-focal, ubuntu-hirsute, ubuntu-impish, ubuntu-jammy, ubuntu-kinetic, debian-stretch, debian-buster, debian-bullseye, debian-bookworm, debian-sid, linuxmint-19, linuxmint-19.3, linuxmint-20.1, linuxmint-20.2, linuxmint-20.3, linuxmint-21, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, fedora-35, fedora-36, fedora-37, centos-7-devtoolset-gcc_11, centos-stream-8, gentoo-python3.9, gentoo-python3.10, archlinux-latest, opensuse-15.3, opensuse-tumbleweed, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386-devtoolset-gcc_11] - tox_packages_factor: [minimal, standard] - env: - TOX_ENV: docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - LOGS_ARTIFACT_NAME: logs-commit-${{ github.sha }}-tox-docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - DOCKER_TARGETS: configured with-targets with-targets-optional - steps: - - name: Check out SageMath - uses: actions/checkout@v2 - with: - repository: ${{ env.SAGE_REPO }} - ref: ${{ env.SAGE_REF }} - fetch-depth: 2000 - if: env.SAGE_REPO != '' - - name: Check out git-trac-command - uses: actions/checkout@v2 - with: - repository: sagemath/git-trac-command - path: git-trac-command - if: env.SAGE_TRAC_GIT != '' - - name: Check out SageMath from trac.sagemath.org - shell: bash {0} - run: | - git config --global user.email "ci-sage@example.com" - git config --global user.name "ci-sage workflow" - if [ ! -d .git ]; then git init; fi; git remote add trac ${{ env.SAGE_TRAC_GIT }} && x=1 && while [ $x -le 5 ]; do x=$(( $x + 1 )); sleep $(( $RANDOM % 60 + 1 )); if git-trac-command/git-trac fetch $SAGE_TICKET; then git merge FETCH_HEAD || echo "(ignored)"; exit 0; fi; sleep 40; done; exit 1 - if: env.SAGE_TRAC_GIT != '' - - uses: actions/download-artifact@v2 - with: - path: upstream - name: upstream - - name: Install test prerequisites - run: | - sudo DEBIAN_FRONTEND=noninteractive apt-get update - sudo DEBIAN_FRONTEND=noninteractive apt-get install tox python3-setuptools - - name: Update Sage packages from upstream artifact - run: | - (export PATH=$(pwd)/build/bin:$PATH; (cd upstream && bash -x update-pkgs.sh) && sed -i.bak '/upstream/d' .dockerignore && echo "/:toolchain:/i ADD upstream upstream" | sed -i.bak -f - build/bin/write-dockerfile.sh && git diff) - - name: Configure and build Sage distribution within a Docker container - run: | - set -o pipefail; EXTRA_DOCKER_BUILD_ARGS="--build-arg USE_MAKEFLAGS=\"-k V=0 SAGE_NUM_THREADS=3\"" tox -e $TOX_ENV -- $TARGETS 2>&1 | sed "/^configure: notice:/s|^|::warning file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;/^configure: warning:/s|^|::warning file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;/^configure: error:/s|^|::error file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;" - - name: Copy logs from the Docker image or build container - run: | - mkdir -p "artifacts/$LOGS_ARTIFACT_NAME" - cp -r .tox/$TOX_ENV/Dockerfile .tox/$TOX_ENV/log "artifacts/$LOGS_ARTIFACT_NAME" - if [ -f .tox/$TOX_ENV/Dockertags ]; then CONTAINERS=$(docker create $(tail -1 .tox/$TOX_ENV/Dockertags) /bin/bash || true); fi - if [ -n "$CONTAINERS" ]; then for CONTAINER in $CONTAINERS; do for ARTIFACT in /sage/logs; do docker cp $CONTAINER:$ARTIFACT artifacts/$LOGS_ARTIFACT_NAME && HAVE_LOG=1; done; if [ -n "$HAVE_LOG" ]; then break; fi; done; fi - if: always() - - uses: actions/upload-artifact@v2 - with: - path: artifacts - name: ${{ env.LOGS_ARTIFACT_NAME }} - if: always() - - name: Print out logs for immediate inspection - # and markup the output with GitHub Actions logging commands - run: | - .github/workflows/scan-logs.sh "artifacts/$LOGS_ARTIFACT_NAME" - if: always() diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b5d1a1e..7b4669a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,16 +16,20 @@ jobs: distutils: - local python: - - 3.7-dev - - 3.10-dev + - "3.7" + - "3.10" # disabled due to #3365 - # - 3.11-dev - - pypy-3.7 + # - "3.11" + # Workaround for actions/setup-python#508 + dev: + - -dev platform: - ubuntu-latest - macos-latest - windows-latest include: + - python: pypy3.9 + platform: ubuntu-latest - platform: ubuntu-latest python: "3.10" distutils: stdlib @@ -38,7 +42,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python }} + python-version: ${{ matrix.python }}${{ matrix.dev }} - uses: actions/cache@v3 id: cache with: @@ -149,7 +153,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: "3.11-dev" + python-version: 3.11-dev - name: Install tox run: | python -m pip install tox diff --git a/CHANGES.rst b/CHANGES.rst index 2c39cfd..089ec83 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,13 @@ +v65.4.1 +------- + + +Misc +^^^^ +* #3613: Fixed encoding errors in ``expand.StaticModule`` when system default encoding doesn't match expectations for source files. +* #3617: Merge with pypa/distutils@6852b20 including fix for pypa/distutils#181. + + v65.4.0 ------- diff --git a/docs/conf.py b/docs/conf.py index 2b60bf5..ecd7aac 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,15 @@ -extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker'] +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +extensions = [ + 'sphinx.ext.autodoc', + 'jaraco.packaging.sphinx', +] master_doc = "index" +# Link dates and other references in the changelog +extensions += ['rst.linker'] link_files = { '../CHANGES.rst': dict( using=dict( @@ -81,7 +89,7 @@ link_files = { ), } -# Be strict about any broken references: +# Be strict about any broken references nitpicky = True # Include Python intersphinx mapping to prevent failures @@ -91,6 +99,9 @@ intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), } +# Preserve authored syntax for defaults +autodoc_preserve_defaults = True + intersphinx_mapping.update({ 'pip': ('https://pip.pypa.io/en/latest', None), 'build': ('https://pypa-build.readthedocs.io/en/latest', None), diff --git a/setup.cfg b/setup.cfg index fe95dd6..436435a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = setuptools -version = 65.4.0 +version = 65.4.1 author = Python Packaging Authority author_email = distutils-sig@python.org description = Easily download, build, install, upgrade, and uninstall Python packages @@ -87,7 +87,7 @@ testing-integration = docs = # upstream - sphinx + sphinx >= 3.5 jaraco.packaging >= 9 rst.linker >= 1.9 jaraco.tidelift >= 1.4 diff --git a/setuptools/_distutils/dist.py b/setuptools/_distutils/dist.py index 1dc25fe..917cd94 100644 --- a/setuptools/_distutils/dist.py +++ b/setuptools/_distutils/dist.py @@ -334,7 +334,7 @@ Common commands: (see '--help-commands' for more) - a file named by an environment variable """ check_environ() - files = [str(path) for path in self._gen_paths() if path.is_file()] + files = [str(path) for path in self._gen_paths() if os.path.isfile(path)] if DEBUG: self.announce("using config files: %s" % ', '.join(files)) diff --git a/setuptools/_distutils/sysconfig.py b/setuptools/_distutils/sysconfig.py index 3dd8185..6a979f8 100644 --- a/setuptools/_distutils/sysconfig.py +++ b/setuptools/_distutils/sysconfig.py @@ -13,6 +13,7 @@ import os import re import sys import sysconfig +import pathlib from .errors import DistutilsPlatformError from . import py39compat @@ -40,14 +41,13 @@ else: project_base = os.getcwd() -# python_build: (Boolean) if true, we're either building Python or -# building an extension with an un-installed Python, so we use -# different (hard-wired) directories. def _is_python_source_dir(d): - for fn in ("Setup", "Setup.local"): - if os.path.isfile(os.path.join(d, "Modules", fn)): - return True - return False + """ + Return True if the target directory appears to point to an + un-installed Python. + """ + modules = pathlib.Path(d).joinpath('Modules') + return any(modules.joinpath(fn).is_file() for fn in ('Setup', 'Setup.local')) _sys_home = getattr(sys, '_home', None) diff --git a/setuptools/_distutils/tests/py37compat.py b/setuptools/_distutils/tests/py37compat.py new file mode 100644 index 0000000..e5d406a --- /dev/null +++ b/setuptools/_distutils/tests/py37compat.py @@ -0,0 +1,18 @@ +import os +import sys +import platform + + +def subprocess_args_compat(*args): + return list(map(os.fspath, args)) + + +def subprocess_args_passthrough(*args): + return list(args) + + +subprocess_args = ( + subprocess_args_compat + if platform.system() == "Windows" and sys.version_info < (3, 8) + else subprocess_args_passthrough +) diff --git a/setuptools/_distutils/tests/py38compat.py b/setuptools/_distutils/tests/py38compat.py index 35ddbb5..211d3a6 100644 --- a/setuptools/_distutils/tests/py38compat.py +++ b/setuptools/_distutils/tests/py38compat.py @@ -18,27 +18,19 @@ except (ModuleNotFoundError, ImportError): try: from test.support.os_helper import ( - change_cwd, rmtree, EnvironmentVarGuard, - TESTFN, unlink, skip_unless_symlink, temp_dir, - create_empty_file, - temp_cwd, ) except (ModuleNotFoundError, ImportError): from test.support import ( - change_cwd, rmtree, EnvironmentVarGuard, - TESTFN, unlink, skip_unless_symlink, temp_dir, - create_empty_file, - temp_cwd, ) diff --git a/setuptools/_distutils/tests/test_archive_util.py b/setuptools/_distutils/tests/test_archive_util.py index 72aa9d7..d0f5b73 100644 --- a/setuptools/_distutils/tests/test_archive_util.py +++ b/setuptools/_distutils/tests/test_archive_util.py @@ -9,6 +9,7 @@ import operator import pathlib import pytest +import path from distutils import archive_util from distutils.archive_util import ( @@ -23,7 +24,6 @@ from distutils.tests import support from test.support import patch from .unix_compat import require_unix_id, require_uid_0, grp, pwd, UID_0_SUPPORT -from .py38compat import change_cwd from .py38compat import check_warnings @@ -95,7 +95,7 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer): base_name = os.path.join(tmpdir2, target_name) # working with relative paths to avoid tar warnings - with change_cwd(tmpdir): + with path.Path(tmpdir): make_tarball(splitdrive(base_name)[1], 'dist', **kwargs) # check if the compressed tarball was created @@ -227,7 +227,7 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer): # creating something to tar tmpdir = self._create_files() base_name = os.path.join(self.mkdtemp(), 'archive') - with change_cwd(tmpdir): + with path.Path(tmpdir): make_zipfile(base_name, 'dist') # check if the compressed tarball was created @@ -253,7 +253,7 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer): # create something to tar and compress tmpdir = self._create_files() base_name = os.path.join(self.mkdtemp(), 'archive') - with change_cwd(tmpdir): + with path.Path(tmpdir): make_zipfile(base_name, 'dist') tarball = base_name + '.zip' diff --git a/setuptools/_distutils/tests/test_build_ext.py b/setuptools/_distutils/tests/test_build_ext.py index e60814f..cf6e989 100644 --- a/setuptools/_distutils/tests/test_build_ext.py +++ b/setuptools/_distutils/tests/test_build_ext.py @@ -8,6 +8,10 @@ import platform import tempfile import importlib import shutil +import re + +import path +import pytest from distutils.core import Distribution from distutils.command.build_ext import build_ext @@ -27,10 +31,7 @@ from distutils.errors import ( ) from test import support -from . import py38compat as os_helper from . import py38compat as import_helper -import pytest -import re @pytest.fixture() @@ -47,7 +48,7 @@ def user_site_dir(request): # bpo-30132: On Windows, a .pdb file may be created in the current # working directory. Create a temporary working directory to cleanup # everything at the end of the test. - with os_helper.change_cwd(self.tmp_dir): + with path.Path(self.tmp_dir): yield site.USER_BASE = orig_user_base diff --git a/setuptools/_distutils/tests/test_core.py b/setuptools/_distutils/tests/test_core.py index 86b0040..5fe7e95 100644 --- a/setuptools/_distutils/tests/test_core.py +++ b/setuptools/_distutils/tests/test_core.py @@ -8,7 +8,6 @@ from test.support import captured_stdout import pytest -from . import py38compat as os_helper from distutils.dist import Distribution # setup script that uses __file__ @@ -61,65 +60,63 @@ def save_stdout(monkeypatch): monkeypatch.setattr(sys, 'stdout', sys.stdout) +@pytest.fixture +def temp_file(tmp_path): + return tmp_path / 'file' + + @pytest.mark.usefixtures('save_env') @pytest.mark.usefixtures('save_argv') -@pytest.mark.usefixtures('cleanup_testfn') class TestCore: - def write_setup(self, text, path=os_helper.TESTFN): - f = open(path, "w") - try: - f.write(text) - finally: - f.close() - return path - - def test_run_setup_provides_file(self): + def test_run_setup_provides_file(self, temp_file): # Make sure the script can use __file__; if that's missing, the test # setup.py script will raise NameError. - distutils.core.run_setup(self.write_setup(setup_using___file__)) + temp_file.write_text(setup_using___file__) + distutils.core.run_setup(temp_file) - def test_run_setup_preserves_sys_argv(self): + def test_run_setup_preserves_sys_argv(self, temp_file): # Make sure run_setup does not clobber sys.argv argv_copy = sys.argv.copy() - distutils.core.run_setup(self.write_setup(setup_does_nothing)) + temp_file.write_text(setup_does_nothing) + distutils.core.run_setup(temp_file) assert sys.argv == argv_copy - def test_run_setup_defines_subclass(self): + def test_run_setup_defines_subclass(self, temp_file): # Make sure the script can use __file__; if that's missing, the test # setup.py script will raise NameError. - dist = distutils.core.run_setup(self.write_setup(setup_defines_subclass)) + temp_file.write_text(setup_defines_subclass) + dist = distutils.core.run_setup(temp_file) install = dist.get_command_obj('install') assert 'cmd' in install.sub_commands - def test_run_setup_uses_current_dir(self): - # This tests that the setup script is run with the current directory - # as its own current directory; this was temporarily broken by a - # previous patch when TESTFN did not use the current directory. + def test_run_setup_uses_current_dir(self, tmp_path): + """ + Test that the setup script is run with the current directory + as its own current directory. + """ sys.stdout = io.StringIO() cwd = os.getcwd() # Create a directory and write the setup.py file there: - os.mkdir(os_helper.TESTFN) - setup_py = os.path.join(os_helper.TESTFN, "setup.py") - distutils.core.run_setup(self.write_setup(setup_prints_cwd, path=setup_py)) + setup_py = tmp_path / 'setup.py' + setup_py.write_text(setup_prints_cwd) + distutils.core.run_setup(setup_py) output = sys.stdout.getvalue() if output.endswith("\n"): output = output[:-1] assert cwd == output - def test_run_setup_within_if_main(self): - dist = distutils.core.run_setup( - self.write_setup(setup_within_if_main), stop_after="config" - ) + def test_run_setup_within_if_main(self, temp_file): + temp_file.write_text(setup_within_if_main) + dist = distutils.core.run_setup(temp_file, stop_after="config") assert isinstance(dist, Distribution) assert dist.get_name() == "setup_within_if_main" - def test_run_commands(self): + def test_run_commands(self, temp_file): sys.argv = ['setup.py', 'build'] - dist = distutils.core.run_setup( - self.write_setup(setup_within_if_main), stop_after="commandline" - ) + temp_file.write_text(setup_within_if_main) + dist = distutils.core.run_setup(temp_file, stop_after="commandline") assert 'build' not in dist.have_run distutils.core.run_commands(dist) assert 'build' in dist.have_run diff --git a/setuptools/_distutils/tests/test_dist.py b/setuptools/_distutils/tests/test_dist.py index a943832..52e0b3c 100644 --- a/setuptools/_distutils/tests/test_dist.py +++ b/setuptools/_distutils/tests/test_dist.py @@ -14,7 +14,6 @@ from distutils.dist import Distribution, fix_help_options from distutils.cmd import Command from test.support import captured_stdout, captured_stderr -from .py38compat import TESTFN from distutils.tests import support from distutils import log @@ -95,15 +94,15 @@ class TestDistributionBehavior( 'distutils' not in Distribution.parse_config_files.__module__, reason='Cannot test when virtualenv has monkey-patched Distribution', ) - def test_venv_install_options(self, request): + def test_venv_install_options(self, tmp_path): sys.argv.append("install") - request.addfinalizer(functools.partial(os.unlink, TESTFN)) + file = str(tmp_path / 'file') fakepath = '/somedir' jaraco.path.build( { - TESTFN: f""" + file: f""" [install] install-base = {fakepath} install-platbase = {fakepath} @@ -124,9 +123,9 @@ class TestDistributionBehavior( # Base case: Not in a Virtual Environment with mock.patch.multiple(sys, prefix='/a', base_prefix='/a'): - d = self.create_distribution([TESTFN]) + d = self.create_distribution([file]) - option_tuple = (TESTFN, fakepath) + option_tuple = (file, fakepath) result_dict = { 'install_base': option_tuple, @@ -153,35 +152,35 @@ class TestDistributionBehavior( # Test case: In a Virtual Environment with mock.patch.multiple(sys, prefix='/a', base_prefix='/b'): - d = self.create_distribution([TESTFN]) + d = self.create_distribution([file]) for key in result_dict.keys(): assert key not in d.command_options.get('install', {}) - def test_command_packages_configfile(self, request, clear_argv): + def test_command_packages_configfile(self, tmp_path, clear_argv): sys.argv.append("build") - request.addfinalizer(functools.partial(os.unlink, TESTFN)) + file = str(tmp_path / "file") jaraco.path.build( { - TESTFN: """ + file: """ [global] command_packages = foo.bar, splat """, } ) - d = self.create_distribution([TESTFN]) + d = self.create_distribution([file]) assert d.get_command_packages() == ["distutils.command", "foo.bar", "splat"] # ensure command line overrides config: sys.argv[1:] = ["--command-packages", "spork", "build"] - d = self.create_distribution([TESTFN]) + d = self.create_distribution([file]) assert d.get_command_packages() == ["distutils.command", "spork"] # Setting --command-packages to '' should cause the default to # be used even if a config file specified something else: sys.argv[1:] = ["--command-packages", "", "build"] - d = self.create_distribution([TESTFN]) + d = self.create_distribution([file]) assert d.get_command_packages() == ["distutils.command"] def test_empty_options(self, request): @@ -259,6 +258,18 @@ class TestDistributionBehavior( # make sure --no-user-cfg disables the user cfg file assert len(all_files) - 1 == len(files) + @pytest.mark.skipif( + 'platform.system() == "Windows"', + reason='Windows does not honor chmod 000', + ) + def test_find_config_files_permission_error(self, fake_home): + """ + Finding config files should not fail when directory is inaccessible. + """ + fake_home.joinpath(pydistutils_cfg).write_text('') + fake_home.chmod(0o000) + Distribution().find_config_files() + @pytest.mark.usefixtures('save_env') @pytest.mark.usefixtures('save_argv') diff --git a/setuptools/_distutils/tests/test_filelist.py b/setuptools/_distutils/tests/test_filelist.py index 2607182..7ff9d3e 100644 --- a/setuptools/_distutils/tests/test_filelist.py +++ b/setuptools/_distutils/tests/test_filelist.py @@ -8,10 +8,12 @@ from distutils.filelist import glob_to_re, translate_pattern, FileList from distutils import filelist from test.support import captured_stdout -from distutils.tests import support -from . import py38compat as os_helper import pytest +import jaraco.path + +from distutils.tests import support +from . import py38compat as os_helper MANIFEST_IN = """\ @@ -303,44 +305,35 @@ class TestFileList(support.LoggingSilencer): class TestFindAll: @os_helper.skip_unless_symlink - def test_missing_symlink(self): - with os_helper.temp_cwd(): - os.symlink('foo', 'bar') - assert filelist.findall() == [] + def test_missing_symlink(self, temp_cwd): + os.symlink('foo', 'bar') + assert filelist.findall() == [] - def test_basic_discovery(self): + def test_basic_discovery(self, temp_cwd): """ When findall is called with no parameters or with '.' as the parameter, the dot should be omitted from the results. """ - with os_helper.temp_cwd(): - os.mkdir('foo') - file1 = os.path.join('foo', 'file1.txt') - os_helper.create_empty_file(file1) - os.mkdir('bar') - file2 = os.path.join('bar', 'file2.txt') - os_helper.create_empty_file(file2) - expected = [file2, file1] - assert sorted(filelist.findall()) == expected - - def test_non_local_discovery(self): + jaraco.path.build({'foo': {'file1.txt': ''}, 'bar': {'file2.txt': ''}}) + file1 = os.path.join('foo', 'file1.txt') + file2 = os.path.join('bar', 'file2.txt') + expected = [file2, file1] + assert sorted(filelist.findall()) == expected + + def test_non_local_discovery(self, tmp_path): """ When findall is called with another path, the full path name should be returned. """ - with os_helper.temp_dir() as temp_dir: - file1 = os.path.join(temp_dir, 'file1.txt') - os_helper.create_empty_file(file1) - expected = [file1] - assert filelist.findall(temp_dir) == expected + filename = tmp_path / 'file1.txt' + filename.write_text('') + expected = [str(filename)] + assert filelist.findall(tmp_path) == expected @os_helper.skip_unless_symlink - def test_symlink_loop(self): - with os_helper.temp_dir() as temp_dir: - link = os.path.join(temp_dir, 'link-to-parent') - content = os.path.join(temp_dir, 'somefile') - os_helper.create_empty_file(content) - os.symlink('.', link) - files = filelist.findall(temp_dir) - assert len(files) == 1 + def test_symlink_loop(self, tmp_path): + tmp_path.joinpath('link-to-parent').symlink_to('.') + tmp_path.joinpath('somefile').write_text('') + files = filelist.findall(tmp_path) + assert len(files) == 1 diff --git a/setuptools/_distutils/tests/test_spawn.py b/setuptools/_distutils/tests/test_spawn.py index d2a898e..5da4997 100644 --- a/setuptools/_distutils/tests/test_spawn.py +++ b/setuptools/_distutils/tests/test_spawn.py @@ -6,6 +6,8 @@ import unittest.mock as mock from test.support import unix_shell +import path + from . import py38compat as os_helper from distutils.spawn import find_executable @@ -44,83 +46,82 @@ class TestSpawn(support.TempdirManager, support.LoggingSilencer): os.chmod(exe, 0o777) spawn([exe]) # should work without any error - def test_find_executable(self): - with os_helper.temp_dir() as tmp_dir: - # use TESTFN to get a pseudo-unique filename - program_noeext = os_helper.TESTFN - # Give the temporary program an ".exe" suffix for all. - # It's needed on Windows and not harmful on other platforms. - program = program_noeext + ".exe" - - filename = os.path.join(tmp_dir, program) - with open(filename, "wb"): - pass - os.chmod(filename, stat.S_IXUSR) - - # test path parameter - rv = find_executable(program, path=tmp_dir) + def test_find_executable(self, tmp_path): + program_noeext = 'program' + # Give the temporary program an ".exe" suffix for all. + # It's needed on Windows and not harmful on other platforms. + program = program_noeext + ".exe" + + program_path = tmp_path / program + program_path.write_text("") + program_path.chmod(stat.S_IXUSR) + filename = str(program_path) + tmp_dir = path.Path(tmp_path) + + # test path parameter + rv = find_executable(program, path=tmp_dir) + assert rv == filename + + if sys.platform == 'win32': + # test without ".exe" extension + rv = find_executable(program_noeext, path=tmp_dir) assert rv == filename - if sys.platform == 'win32': - # test without ".exe" extension - rv = find_executable(program_noeext, path=tmp_dir) - assert rv == filename - - # test find in the current directory - with os_helper.change_cwd(tmp_dir): + # test find in the current directory + with tmp_dir: + rv = find_executable(program) + assert rv == program + + # test non-existent program + dont_exist_program = "dontexist_" + program + rv = find_executable(dont_exist_program, path=tmp_dir) + assert rv is None + + # PATH='': no match, except in the current directory + with os_helper.EnvironmentVarGuard() as env: + env['PATH'] = '' + with mock.patch( + 'distutils.spawn.os.confstr', return_value=tmp_dir, create=True + ), mock.patch('distutils.spawn.os.defpath', tmp_dir): rv = find_executable(program) - assert rv == program - - # test non-existent program - dont_exist_program = "dontexist_" + program - rv = find_executable(dont_exist_program, path=tmp_dir) - assert rv is None - - # PATH='': no match, except in the current directory - with os_helper.EnvironmentVarGuard() as env: - env['PATH'] = '' - with mock.patch( - 'distutils.spawn.os.confstr', return_value=tmp_dir, create=True - ), mock.patch('distutils.spawn.os.defpath', tmp_dir): - rv = find_executable(program) - assert rv is None - - # look in current directory - with os_helper.change_cwd(tmp_dir): - rv = find_executable(program) - assert rv == program - - # PATH=':': explicitly looks in the current directory - with os_helper.EnvironmentVarGuard() as env: - env['PATH'] = os.pathsep - with mock.patch( - 'distutils.spawn.os.confstr', return_value='', create=True - ), mock.patch('distutils.spawn.os.defpath', ''): + assert rv is None + + # look in current directory + with tmp_dir: rv = find_executable(program) - assert rv is None + assert rv == program + + # PATH=':': explicitly looks in the current directory + with os_helper.EnvironmentVarGuard() as env: + env['PATH'] = os.pathsep + with mock.patch( + 'distutils.spawn.os.confstr', return_value='', create=True + ), mock.patch('distutils.spawn.os.defpath', ''): + rv = find_executable(program) + assert rv is None - # look in current directory - with os_helper.change_cwd(tmp_dir): - rv = find_executable(program) - assert rv == program + # look in current directory + with tmp_dir: + rv = find_executable(program) + assert rv == program - # missing PATH: test os.confstr("CS_PATH") and os.defpath - with os_helper.EnvironmentVarGuard() as env: - env.pop('PATH', None) + # missing PATH: test os.confstr("CS_PATH") and os.defpath + with os_helper.EnvironmentVarGuard() as env: + env.pop('PATH', None) - # without confstr - with mock.patch( - 'distutils.spawn.os.confstr', side_effect=ValueError, create=True - ), mock.patch('distutils.spawn.os.defpath', tmp_dir): - rv = find_executable(program) - assert rv == filename + # without confstr + with mock.patch( + 'distutils.spawn.os.confstr', side_effect=ValueError, create=True + ), mock.patch('distutils.spawn.os.defpath', tmp_dir): + rv = find_executable(program) + assert rv == filename - # with confstr - with mock.patch( - 'distutils.spawn.os.confstr', return_value=tmp_dir, create=True - ), mock.patch('distutils.spawn.os.defpath', ''): - rv = find_executable(program) - assert rv == filename + # with confstr + with mock.patch( + 'distutils.spawn.os.confstr', return_value=tmp_dir, create=True + ), mock.patch('distutils.spawn.os.defpath', ''): + rv = find_executable(program) + assert rv == filename def test_spawn_missing_exe(self): with pytest.raises(DistutilsExecError) as ctx: diff --git a/setuptools/_distutils/tests/test_sysconfig.py b/setuptools/_distutils/tests/test_sysconfig.py index f175983..66f92c2 100644 --- a/setuptools/_distutils/tests/test_sysconfig.py +++ b/setuptools/_distutils/tests/test_sysconfig.py @@ -1,13 +1,14 @@ """Tests for distutils.sysconfig.""" import contextlib import os -import shutil import subprocess import sys -import textwrap +import pathlib import pytest import jaraco.envs +import path +from jaraco.text import trim import distutils from distutils import sysconfig @@ -15,33 +16,23 @@ from distutils.ccompiler import get_default_compiler # noqa: F401 from distutils.unixccompiler import UnixCCompiler from test.support import swap_item -from .py38compat import TESTFN +from . import py37compat @pytest.mark.usefixtures('save_env') -@pytest.mark.usefixtures('cleanup_testfn') class TestSysconfig: - def cleanup_testfn(self): - if os.path.isfile(TESTFN): - os.remove(TESTFN) - elif os.path.isdir(TESTFN): - shutil.rmtree(TESTFN) - def test_get_config_h_filename(self): config_h = sysconfig.get_config_h_filename() - assert os.path.isfile(config_h), config_h + assert os.path.isfile(config_h) @pytest.mark.skipif("platform.system() == 'Windows'") @pytest.mark.skipif("sys.implementation.name != 'cpython'") def test_get_makefile_filename(self): makefile = sysconfig.get_makefile_filename() - assert os.path.isfile(makefile), makefile + assert os.path.isfile(makefile) - def test_get_python_lib(self): - # XXX doesn't work on Linux when Python was never installed before - # self.assertTrue(os.path.isdir(lib_dir), lib_dir) - # test for pythonxx.lib? - assert sysconfig.get_python_lib() != sysconfig.get_python_lib(prefix=TESTFN) + def test_get_python_lib(self, tmp_path): + assert sysconfig.get_python_lib() != sysconfig.get_python_lib(prefix=tmp_path) def test_get_config_vars(self): cvars = sysconfig.get_config_vars() @@ -49,33 +40,39 @@ class TestSysconfig: assert cvars @pytest.mark.skipif('sysconfig.IS_PYPY') - @pytest.mark.xfail(reason="broken") - def test_srcdir(self): - # See Issues #15322, #15364. - srcdir = sysconfig.get_config_var('srcdir') + @pytest.mark.skipif('sysconfig.python_build') + @pytest.mark.xfail('platform.system() == "Windows"') + def test_srcdir_simple(self): + # See #15364. + srcdir = pathlib.Path(sysconfig.get_config_var('srcdir')) - assert os.path.isabs(srcdir), srcdir - assert os.path.isdir(srcdir), srcdir + assert srcdir.absolute() + assert srcdir.is_dir() - if sysconfig.python_build: - # The python executable has not been installed so srcdir - # should be a full source checkout. - Python_h = os.path.join(srcdir, 'Include', 'Python.h') - assert os.path.exists(Python_h), Python_h - assert sysconfig._is_python_source_dir(srcdir) - elif os.name == 'posix': - assert os.path.dirname(sysconfig.get_makefile_filename()) == srcdir + makefile = pathlib.Path(sysconfig.get_makefile_filename()) + assert makefile.parent.samefile(srcdir) + + @pytest.mark.skipif('sysconfig.IS_PYPY') + @pytest.mark.skipif('not sysconfig.python_build') + def test_srcdir_python_build(self): + # See #15364. + srcdir = pathlib.Path(sysconfig.get_config_var('srcdir')) + + # The python executable has not been installed so srcdir + # should be a full source checkout. + Python_h = srcdir.joinpath('Include', 'Python.h') + assert Python_h.is_file() + assert sysconfig._is_python_source_dir(srcdir) + assert sysconfig._is_python_source_dir(str(srcdir)) def test_srcdir_independent_of_cwd(self): - # srcdir should be independent of the current working directory - # See Issues #15322, #15364. + """ + srcdir should be independent of the current working directory + """ + # See #15364. srcdir = sysconfig.get_config_var('srcdir') - cwd = os.getcwd() - try: - os.chdir('..') + with path.Path('..'): srcdir2 = sysconfig.get_config_var('srcdir') - finally: - os.chdir(cwd) assert srcdir == srcdir2 def customize_compiler(self): @@ -169,26 +166,32 @@ class TestSysconfig: assert comp.shared_lib_extension == 'sc_shutil_suffix' assert 'ranlib' not in comp.exes - def test_parse_makefile_base(self): - self.makefile = TESTFN - fd = open(self.makefile, 'w') - try: - fd.write(r"CONFIG_ARGS= '--arg1=optarg1' 'ENV=LIB'" '\n') - fd.write('VAR=$OTHER\nOTHER=foo') - finally: - fd.close() - d = sysconfig.parse_makefile(self.makefile) + def test_parse_makefile_base(self, tmp_path): + makefile = tmp_path / 'Makefile' + makefile.write_text( + trim( + """ + CONFIG_ARGS= '--arg1=optarg1' 'ENV=LIB' + VAR=$OTHER + OTHER=foo + """ + ) + ) + d = sysconfig.parse_makefile(makefile) assert d == {'CONFIG_ARGS': "'--arg1=optarg1' 'ENV=LIB'", 'OTHER': 'foo'} - def test_parse_makefile_literal_dollar(self): - self.makefile = TESTFN - fd = open(self.makefile, 'w') - try: - fd.write(r"CONFIG_ARGS= '--arg1=optarg1' 'ENV=\$$LIB'" '\n') - fd.write('VAR=$OTHER\nOTHER=foo') - finally: - fd.close() - d = sysconfig.parse_makefile(self.makefile) + def test_parse_makefile_literal_dollar(self, tmp_path): + makefile = tmp_path / 'Makefile' + makefile.write_text( + trim( + """ + CONFIG_ARGS= '--arg1=optarg1' 'ENV=\\$$LIB' + VAR=$OTHER + OTHER=foo + """ + ) + ) + d = sysconfig.parse_makefile(makefile) assert d == {'CONFIG_ARGS': r"'--arg1=optarg1' 'ENV=\$LIB'", 'OTHER': 'foo'} def test_sysconfig_module(self): @@ -231,24 +234,24 @@ class TestSysconfig: with pytest.warns(DeprecationWarning): sysconfig.get_config_var('SO') - def test_customize_compiler_before_get_config_vars(self): + def test_customize_compiler_before_get_config_vars(self, tmp_path): # Issue #21923: test that a Distribution compiler # instance can be called without an explicit call to # get_config_vars(). - with open(TESTFN, 'w') as f: - f.writelines( - textwrap.dedent( - '''\ + file = tmp_path / 'file' + file.write_text( + trim( + """ from distutils.core import Distribution config = Distribution().get_command_obj('config') # try_compile may pass or it may fail if no compiler # is found but it should not raise an exception. rc = config.try_compile('int x;') - ''' - ) + """ ) + ) p = subprocess.Popen( - [str(sys.executable), TESTFN], + py37compat.subprocess_args(sys.executable, file), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, @@ -278,11 +281,11 @@ class TestSysconfig: '\\PCbuild\\'.casefold() not in sys.executable.casefold(), reason='Need sys.executable to be in a source tree', ) - def test_win_build_venv_from_source_tree(self): + def test_win_build_venv_from_source_tree(self, tmp_path): """Ensure distutils.sysconfig detects venvs from source tree builds.""" env = jaraco.envs.VEnv() env.create_opts = env.clean_opts - env.root = TESTFN + env.root = tmp_path env.ensure_env() cmd = [ env.exe(), diff --git a/setuptools/config/expand.py b/setuptools/config/expand.py index 384504d..38eb3db 100644 --- a/setuptools/config/expand.py +++ b/setuptools/config/expand.py @@ -21,6 +21,7 @@ import ast import importlib import io import os +import pathlib import sys import warnings from glob import iglob @@ -62,9 +63,7 @@ class StaticModule: """Proxy to a module object that avoids executing arbitrary code.""" def __init__(self, name: str, spec: ModuleSpec): - with open(spec.origin) as strm: # type: ignore - src = strm.read() - module = ast.parse(src) + module = ast.parse(pathlib.Path(spec.origin).read_bytes()) vars(self).update(locals()) del self.self diff --git a/setuptools/tests/config/test_expand.py b/setuptools/tests/config/test_expand.py index 523779a..39f3b7c 100644 --- a/setuptools/tests/config/test_expand.py +++ b/setuptools/tests/config/test_expand.py @@ -60,6 +60,20 @@ def test_read_files(tmp_path, monkeypatch): class TestReadAttr: + @pytest.mark.parametrize( + "example", + [ + # No cookie means UTF-8: + b"__version__ = '\xc3\xa9'\nraise SystemExit(1)\n", + # If a cookie is present, honor it: + b"# -*- coding: utf-8 -*-\n__version__ = '\xc3\xa9'\nraise SystemExit(1)\n", + b"# -*- coding: latin1 -*-\n__version__ = '\xe9'\nraise SystemExit(1)\n", + ] + ) + def test_read_attr_encoding_cookie(self, example, tmp_path): + (tmp_path / "mod.py").write_bytes(example) + assert expand.read_attr('mod.__version__', root_dir=tmp_path) == 'é' + def test_read_attr(self, tmp_path, monkeypatch): files = { "pkg/__init__.py": "",