[bumpversion]
-current_version = 62.0.0
+current_version = 62.1.0
commit = True
tag = True
+v62.1.0
+-------
+
+
+Changes
+^^^^^^^
+* #3258: Merge pypa/distutils@5229dad46b.
+
+Misc
+^^^^
+* #3249: Simplified ``package_dir`` obtained via auto-discovery.
+
+
v62.0.0
-------
Declaring required dependency
=============================
This is where a package declares its core dependencies, without which it won't
-be able to run. ``setuptools`` support automatically download and install
+be able to run. ``setuptools`` supports automatically downloading and installing
these dependencies when the package is installed. Although there is more
finesse to it, let's start with a simple example.
Platform specific dependencies
------------------------------
-Setuptools offer the capability to evaluate certain conditions before blindly
+Setuptools offers the capability to evaluate certain conditions before blindly
installing everything listed in ``install_requires``. This is great for platform
specific dependencies. For example, the ``enum`` package was added in Python
3.4, therefore, package that depends on it can elect to install it only when
Optional dependencies
=====================
Setuptools allows you to declare dependencies that only get installed under
-specific circumstances. These dependencies are specified with ``extras_require``
+specific circumstances. These dependencies are specified with the ``extras_require``
keyword and are only installed if another package depends on it (either
-directly or indirectly) This makes it convenient to declare dependencies for
+directly or indirectly). This makes it convenient to declare dependencies for
ancillary functions such as "tests" and "docs".
.. note::
[metadata]
name = setuptools
-version = 62.0.0
+version = 62.1.0
author = Python Packaging Authority
author_email = distutils-sig@python.org
description = Easily download, build, install, upgrade, and uninstall Python packages
"--plat-name only supported on Windows (try "
"using './configure --help' on your platform)")
- plat_specifier = ".%s-%d.%d" % (self.plat_name, *sys.version_info[:2])
+ plat_specifier = ".%s-%s" % (self.plat_name,
+ sys.implementation.cache_tag)
# Make it so Python 2.x and Python 2.x with --with-pydebug don't
# share the same build directories. Doing so confuses the build
--- /dev/null
+import sys
+import platform
+
+
+def add_ext_suffix_39(vars):
+ """
+ Ensure vars contains 'EXT_SUFFIX'. pypa/distutils#130
+ """
+ import _imp
+ ext_suffix = _imp.extension_suffixes()[0]
+ vars.update(
+ EXT_SUFFIX=ext_suffix,
+ # sysconfig sets SO to match EXT_SUFFIX, so maintain
+ # that expectation.
+ # https://github.com/python/cpython/blob/785cc6770588de087d09e89a69110af2542be208/Lib/sysconfig.py#L671-L673
+ SO=ext_suffix,
+ )
+
+
+needs_ext_suffix = sys.version_info < (3, 10) and platform.system() == 'Windows'
+add_ext_suffix = add_ext_suffix_39 if needs_ext_suffix else lambda vars: None
Email: <fdrake@acm.org>
"""
-import _imp
import os
import re
import sys
import sysconfig
from .errors import DistutilsPlatformError
+from . import py39compat
IS_PYPY = '__pypy__' in sys.builtin_module_names
return True
return False
+
_sys_home = getattr(sys, '_home', None)
if os.name == 'nt':
project_base = _fix_pcbuild(project_base)
_sys_home = _fix_pcbuild(_sys_home)
+
def _python_build():
if _sys_home:
return _is_python_source_dir(_sys_home)
return _is_python_source_dir(project_base)
+
python_build = _python_build()
# this attribute, which is fine.
pass
+
def get_python_version():
"""Return a string containing the major and minor Python version,
leaving off the patchlevel. Sample return values could be '1.5'
"on platform '%s'" % os.name)
-
def customize_compiler(compiler):
"""Do any platform-specific customization of a CCompiler instance.
_config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True'
(cc, cxx, cflags, ccshared, ldshared, shlib_suffix, ar, ar_flags) = \
- get_config_vars('CC', 'CXX', 'CFLAGS',
- 'CCSHARED', 'LDSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS')
+ get_config_vars(
+ 'CC', 'CXX', 'CFLAGS',
+ 'CCSHARED', 'LDSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS')
if 'CC' in os.environ:
newcc = os.environ['CC']
return sysconfig.get_config_h_filename()
-
def get_makefile_filename():
"""Return full pathname of installed Makefile from the Python build."""
return sysconfig.get_makefile_filename()
_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
+
def parse_makefile(fn, g=None):
"""Parse a Makefile-style file.
used instead of a new dictionary.
"""
from distutils.text_file import TextFile
- fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape")
+ fp = TextFile(
+ fn, strip_comments=1, skip_blanks=1, join_lines=1,
+ errors="surrogateescape")
if g is None:
g = {}
while True:
line = fp.readline()
- if line is None: # eof
+ if line is None: # eof
break
m = _variable_rx.match(line)
if m:
item = os.environ[n]
elif n in renamed_variables:
- if name.startswith('PY_') and name[3:] in renamed_variables:
+ if name.startswith('PY_') and \
+ name[3:] in renamed_variables:
item = ""
elif 'PY_' + n in notdone:
if "$" in after:
notdone[name] = value
else:
- try: value = int(value)
+ try:
+ value = int(value)
except ValueError:
done[name] = value.strip()
else:
del notdone[name]
if name.startswith('PY_') \
- and name[3:] in renamed_variables:
+ and name[3:] in renamed_variables:
name = name[3:]
if name not in done:
global _config_vars
if _config_vars is None:
_config_vars = sysconfig.get_config_vars().copy()
+ py39compat.add_ext_suffix(_config_vars)
if args:
vals = []
else:
return _config_vars
+
def get_config_var(name):
"""Return the value of a single variable using the dictionary
returned by 'get_config_vars()'. Equivalent to
"""
if name == 'SO':
import warnings
- warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2)
+ warnings.warn(
+ 'SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2)
return get_config_vars().get(name)
wanted = os.path.join(cmd.build_base, 'lib')
self.assertEqual(cmd.build_purelib, wanted)
- # build_platlib is 'build/lib.platform-x.x[-pydebug]'
+ # build_platlib is 'build/lib.platform-cache_tag[-pydebug]'
# examples:
- # build/lib.macosx-10.3-i386-2.7
- plat_spec = '.%s-%d.%d' % (cmd.plat_name, *sys.version_info[:2])
+ # build/lib.macosx-10.3-i386-cpython39
+ plat_spec = '.%s-%s' % (cmd.plat_name, sys.implementation.cache_tag)
if hasattr(sys, 'gettotalrefcount'):
self.assertTrue(cmd.build_platlib.endswith('-pydebug'))
plat_spec += '-pydebug'
expected = os.path.normpath(expected)
self.assertEqual(got, expected)
- libdir = os.path.join(destination, "lib", "python")
+ impl_name = sys.implementation.name.replace("cpython", "python")
+ libdir = os.path.join(destination, "lib", impl_name)
check_path(cmd.install_lib, libdir)
_platlibdir = getattr(sys, "platlibdir", "lib")
- platlibdir = os.path.join(destination, _platlibdir, "python")
+ platlibdir = os.path.join(destination, _platlibdir, impl_name)
check_path(cmd.install_platlib, platlibdir)
check_path(cmd.install_purelib, libdir)
check_path(cmd.install_headers,
- os.path.join(destination, "include", "python", "foopkg"))
+ os.path.join(destination, "include", impl_name, "foopkg"))
check_path(cmd.install_scripts, os.path.join(destination, "bin"))
check_path(cmd.install_data, destination)
@unittest.skipIf(sys.platform == 'win32',
'Makefile only exists on Unix like systems')
+ @unittest.skipIf(sys.implementation.name != 'cpython',
+ 'Makefile only exists in CPython')
def test_get_makefile_filename(self):
makefile = sysconfig.get_makefile_filename()
self.assertTrue(os.path.isfile(makefile), makefile)
result = sysconfig.parse_config_h(f)
self.assertTrue(isinstance(result, dict))
+ @unittest.skipUnless(sys.platform == 'win32',
+ 'Testing windows pyd suffix')
+ @unittest.skipUnless(sys.implementation.name == 'cpython',
+ 'Need cpython for this test')
+ def test_win_ext_suffix(self):
+ self.assertTrue(sysconfig.get_config_var("EXT_SUFFIX").endswith(".pyd"))
+ self.assertNotEqual(sysconfig.get_config_var("EXT_SUFFIX"), ".pyd")
+
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(SysconfigTestCase))
"""
def __init__ (self, vstring=None):
+ if vstring:
+ self.parse(vstring)
warnings.warn(
"distutils Version classes are deprecated. "
"Use packaging.version instead.",
DeprecationWarning,
stacklevel=2,
)
- if vstring:
- self.parse(vstring)
def __repr__ (self):
return "%s ('%s')" % (self.__class__.__name__, str(self))
where = kwargs.pop('where', ['.'])
packages: List[str] = []
fill_package_dir = {} if fill_package_dir is None else fill_package_dir
+ search = list(unique_everseen(always_iterable(where)))
- for path in unique_everseen(always_iterable(where)):
+ if len(search) == 1 and all(not _same_path(search[0], x) for x in (".", root_dir)):
+ fill_package_dir.setdefault("", search[0])
+
+ for path in search:
package_path = _nest_path(root_dir, path)
pkgs = PackageFinder.find(package_path, **kwargs)
packages.extend(pkgs)
return packages
+def _same_path(p1: _Path, p2: _Path) -> bool:
+ """Differs from os.path.samefile because it does not require paths to exist.
+ Purely string based (no comparison between i-nodes).
+ >>> _same_path("a/b", "./a/b")
+ True
+ >>> _same_path("a/b", "a/./b")
+ True
+ >>> _same_path("a/b", "././a/b")
+ True
+ >>> _same_path("a/b", "./a/b/c/..")
+ True
+ >>> _same_path("a/b", "../a/b/c")
+ False
+ >>> _same_path("a", "a/b")
+ False
+ """
+ return os.path.normpath(p1) == os.path.normpath(p2)
+
+
def _nest_path(parent: _Path, path: _Path) -> str:
- path = parent if path == "." else os.path.join(parent, path)
+ path = parent if path in {".", ""} else os.path.join(parent, path)
return os.path.normpath(path)
({}, {"pkg", "other", "dir1", "dir1.dir2"}), # default value for `namespaces`
]
)
-def test_find_packages(tmp_path, monkeypatch, args, pkgs):
+def test_find_packages(tmp_path, args, pkgs):
files = {
"pkg/__init__.py",
"other/__init__.py",
]
assert set(expand.find_packages(where=where, **args)) == pkgs
+
+
+@pytest.mark.parametrize(
+ "files, where, expected_package_dir",
+ [
+ (["pkg1/__init__.py", "pkg1/other.py"], ["."], {}),
+ (["pkg1/__init__.py", "pkg2/__init__.py"], ["."], {}),
+ (["src/pkg1/__init__.py", "src/pkg1/other.py"], ["src"], {"": "src"}),
+ (["src/pkg1/__init__.py", "src/pkg2/__init__.py"], ["src"], {"": "src"}),
+ (
+ ["src1/pkg1/__init__.py", "src2/pkg2/__init__.py"],
+ ["src1", "src2"],
+ {"pkg1": "src1/pkg1", "pkg2": "src2/pkg2"},
+ ),
+ (
+ ["src/pkg1/__init__.py", "pkg2/__init__.py"],
+ ["src", "."],
+ {"pkg1": "src/pkg1"},
+ ),
+ ],
+)
+def test_fill_package_dir(tmp_path, files, where, expected_package_dir):
+ write_files({k: "" for k in files}, tmp_path)
+ pkg_dir = {}
+ kwargs = {"root_dir": tmp_path, "fill_package_dir": pkg_dir, "namespaces": False}
+ pkgs = expand.find_packages(where=where, **kwargs)
+ assert set(pkg_dir.items()) == set(expected_package_dir.items())
+ for pkg in pkgs:
+ pkg_path = find_package_path(pkg, pkg_dir, tmp_path)
+ assert os.path.exists(pkg_path)