summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2022-05-09 10:28:14 -0400
committerJason R. Coombs <jaraco@jaraco.com>2022-05-10 07:47:52 -0400
commit7e3e58ce198d9319bb2279c2da4b1f298914d8a5 (patch)
tree1af3b0f99f5e37c47789bf93519392e08eb24094
parente009a87b5578cb16099b697ba8395c8f6bdd70f3 (diff)
parenta7cfb56a7b1eaa95824d81a34e8e70b85d42f784 (diff)
downloadpython-setuptools-git-7e3e58ce198d9319bb2279c2da4b1f298914d8a5.tar.gz
Merge https://github.com/pypa/distutils into feature/distutils-a7cfb56a7b1eaa
-rw-r--r--.github/workflows/main.yml23
-rw-r--r--docs/deprecated/distutils/examples.rst4
-rw-r--r--docs/deprecated/distutils/setupscript.rst6
-rw-r--r--setuptools/_distutils/_functools.py20
-rw-r--r--setuptools/_distutils/command/bdist_msi.py6
-rw-r--r--setuptools/_distutils/command/bdist_rpm.py8
-rw-r--r--setuptools/_distutils/command/check.py43
-rw-r--r--setuptools/_distutils/dist.py55
-rw-r--r--setuptools/_distutils/sysconfig.py22
-rw-r--r--setuptools/_distutils/tests/test_check.py11
-rw-r--r--setuptools/_distutils/tests/test_dist.py2
-rw-r--r--setuptools/_distutils/tests/test_register.py4
-rw-r--r--setuptools/_distutils/tests/test_sdist.py2
-rw-r--r--setuptools/_distutils/tests/test_sysconfig.py28
-rw-r--r--tox.ini10
15 files changed, 129 insertions, 115 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 4275bbde..45af54e5 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -67,7 +67,13 @@ jobs:
${{ matrix.python }}
test_cygwin:
- runs-on: windows-latest
+ strategy:
+ matrix:
+ python:
+ - 38
+ platform:
+ - windows-latest
+ runs-on: ${{ matrix.platform }}
timeout-minutes: 75
steps:
- uses: actions/checkout@v2
@@ -76,19 +82,14 @@ jobs:
with:
platform: x86_64
packages: >-
- git,
+ python${{ matrix.python }},
+ python${{ matrix.python }}-devel,
+ python${{ matrix.python }}-tox,
gcc-core,
- python38,
- python38-devel,
- python38-pip
- - name: Install tox
- shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -leo pipefail -o igncr {0}
- run: |
- python3.8 -m pip install tox
+ git,
- name: Run tests
shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -leo pipefail -o igncr {0}
- run: |
- tox -- --cov-report xml
+ run: tox
integration-test:
needs: test
diff --git a/docs/deprecated/distutils/examples.rst b/docs/deprecated/distutils/examples.rst
index d0984655..00eef73f 100644
--- a/docs/deprecated/distutils/examples.rst
+++ b/docs/deprecated/distutils/examples.rst
@@ -253,9 +253,7 @@ Running the ``check`` command will display some warnings:
$ python setup.py check
running check
- warning: check: missing required meta-data: version, url
- warning: check: missing meta-data: either (author and author_email) or
- (maintainer and maintainer_email) should be supplied
+ warning: check: missing required meta-data: version
If you use the reStructuredText syntax in the ``long_description`` field and
diff --git a/docs/deprecated/distutils/setupscript.rst b/docs/deprecated/distutils/setupscript.rst
index f49c4f89..ec9cf34e 100644
--- a/docs/deprecated/distutils/setupscript.rst
+++ b/docs/deprecated/distutils/setupscript.rst
@@ -582,7 +582,7 @@ This information includes:
| ``maintainer_email`` | email address of the | email address | \(3) |
| | package maintainer | | |
+----------------------+---------------------------+-----------------+--------+
-| ``url`` | home page for the package | URL | \(1) |
+| ``url`` | home page for the package | URL | |
+----------------------+---------------------------+-----------------+--------+
| ``description`` | short, summary | short string | |
| | description of the | | |
@@ -612,8 +612,8 @@ Notes:
It is recommended that versions take the form *major.minor[.patch[.sub]]*.
(3)
- Either the author or the maintainer must be identified. If maintainer is
- provided, distutils lists it as the author in :file:`PKG-INFO`.
+ If maintainer is provided and author is not, distutils lists maintainer as
+ the author in :file:`PKG-INFO`.
(4)
The ``long_description`` field is used by PyPI when you publish a package,
diff --git a/setuptools/_distutils/_functools.py b/setuptools/_distutils/_functools.py
new file mode 100644
index 00000000..e7053bac
--- /dev/null
+++ b/setuptools/_distutils/_functools.py
@@ -0,0 +1,20 @@
+import functools
+
+
+# from jaraco.functools 3.5
+def pass_none(func):
+ """
+ Wrap func so it's not called if its first param is None
+
+ >>> print_text = pass_none(print)
+ >>> print_text('text')
+ text
+ >>> print_text(None)
+ """
+
+ @functools.wraps(func)
+ def wrapper(param, *args, **kwargs):
+ if param is not None:
+ return func(param, *args, **kwargs)
+
+ return wrapper
diff --git a/setuptools/_distutils/command/bdist_msi.py b/setuptools/_distutils/command/bdist_msi.py
index 15259532..56c4b988 100644
--- a/setuptools/_distutils/command/bdist_msi.py
+++ b/setuptools/_distutils/command/bdist_msi.py
@@ -231,11 +231,7 @@ class bdist_msi(Command):
if os.path.exists(installer_name): os.unlink(installer_name)
metadata = self.distribution.metadata
- author = metadata.author
- if not author:
- author = metadata.maintainer
- if not author:
- author = "UNKNOWN"
+ author = metadata.author or metadata.maintainer
version = metadata.get_version()
# ProductVersion must be strictly numeric
# XXX need to deal with prerelease versions
diff --git a/setuptools/_distutils/command/bdist_rpm.py b/setuptools/_distutils/command/bdist_rpm.py
index 550cbfa1..a2a9e8e5 100644
--- a/setuptools/_distutils/command/bdist_rpm.py
+++ b/setuptools/_distutils/command/bdist_rpm.py
@@ -399,7 +399,7 @@ class bdist_rpm(Command):
'%define unmangled_version ' + self.distribution.get_version(),
'%define release ' + self.release.replace('-','_'),
'',
- 'Summary: ' + self.distribution.get_description(),
+ 'Summary: ' + (self.distribution.get_description() or "UNKNOWN"),
]
# Workaround for #14443 which affects some RPM based systems such as
@@ -438,7 +438,7 @@ class bdist_rpm(Command):
spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz')
spec_file.extend([
- 'License: ' + self.distribution.get_license(),
+ 'License: ' + (self.distribution.get_license() or "UNKNOWN"),
'Group: ' + self.group,
'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
'Prefix: %{_prefix}', ])
@@ -464,7 +464,7 @@ class bdist_rpm(Command):
spec_file.append('%s: %s' % (field, val))
- if self.distribution.get_url() != 'UNKNOWN':
+ if self.distribution.get_url():
spec_file.append('Url: ' + self.distribution.get_url())
if self.distribution_name:
@@ -483,7 +483,7 @@ class bdist_rpm(Command):
spec_file.extend([
'',
'%description',
- self.distribution.get_long_description()
+ self.distribution.get_long_description() or "",
])
# put locale descriptions into spec file
diff --git a/setuptools/_distutils/command/check.py b/setuptools/_distutils/command/check.py
index af311ca9..8a02dbca 100644
--- a/setuptools/_distutils/command/check.py
+++ b/setuptools/_distutils/command/check.py
@@ -82,54 +82,19 @@ class check(Command):
"""Ensures that all required elements of meta-data are supplied.
Required fields:
- name, version, URL
-
- Recommended fields:
- (author and author_email) or (maintainer and maintainer_email))
+ name, version
Warns if any are missing.
"""
metadata = self.distribution.metadata
missing = []
- for attr in ('name', 'version', 'url'):
- if not (hasattr(metadata, attr) and getattr(metadata, attr)):
+ for attr in 'name', 'version':
+ if not getattr(metadata, attr, None):
missing.append(attr)
if missing:
- self.warn("missing required meta-data: %s" % ', '.join(missing))
- if not (
- self._check_contact("author", metadata) or
- self._check_contact("maintainer", metadata)
- ):
- self.warn("missing meta-data: either (author and author_email) " +
- "or (maintainer and maintainer_email) " +
- "should be supplied")
-
- def _check_contact(self, kind, metadata):
- """
- Returns True if the contact's name is specified and False otherwise.
- This function will warn if the contact's email is not specified.
- """
- name = getattr(metadata, kind) or ''
- email = getattr(metadata, kind + '_email') or ''
-
- msg = ("missing meta-data: if '{}' supplied, " +
- "'{}' should be supplied too")
-
- if name and email:
- return True
-
- if name:
- self.warn(msg.format(kind, kind + '_email'))
- return True
-
- addresses = [(alias, addr) for alias, addr in getaddresses([email])]
- if any(alias and addr for alias, addr in addresses):
- # The contact's name can be encoded in the email: `Name <email>`
- return True
-
- return False
+ self.warn("missing required meta-data: %s" % ', '.join(missing))
def check_restructuredtext(self):
"""Checks if the long string fields are reST-compliant."""
diff --git a/setuptools/_distutils/dist.py b/setuptools/_distutils/dist.py
index 37db4d6c..45024975 100644
--- a/setuptools/_distutils/dist.py
+++ b/setuptools/_distutils/dist.py
@@ -1064,9 +1064,8 @@ class DistributionMetadata:
def _read_field(name):
value = msg[name]
- if value == 'UNKNOWN':
- return None
- return value
+ if value and value != "UNKNOWN":
+ return value
def _read_list(name):
values = msg.get_all(name, None)
@@ -1125,23 +1124,24 @@ class DistributionMetadata:
self.classifiers or self.download_url):
version = '1.1'
+ # required fields
file.write('Metadata-Version: %s\n' % version)
file.write('Name: %s\n' % self.get_name())
file.write('Version: %s\n' % self.get_version())
- file.write('Summary: %s\n' % self.get_description())
- file.write('Home-page: %s\n' % self.get_url())
- file.write('Author: %s\n' % self.get_contact())
- file.write('Author-email: %s\n' % self.get_contact_email())
- file.write('License: %s\n' % self.get_license())
- if self.download_url:
- file.write('Download-URL: %s\n' % self.download_url)
- long_desc = rfc822_escape(self.get_long_description())
- file.write('Description: %s\n' % long_desc)
+ def maybe_write(header, val):
+ if val:
+ file.write("{}: {}\n".format(header, val))
- keywords = ','.join(self.get_keywords())
- if keywords:
- file.write('Keywords: %s\n' % keywords)
+ # optional fields
+ maybe_write("Summary", self.get_description())
+ maybe_write("Home-page", self.get_url())
+ maybe_write("Author", self.get_contact())
+ maybe_write("Author-email", self.get_contact_email())
+ maybe_write("License", self.get_license())
+ maybe_write("Download-URL", self.download_url)
+ maybe_write("Description", rfc822_escape(self.get_long_description() or ""))
+ maybe_write("Keywords", ",".join(self.get_keywords()))
self._write_list(file, 'Platform', self.get_platforms())
self._write_list(file, 'Classifier', self.get_classifiers())
@@ -1152,6 +1152,7 @@ class DistributionMetadata:
self._write_list(file, 'Obsoletes', self.get_obsoletes())
def _write_list(self, file, name, values):
+ values = values or []
for value in values:
file.write('%s: %s\n' % (name, value))
@@ -1167,35 +1168,35 @@ class DistributionMetadata:
return "%s-%s" % (self.get_name(), self.get_version())
def get_author(self):
- return self.author or "UNKNOWN"
+ return self.author
def get_author_email(self):
- return self.author_email or "UNKNOWN"
+ return self.author_email
def get_maintainer(self):
- return self.maintainer or "UNKNOWN"
+ return self.maintainer
def get_maintainer_email(self):
- return self.maintainer_email or "UNKNOWN"
+ return self.maintainer_email
def get_contact(self):
- return self.maintainer or self.author or "UNKNOWN"
+ return self.maintainer or self.author
def get_contact_email(self):
- return self.maintainer_email or self.author_email or "UNKNOWN"
+ return self.maintainer_email or self.author_email
def get_url(self):
- return self.url or "UNKNOWN"
+ return self.url
def get_license(self):
- return self.license or "UNKNOWN"
+ return self.license
get_licence = get_license
def get_description(self):
- return self.description or "UNKNOWN"
+ return self.description
def get_long_description(self):
- return self.long_description or "UNKNOWN"
+ return self.long_description
def get_keywords(self):
return self.keywords or []
@@ -1204,7 +1205,7 @@ class DistributionMetadata:
self.keywords = _ensure_list(value, 'keywords')
def get_platforms(self):
- return self.platforms or ["UNKNOWN"]
+ return self.platforms
def set_platforms(self, value):
self.platforms = _ensure_list(value, 'platforms')
@@ -1216,7 +1217,7 @@ class DistributionMetadata:
self.classifiers = _ensure_list(value, 'classifiers')
def get_download_url(self):
- return self.download_url or "UNKNOWN"
+ return self.download_url
# PEP 314
def get_requires(self):
diff --git a/setuptools/_distutils/sysconfig.py b/setuptools/_distutils/sysconfig.py
index 55a42e16..7543f794 100644
--- a/setuptools/_distutils/sysconfig.py
+++ b/setuptools/_distutils/sysconfig.py
@@ -16,6 +16,7 @@ import sysconfig
from .errors import DistutilsPlatformError
from . import py39compat
+from ._functools import pass_none
IS_PYPY = '__pypy__' in sys.builtin_module_names
@@ -51,12 +52,25 @@ def _is_python_source_dir(d):
_sys_home = getattr(sys, '_home', None)
+
+def _is_parent(dir_a, dir_b):
+ """
+ Return True if a is a parent of b.
+ """
+ return os.path.normcase(dir_a).startswith(os.path.normcase(dir_b))
+
+
if os.name == 'nt':
+ @pass_none
def _fix_pcbuild(d):
- if d and os.path.normcase(d).startswith(
- os.path.normcase(os.path.join(PREFIX, "PCbuild"))):
- return PREFIX
- return d
+ # In a venv, sys._home will be inside BASE_PREFIX rather than PREFIX.
+ prefixes = PREFIX, BASE_PREFIX
+ matched = (
+ prefix
+ for prefix in prefixes
+ if _is_parent(d, os.path.join(prefix, "PCbuild"))
+ )
+ return next(matched, d)
project_base = _fix_pcbuild(project_base)
_sys_home = _fix_pcbuild(_sys_home)
diff --git a/setuptools/_distutils/tests/test_check.py b/setuptools/_distutils/tests/test_check.py
index b41dba3d..2414d6eb 100644
--- a/setuptools/_distutils/tests/test_check.py
+++ b/setuptools/_distutils/tests/test_check.py
@@ -43,7 +43,7 @@ class CheckTestCase(support.LoggingSilencer,
# by default, check is checking the metadata
# should have some warnings
cmd = self._run()
- self.assertEqual(cmd._warnings, 2)
+ self.assertEqual(cmd._warnings, 1)
# now let's add the required fields
# and run it again, to make sure we don't get
@@ -81,17 +81,16 @@ class CheckTestCase(support.LoggingSilencer,
cmd = self._run(metadata)
self.assertEqual(cmd._warnings, 0)
- # the check should warn if only email is given and it does not
- # contain the name
+ # the check should not warn if only email is given
metadata[kind + '_email'] = 'name@email.com'
cmd = self._run(metadata)
- self.assertEqual(cmd._warnings, 1)
+ self.assertEqual(cmd._warnings, 0)
- # the check should warn if only the name is given
+ # the check should not warn if only the name is given
metadata[kind] = "Name"
del metadata[kind + '_email']
cmd = self._run(metadata)
- self.assertEqual(cmd._warnings, 1)
+ self.assertEqual(cmd._warnings, 0)
@unittest.skipUnless(HAS_DOCUTILS, "won't test without docutils")
def test_check_document(self):
diff --git a/setuptools/_distutils/tests/test_dist.py b/setuptools/_distutils/tests/test_dist.py
index 36155be1..9132bc04 100644
--- a/setuptools/_distutils/tests/test_dist.py
+++ b/setuptools/_distutils/tests/test_dist.py
@@ -519,7 +519,7 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
self.assertEqual(metadata.description, "xxx")
self.assertEqual(metadata.download_url, 'http://example.com')
self.assertEqual(metadata.keywords, ['one', 'two'])
- self.assertEqual(metadata.platforms, ['UNKNOWN'])
+ self.assertEqual(metadata.platforms, None)
self.assertEqual(metadata.obsoletes, None)
self.assertEqual(metadata.requires, ['foo'])
diff --git a/setuptools/_distutils/tests/test_register.py b/setuptools/_distutils/tests/test_register.py
index 5770ed58..45567686 100644
--- a/setuptools/_distutils/tests/test_register.py
+++ b/setuptools/_distutils/tests/test_register.py
@@ -154,8 +154,8 @@ class RegisterTestCase(BasePyPIRCCommandTestCase):
req1 = dict(self.conn.reqs[0].headers)
req2 = dict(self.conn.reqs[1].headers)
- self.assertEqual(req1['Content-length'], '1374')
- self.assertEqual(req2['Content-length'], '1374')
+ self.assertEqual(req1['Content-length'], '1359')
+ self.assertEqual(req2['Content-length'], '1359')
self.assertIn(b'xxx', self.conn.reqs[1].data)
def test_password_not_in_file(self):
diff --git a/setuptools/_distutils/tests/test_sdist.py b/setuptools/_distutils/tests/test_sdist.py
index 4c51717c..aa04dd05 100644
--- a/setuptools/_distutils/tests/test_sdist.py
+++ b/setuptools/_distutils/tests/test_sdist.py
@@ -251,7 +251,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase):
cmd.run()
warnings = [msg for msg in self.get_logs(WARN) if
msg.startswith('warning: check:')]
- self.assertEqual(len(warnings), 2)
+ self.assertEqual(len(warnings), 1)
# trying with a complete set of metadata
self.clear_logs()
diff --git a/setuptools/_distutils/tests/test_sysconfig.py b/setuptools/_distutils/tests/test_sysconfig.py
index e671f9e0..1c88cc85 100644
--- a/setuptools/_distutils/tests/test_sysconfig.py
+++ b/setuptools/_distutils/tests/test_sysconfig.py
@@ -7,6 +7,9 @@ import sys
import textwrap
import unittest
+import jaraco.envs
+
+import distutils
from distutils import sysconfig
from distutils.ccompiler import get_default_compiler
from distutils.unixccompiler import UnixCCompiler
@@ -309,6 +312,31 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase):
self.assertTrue(sysconfig.get_config_var("EXT_SUFFIX").endswith(".pyd"))
self.assertNotEqual(sysconfig.get_config_var("EXT_SUFFIX"), ".pyd")
+ @unittest.skipUnless(
+ sys.platform == 'win32',
+ 'Testing Windows build layout')
+ @unittest.skipUnless(
+ sys.implementation.name == 'cpython',
+ 'Need cpython for this test')
+ @unittest.skipUnless(
+ '\\PCbuild\\'.casefold() in sys.executable.casefold(),
+ 'Need sys.executable to be in a source tree')
+ def test_win_build_venv_from_source_tree(self):
+ """Ensure distutils.sysconfig detects venvs from source tree builds."""
+ env = jaraco.envs.VEnv()
+ env.create_opts = env.clean_opts
+ env.root = TESTFN
+ env.ensure_env()
+ cmd = [
+ env.exe(),
+ "-c",
+ "import distutils.sysconfig; print(distutils.sysconfig.python_build)"
+ ]
+ distutils_path = os.path.dirname(os.path.dirname(distutils.__file__))
+ out = subprocess.check_output(cmd, env={**os.environ, "PYTHONPATH": distutils_path})
+ assert out == "True"
+
+
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(SysconfigTestCase))
diff --git a/tox.ini b/tox.ini
index 973f3763..bb2e7cb1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist = python
-minversion = 3.2
+minversion = 3.25
# https://github.com/jaraco/skeleton/issues/6
tox_pip_extensions_ext_venv_update = true
toxworkdir={env:TOX_WORK_DIR:.tox}
@@ -20,10 +20,6 @@ passenv =
windir # required for test_pkg_resources
# honor git config in pytest-perf
HOME
- # workaround for tox-dev/tox#2382
- PROGRAMDATA
- PROGRAMFILES
- PROGRAMFILES(x86)
[testenv:integration]
deps = {[testenv]deps}
@@ -31,10 +27,6 @@ extras = testing-integration
passenv =
{[testenv]passenv}
DOWNLOAD_PATH
- # workaround for tox-dev/tox#2382
- PROGRAMDATA
- PROGRAMFILES
- PROGRAMFILES(x86)
setenv =
PROJECT_ROOT = {toxinidir}
commands =