[bumpversion]
-current_version = 65.6.0
+current_version = 65.6.1
commit = True
tag = True
runs-on: ${{ matrix.platform }}
timeout-minutes: 75
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Install Cygwin with Python
- uses: cygwin/cygwin-install-action@v1
+ uses: cygwin/cygwin-install-action@v2
with:
platform: x86_64
packages: >-
runs-on: ubuntu-latest
timeout-minutes: 75
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Install OS-level dependencies
run: |
sudo apt-get update
sudo apt-get install build-essential gfortran libopenblas-dev
- name: Setup Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
# Use a release that is not very new but still have a long life:
python-version: "3.8"
+v65.6.1
+-------
+
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #3689: Document that ``distutils.cfg`` might be ignored unless
+ ``SETUPTOOLS_USE_DISTUTILS=stdlib``.
+
+Misc
+^^^^
+* #3678: Improve clib builds reproducibility by sorting sources -- by :user:`danigm`
+* #3684: Improved exception/traceback when invalid entry-points are specified.
+* #3690: Fixed logging errors: 'underlying buffer has been detached' (issue #1631).
+* #3693: Merge pypa/distutils@3e9d47e with compatibility fix for distutils.log.Log.
+* #3704: Fix temporary build directories interference with auto-discovery.
+
+
v65.6.0
-------
pyproject.toml
setup.cfg
meowpkg/
- __init__.py
- module.py
+ __init__.py
+ module.py
The ``pyproject.toml`` file specifies the build system (i.e. what is
being used to package your scripts and install from source). To use it with
and :pypi:`setuptools-svn`), or by correctly setting up :ref:`MANIFEST.in
<manifest>`.
- The generated ``.tar.gz`` and ``.whl`` files are compressed archives that
+ The generated ``.tar.gz`` and ``.whl`` files are compressed archives that
can be inspected as follows:
On POSIX systems, this can be done with ``tar -tf dist/*.tar.gz``
and ``unzip -l dist/*.whl``.
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
extensions = [
'sphinx.ext.autodoc',
SETUPTOOLS_USE_DISTUTILS=stdlib
+.. warning::
+ Please note that this also affects how ``distutils.cfg`` files inside stdlib's ``distutils``
+ package directory are processed.
+ Unless ``SETUPTOOLS_USE_DISTUTILS=stdlib``, they will have no effect on the build process.
+
+ You can still use a global user config file, ``~/.pydistutils.cfg`` (POSIX) or ``%USERPROFILE%/pydistutils.cfg`` (Windows),
+ or use the environment variable :doc:`DIST_EXTRA_CONFIG <deprecated/distutils/configfile>` to point to another
+ supplementary configuration file.
+
Prefer Setuptools
-----------------
.. % \subsection{\protect\command{bdist}}
.. % \subsection{\protect\command{bdist\_dumb}}
.. % \subsection{\protect\command{bdist\_rpm}}
-
-
.. [#] This ideal probably won't be achieved until auto-configuration is fully
supported by the Distutils.
-
:command:`upload` can upload it to PyPI. The *filename* in the pair contains no
path information, only the name of the file itself. In dry-run mode, pairs
should still be added to represent what would have been created.
-
-
indexes (such as PyPI_) consider that distribution packages are always
installed as a directory.
It is however still possible to load packages from zip files added to
-:obj:`sys.path`, thanks to the :mod:`zipimport` module
+:obj:`sys.path`, thanks to the :mod:`zipimport` module
and the :mod:`importlib` machinery provided by Python standard library.
When working with modules loaded from a zip file, it is important to keep in
You can enter this "development mode" by performing an :doc:`editable installation
<pip:topics/local-project-installs>` inside of a :term:`virtual environment`,
-using :doc:`pip's <pip:cli/pip_install>` ``-e/--editable`` flag, as shown bellow:
+using :doc:`pip's <pip:cli/pip_install>` ``-e/--editable`` flag, as shown below:
.. code-block:: bash
sudo apt install python3-venv
- Alternatively, you can also try installing :pypi:`virtualenᴠ`.
+ Alternatively, you can also try installing :pypi:`virtualenv`.
More information is available on the Python Packaging User Guide on
:doc:`PyPUG:guides/installing-using-pip-and-virtual-environments`.
Unfortunately these expectations are in conflict with each other.
To solve this problem ``setuptools`` allows developers to choose a more
*"strict"* mode for the editable installation. This can be done by passing
-a special *configuration setting* via :pypi:`pip`, as indicated bellow:
+a special *configuration setting* via :pypi:`pip`, as indicated below:
.. code-block:: bash
.. tab:: setup.py
.. code-block:: python
-
+
from setuptools import setup
setup(
:doc:`setup.cfg <declarative_config>`, and keep the ``setup.py`` minimal
with only the dynamic parts (or even omit it completely if applicable).
- See `Why you shouldn't invoke setup.py directly`_ for more background.
+ See `Why you shouldn't invoke setup.py directly`_ for more background.
.. _Why you shouldn't invoke setup.py directly: https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html
}
BOOL control_handler(DWORD control_type) {
- /*
+ /*
* distribute-issue207
* control event handler callback function
*/
ZeroMemory(&p_info, sizeof(p_info));
ZeroMemory(&s_info, sizeof(s_info));
s_info.cb = sizeof(STARTUPINFO);
- // set-up control handler callback funciotn
+ // set-up control handler callback function
SetConsoleCtrlHandler((PHANDLER_ROUTINE) control_handler, TRUE);
if (!CreateProcessA(NULL, commandline, NULL, NULL, TRUE, 0, NULL, NULL, &s_info, &p_info)) {
fprintf(stderr, "failed to create process.\n");
return 0;
- }
+ }
child_pid = p_info.dwProcessId;
// wait for Python to exit
WaitForSingleObject(p_info.hProcess, INFINITE);
*/
int len,counter;
char* cmdline;
-
+
len=strlen(executable)+2;
for (counter=1; counter<argc; counter++) {
len+=strlen(args[counter])+1;
int main(int argc, char** argv) {
return run(argc, argv, GUI);
}
-
[metadata]
name = setuptools
-version = 65.6.0
+version = 65.6.1
author = Python Packaging Authority
author_email = distutils-sig@python.org
description = Easily download, build, install, upgrade, and uninstall Python packages
"""
import logging
+import warnings
from ._log import log as _global_log
set_threshold(logging.INFO)
elif v >= 2:
set_threshold(logging.DEBUG)
+
+
+class Log(logging.Logger):
+ """distutils.log.Log is deprecated, please use an alternative from `logging`."""
+
+ def __init__(self, threshold=WARN):
+ warnings.warn(Log.__doc__) # avoid DeprecationWarning to ensure warn is shown
+ super().__init__(__name__, level=threshold)
+
+ @property
+ def threshold(self):
+ return self.level
+
+ @threshold.setter
+ def threshold(self, level):
+ self.setLevel(level)
+
+ warn = logging.Logger.warning
import operator
import itertools
+from .errors import OptionError
from .extern.jaraco.text import yield_lines
from .extern.jaraco.functools import pass_none
from ._importlib import metadata
Exercise one of the dynamic properties to trigger
the pattern match.
"""
- ep.extras
+ try:
+ ep.extras
+ except AttributeError as ex:
+ msg = (
+ f"Problems to parse {ep}.\nPlease ensure entry-point follows the spec: "
+ "https://packaging.python.org/en/latest/specifications/entry-points/"
+ )
+ raise OptionError(msg) from ex
def load_group(value, group):
# Build in a temporary directory, then copy to the target.
os.makedirs(result_directory, exist_ok=True)
- with tempfile.TemporaryDirectory(dir=result_directory) as tmp_dist_dir:
+ temp_opts = {"prefix": ".tmp-", "dir": result_directory}
+ with tempfile.TemporaryDirectory(**temp_opts) as tmp_dist_dir:
sys.argv = [
*sys.argv[:1],
*self._global_args(config_settings),
"in 'libraries' option (library '%s'), "
"'sources' must be present and must be "
"a list of source filenames" % lib_name)
- sources = list(sources)
+ sources = sorted(list(sources))
log.info("building '%s' library", lib_name)
-# -*- coding: utf-8 -*-
"""upload_docs
Implements a Distutils 'upload_docs' subcommand (upload documentation to
-# -*- coding: utf-8 -*-
__all__ = ['Distribution']
import io
# Print metadata in UTF-8 no matter the platform
encoding = sys.stdout.encoding
- errors = sys.stdout.errors
- newline = sys.platform != 'win32' and '\n' or None
- line_buffering = sys.stdout.line_buffering
-
- sys.stdout = io.TextIOWrapper(
- sys.stdout.detach(), 'utf-8', errors, newline, line_buffering
- )
+ sys.stdout.reconfigure(encoding='utf-8')
try:
return _Distribution.handle_display_options(self, option_order)
finally:
- sys.stdout = io.TextIOWrapper(
- sys.stdout.detach(), encoding, errors, newline, line_buffering
- )
+ sys.stdout.reconfigure(encoding=encoding)
def run_command(self, command):
self.set_defaults()
-# -*- coding: utf-8 -*-
-
-result = 'passed'
+result = 'passed'
import pytest
+import random
from distutils.errors import DistutilsSetupError
from setuptools.command.build_clib import build_clib
from setuptools.dist import Distribution
cmd.build_libraries(libs)
assert cmd.compiler.compile.call_count == 1
assert cmd.compiler.create_static_lib.call_count == 1
+
+ @mock.patch(
+ 'setuptools.command.build_clib.newer_pairwise_group')
+ def test_build_libraries_reproducible(self, mock_newer):
+ dist = Distribution()
+ cmd = build_clib(dist)
+
+ # with that out of the way, let's see if the crude dependency
+ # system works
+ cmd.compiler = mock.MagicMock(spec=cmd.compiler)
+ mock_newer.return_value = ([], [])
+
+ original_sources = ['a-example.c', 'example.c']
+ sources = original_sources
+
+ obj_deps = {'': ('global.h',), 'example.c': ('example.h',)}
+ libs = [('example', {'sources': sources, 'obj_deps': obj_deps})]
+
+ cmd.build_libraries(libs)
+ computed_call_args = mock_newer.call_args[0]
+
+ while sources == original_sources:
+ sources = random.sample(original_sources, len(original_sources))
+ libs = [('example', {'sources': sources, 'obj_deps': obj_deps})]
+
+ cmd.build_libraries(libs)
+ assert computed_call_args == mock_newer.call_args[0]
self.pool = futures.ProcessPoolExecutor(max_workers=1)
def __getattr__(self, name):
- """Handles aribrary function invocations on the build backend."""
+ """Handles arbitrary function invocations on the build backend."""
def method(*args, **kw):
root = os.path.abspath(self.cwd)
self.backend_obj) = self.backend_name.partition(':')
def __call__(self, name, *args, **kw):
- """Handles aribrary function invocations on the build backend."""
+ """Handles arbitrary function invocations on the build backend."""
os.chdir(self.cwd)
os.environ.update(self.env)
mod = importlib.import_module(self.backend_name)
with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
_get_dist(tmp_path, {})
+ def test_py_modules_when_wheel_dir_is_cwd(self, tmp_path):
+ """Regression for issue 3692"""
+ from setuptools import build_meta
+
+ pyproject = '[project]\nname = "test"\nversion = "1"'
+ (tmp_path / "pyproject.toml").write_text(DALS(pyproject), encoding="utf-8")
+ (tmp_path / "foo.py").touch()
+ with jaraco.path.DirectoryStack().context(tmp_path):
+ build_meta.build_wheel(".")
+ # Ensure py_modules are found
+ wheel_files = get_wheel_members(next(tmp_path.glob("*.whl")))
+ assert "foo.py" in wheel_files
+
class TestNoConfig:
DEFAULT_VERSION = "0.0.0" # Default version given by setuptools
import stat
import time
from typing import List, Tuple
+from pathlib import Path
import pytest
from jaraco import path
+from setuptools import errors
from setuptools.command.egg_info import (
- egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision,
+ EggInfoDeprecationWarning,
+ egg_info,
+ get_pkg_info_revision,
+ manifest_maker,
+ write_entries,
)
from setuptools.dist import Distribution
pass
+@pytest.fixture
+def env():
+ with contexts.tempdir(prefix='setuptools-test.') as env_dir:
+ env = Environment(env_dir)
+ os.chmod(env_dir, stat.S_IRWXU)
+ subs = 'home', 'lib', 'scripts', 'data', 'egg-base'
+ env.paths = dict(
+ (dirname, os.path.join(env_dir, dirname))
+ for dirname in subs
+ )
+ list(map(os.mkdir, env.paths.values()))
+ path.build({
+ env.paths['home']: {
+ '.pydistutils.cfg': DALS("""
+ [egg_info]
+ egg-base = %(egg-base)s
+ """ % env.paths)
+ }
+ })
+ yield env
+
+
class TestEggInfo:
setup_script = DALS("""
version_str = pkg_info_lines[0].split(' ')[1]
return tuple(map(int, version_str.split('.')[:2]))
- @pytest.fixture
- def env(self):
- with contexts.tempdir(prefix='setuptools-test.') as env_dir:
- env = Environment(env_dir)
- os.chmod(env_dir, stat.S_IRWXU)
- subs = 'home', 'lib', 'scripts', 'data', 'egg-base'
- env.paths = dict(
- (dirname, os.path.join(env_dir, dirname))
- for dirname in subs
- )
- list(map(os.mkdir, env.paths.values()))
- path.build({
- env.paths['home']: {
- '.pydistutils.cfg': DALS("""
- [egg_info]
- egg-base = %(egg-base)s
- """ % env.paths)
- }
- })
- yield env
-
def test_egg_info_save_version_info_setup_empty(self, tmpdir_cwd, env):
"""
When the egg_info section is empty or not present, running
def test_get_pkg_info_revision_deprecated(self):
pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision)
+
+
+class TestWriteEntries:
+
+ def test_invalid_entry_point(self, tmpdir_cwd, env):
+ dist = Distribution({"name": "foo", "version": "0.0.1"})
+ dist.entry_points = {"foo": "foo = invalid-identifier:foo"}
+ cmd = dist.get_command_obj("egg_info")
+ expected_msg = r"Problems to parse .*invalid-identifier.*"
+ with pytest.raises(errors.OptionError, match=expected_msg) as ex:
+ write_entries(cmd, "entry_points", "entry_points.txt")
+ assert "ensure entry-point follows the spec" in ex.value.args[0]
+
+ def test_valid_entry_point(self, tmpdir_cwd, env):
+ dist = Distribution({"name": "foo", "version": "0.0.1"})
+ dist.entry_points = {
+ "abc": "foo = bar:baz",
+ "def": ["faa = bor:boz"],
+ }
+ cmd = dist.get_command_obj("egg_info")
+ write_entries(cmd, "entry_points", "entry_points.txt")
+ content = Path("entry_points.txt").read_text(encoding="utf-8")
+ assert "[abc]\nfoo = bar:baz\n" in content
+ assert "[def]\nfaa = bor:boz\n" in content
-# -*- coding: utf-8 -*-
"""sdist tests"""
import contextlib
import tempfile
import itertools
import io
+import logging
from distutils import log
from distutils.errors import DistutilsTemplateError
import pytest
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
def make_local_path(s):
"""Converts '/' in a string to os.sep"""
return s.replace('/', os.sep)
to ensure setuptools' version of FileList keeps parity with distutils.
"""
- def setup_method(self, method):
- if not hasattr(log, 'Log'):
- pytest.skip("These tests rely on old logging infra")
- super(TestFileListTest, self).setup_method(method)
- self.threshold = log.set_threshold(log.FATAL)
- self._old_log = log.Log._log
- log.Log._log = self._log
- self.logs = []
+ @pytest.fixture(autouse=os.getenv("SETUPTOOLS_USE_DISTUTILS") == "stdlib")
+ def _compat_record_logs(self, monkeypatch, caplog):
+ """Account for stdlib compatibility"""
+ def _log(_logger, level, msg, args):
+ exc = sys.exc_info()
+ rec = logging.LogRecord("distutils", level, "", 0, msg, args, exc)
+ caplog.records.append(rec)
- def teardown_method(self, method):
- log.set_threshold(self.threshold)
- log.Log._log = self._old_log
- super(TestFileListTest, self).teardown_method(method)
-
- def _log(self, level, msg, args):
- if level not in (log.DEBUG, log.INFO, log.WARN, log.ERROR, log.FATAL):
- raise ValueError('%s wrong log level' % str(level))
- self.logs.append((level, msg, args))
-
- def get_logs(self, *levels):
- def _format(msg, args):
- if len(args) == 0:
- return msg
- return msg % args
- return [_format(msg, args) for level, msg, args
- in self.logs if level in levels]
-
- def clear_logs(self):
- self.logs = []
-
- def assertNoWarnings(self):
- assert self.get_logs(log.WARN) == []
- self.clear_logs()
-
- def assertWarnings(self):
- assert len(self.get_logs(log.WARN)) > 0
- self.clear_logs()
+ monkeypatch.setattr(log.Log, "_log", _log)
+
+ def get_records(self, caplog, *levels):
+ return [r for r in caplog.records if r.levelno in levels]
+
+ def assertNoWarnings(self, caplog):
+ assert self.get_records(caplog, log.WARN) == []
+ caplog.clear()
+
+ def assertWarnings(self, caplog):
+ if IS_PYPY and not caplog.records:
+ pytest.xfail("caplog checks may not work well in PyPy")
+ else:
+ assert len(self.get_records(caplog, log.WARN)) > 0
+ caplog.clear()
def make_files(self, files):
for file in files:
else:
assert False, "Should have thrown an error"
- def test_include(self):
+ def test_include(self, caplog):
+ caplog.set_level(logging.DEBUG)
ml = make_local_path
# include
file_list = FileList()
file_list.process_template_line('include *.py')
file_list.sort()
assert file_list.files == ['a.py']
- self.assertNoWarnings()
+ self.assertNoWarnings(caplog)
file_list.process_template_line('include *.rb')
file_list.sort()
assert file_list.files == ['a.py']
- self.assertWarnings()
+ self.assertWarnings(caplog)
- def test_exclude(self):
+ def test_exclude(self, caplog):
+ caplog.set_level(logging.DEBUG)
ml = make_local_path
# exclude
file_list = FileList()
file_list.process_template_line('exclude *.py')
file_list.sort()
assert file_list.files == ['b.txt', ml('d/c.py')]
- self.assertNoWarnings()
+ self.assertNoWarnings(caplog)
file_list.process_template_line('exclude *.rb')
file_list.sort()
assert file_list.files == ['b.txt', ml('d/c.py')]
- self.assertWarnings()
+ self.assertWarnings(caplog)
- def test_global_include(self):
+ def test_global_include(self, caplog):
+ caplog.set_level(logging.DEBUG)
ml = make_local_path
# global-include
file_list = FileList()
file_list.process_template_line('global-include *.py')
file_list.sort()
assert file_list.files == ['a.py', ml('d/c.py')]
- self.assertNoWarnings()
+ self.assertNoWarnings(caplog)
file_list.process_template_line('global-include *.rb')
file_list.sort()
assert file_list.files == ['a.py', ml('d/c.py')]
- self.assertWarnings()
+ self.assertWarnings(caplog)
- def test_global_exclude(self):
+ def test_global_exclude(self, caplog):
+ caplog.set_level(logging.DEBUG)
ml = make_local_path
# global-exclude
file_list = FileList()
file_list.process_template_line('global-exclude *.py')
file_list.sort()
assert file_list.files == ['b.txt']
- self.assertNoWarnings()
+ self.assertNoWarnings(caplog)
file_list.process_template_line('global-exclude *.rb')
file_list.sort()
assert file_list.files == ['b.txt']
- self.assertWarnings()
+ self.assertWarnings(caplog)
- def test_recursive_include(self):
+ def test_recursive_include(self, caplog):
+ caplog.set_level(logging.DEBUG)
ml = make_local_path
# recursive-include
file_list = FileList()
file_list.process_template_line('recursive-include d *.py')
file_list.sort()
assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
- self.assertNoWarnings()
+ self.assertNoWarnings(caplog)
file_list.process_template_line('recursive-include e *.py')
file_list.sort()
assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
- self.assertWarnings()
+ self.assertWarnings(caplog)
- def test_recursive_exclude(self):
+ def test_recursive_exclude(self, caplog):
+ caplog.set_level(logging.DEBUG)
ml = make_local_path
# recursive-exclude
file_list = FileList()
file_list.process_template_line('recursive-exclude d *.py')
file_list.sort()
assert file_list.files == ['a.py', ml('d/c.txt')]
- self.assertNoWarnings()
+ self.assertNoWarnings(caplog)
file_list.process_template_line('recursive-exclude e *.py')
file_list.sort()
assert file_list.files == ['a.py', ml('d/c.txt')]
- self.assertWarnings()
+ self.assertWarnings(caplog)
- def test_graft(self):
+ def test_graft(self, caplog):
+ caplog.set_level(logging.DEBUG)
ml = make_local_path
# graft
file_list = FileList()
file_list.process_template_line('graft d')
file_list.sort()
assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
- self.assertNoWarnings()
+ self.assertNoWarnings(caplog)
file_list.process_template_line('graft e')
file_list.sort()
assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
- self.assertWarnings()
+ self.assertWarnings(caplog)
- def test_prune(self):
+ def test_prune(self, caplog):
+ caplog.set_level(logging.DEBUG)
ml = make_local_path
# prune
file_list = FileList()
file_list.process_template_line('prune d')
file_list.sort()
assert file_list.files == ['a.py', ml('f/f.py')]
- self.assertNoWarnings()
+ self.assertNoWarnings(caplog)
file_list.process_template_line('prune e')
file_list.sort()
assert file_list.files == ['a.py', ml('f/f.py')]
- self.assertWarnings()
+ self.assertWarnings(caplog)
-# -*- coding: utf-8 -*-
"""
Tests for msvc support module (msvc14 unit tests).
"""
-# -*- coding: utf-8 -*-
-
"""wheel tests
"""