Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge with pypa/distutils@a7cfb56a7 #3311

Merged
merged 20 commits into from May 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 12 additions & 11 deletions .github/workflows/main.yml
Expand Up @@ -67,7 +67,13 @@ jobs:
${{ matrix.python }}

test_cygwin:
runs-on: windows-latest
strategy:
matrix:
python:
- 39
platform:
- windows-latest
runs-on: ${{ matrix.platform }}
timeout-minutes: 75
steps:
- uses: actions/checkout@v2
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion changelog.d/3299.change.rst
@@ -1 +1 @@
Optional metadata fields are now truly optional.
Optional metadata fields are now truly optional. Includes merge with pypa/distutils@a7cfb56 per pypa/distutils#138.
4 changes: 1 addition & 3 deletions docs/deprecated/distutils/examples.rst
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions docs/deprecated/distutils/setupscript.rst
Expand Up @@ -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 | | |
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 20 additions & 0 deletions 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
6 changes: 1 addition & 5 deletions setuptools/_distutils/command/bdist_msi.py
Expand Up @@ -231,11 +231,7 @@ def run(self):
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
Expand Down
8 changes: 4 additions & 4 deletions setuptools/_distutils/command/bdist_rpm.py
Expand Up @@ -399,7 +399,7 @@ def _make_spec_file(self):
'%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
Expand Down Expand Up @@ -438,7 +438,7 @@ def _make_spec_file(self):
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}', ])
Expand All @@ -464,7 +464,7 @@ def _make_spec_file(self):
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:
Expand All @@ -483,7 +483,7 @@ def _make_spec_file(self):
spec_file.extend([
'',
'%description',
self.distribution.get_long_description()
self.distribution.get_long_description() or "",
])

# put locale descriptions into spec file
Expand Down
43 changes: 4 additions & 39 deletions setuptools/_distutils/command/check.py
Expand Up @@ -82,54 +82,19 @@ def check_metadata(self):
"""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."""
Expand Down
55 changes: 28 additions & 27 deletions setuptools/_distutils/dist.py
Expand Up @@ -1064,9 +1064,8 @@ def read_pkg_file(self, file):

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)
Expand Down Expand Up @@ -1125,23 +1124,24 @@ def write_pkg_file(self, file):
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())
Expand All @@ -1152,6 +1152,7 @@ def write_pkg_file(self, file):
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))

Expand All @@ -1167,35 +1168,35 @@ def get_fullname(self):
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 []
Expand All @@ -1204,7 +1205,7 @@ def set_keywords(self, value):
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')
Expand All @@ -1216,7 +1217,7 @@ def set_classifiers(self, value):
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):
Expand Down
22 changes: 18 additions & 4 deletions setuptools/_distutils/sysconfig.py
Expand Up @@ -16,6 +16,7 @@

from .errors import DistutilsPlatformError
from . import py39compat
from ._functools import pass_none

IS_PYPY = '__pypy__' in sys.builtin_module_names

Expand Down Expand Up @@ -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)

Expand Down
11 changes: 5 additions & 6 deletions setuptools/_distutils/tests/test_check.py
Expand Up @@ -43,7 +43,7 @@ def test_check_metadata(self):
# 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
Expand Down Expand Up @@ -81,17 +81,16 @@ def test_check_author_maintainer(self):
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):
Expand Down