aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml3
-rw-r--r--changelog.d/1352.misc.rst1
-rw-r--r--changelog.d/1353.doc.rst1
-rw-r--r--changelog.d/1356.doc.rst1
-rw-r--r--changelog.d/1359.change.rst2
-rw-r--r--changelog.d/1360.change.rst1
-rw-r--r--changelog.d/1365.change.rst2
-rw-r--r--changelog.d/1368.misc.rst1
-rw-r--r--changelog.d/1369.misc.rst1
-rw-r--r--changelog.d/1372.misc.rst2
-rw-r--r--changelog.d/1376.doc.rst1
-rw-r--r--docs/releases.txt37
-rw-r--r--docs/setuptools.txt6
-rw-r--r--netlify.toml5
-rw-r--r--pkg_resources/__init__.py6
-rw-r--r--setuptools/config.py47
-rw-r--r--setuptools/tests/test_config.py70
-rw-r--r--setuptools/tests/test_egg_info.py66
-rw-r--r--setuptools/tests/test_glibc.py37
-rw-r--r--setuptools/tests/test_pep425tags.py164
-rw-r--r--setuptools/tests/test_wheel.py33
-rw-r--r--setuptools/wheel.py16
-rw-r--r--towncrier_template.rst1
23 files changed, 423 insertions, 81 deletions
diff --git a/.travis.yml b/.travis.yml
index 9f09c0fa..63d0333a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,6 @@ sudo: false
language: python
python:
- &latest_py2 2.7
-- 3.3
- 3.4
- 3.5
- &latest_py3 3.6
@@ -43,7 +42,7 @@ install:
# ensure we have recent pip/setuptools
- pip install --upgrade pip setuptools
# need tox to get started
-- pip install tox 'tox-venv; python_version!="3.3"'
+- pip install tox tox-venv
# Output the env, to verify behavior
- env
diff --git a/changelog.d/1352.misc.rst b/changelog.d/1352.misc.rst
new file mode 100644
index 00000000..331ce47b
--- /dev/null
+++ b/changelog.d/1352.misc.rst
@@ -0,0 +1 @@
+Added ``tox`` environment for documentation builds.
diff --git a/changelog.d/1353.doc.rst b/changelog.d/1353.doc.rst
new file mode 100644
index 00000000..f23fec8f
--- /dev/null
+++ b/changelog.d/1353.doc.rst
@@ -0,0 +1 @@
+Added coverage badge to README.
diff --git a/changelog.d/1356.doc.rst b/changelog.d/1356.doc.rst
new file mode 100644
index 00000000..7ea0e830
--- /dev/null
+++ b/changelog.d/1356.doc.rst
@@ -0,0 +1 @@
+Made small fixes to the developer guide documentation.
diff --git a/changelog.d/1359.change.rst b/changelog.d/1359.change.rst
new file mode 100644
index 00000000..05746e73
--- /dev/null
+++ b/changelog.d/1359.change.rst
@@ -0,0 +1,2 @@
+Support using "file:" to load a PEP 440-compliant package version
+from a text file.
diff --git a/changelog.d/1360.change.rst b/changelog.d/1360.change.rst
new file mode 100644
index 00000000..36f40483
--- /dev/null
+++ b/changelog.d/1360.change.rst
@@ -0,0 +1 @@
+Fixed issue with a mismatch between the name of the package and the name of the .dist-info file in wheel files
diff --git a/changelog.d/1365.change.rst b/changelog.d/1365.change.rst
new file mode 100644
index 00000000..63eb008b
--- /dev/null
+++ b/changelog.d/1365.change.rst
@@ -0,0 +1,2 @@
+Take the package_dir option into account when loading the version from a
+module attribute.
diff --git a/changelog.d/1368.misc.rst b/changelog.d/1368.misc.rst
new file mode 100644
index 00000000..289e6a68
--- /dev/null
+++ b/changelog.d/1368.misc.rst
@@ -0,0 +1 @@
+Fixed tests which failed without network connectivity.
diff --git a/changelog.d/1369.misc.rst b/changelog.d/1369.misc.rst
new file mode 100644
index 00000000..d1803f23
--- /dev/null
+++ b/changelog.d/1369.misc.rst
@@ -0,0 +1 @@
+Added unit tests for PEP 425 compatibility tags support.
diff --git a/changelog.d/1372.misc.rst b/changelog.d/1372.misc.rst
new file mode 100644
index 00000000..d1bc789b
--- /dev/null
+++ b/changelog.d/1372.misc.rst
@@ -0,0 +1,2 @@
+Stop testing Python 3.3 in Travis CI, now that the latest version of
+``wheel`` no longer installs on it.
diff --git a/changelog.d/1376.doc.rst b/changelog.d/1376.doc.rst
new file mode 100644
index 00000000..c2dcacda
--- /dev/null
+++ b/changelog.d/1376.doc.rst
@@ -0,0 +1 @@
+Updated release process docs.
diff --git a/docs/releases.txt b/docs/releases.txt
index 30ea084f..234f69ee 100644
--- a/docs/releases.txt
+++ b/docs/releases.txt
@@ -7,20 +7,31 @@ mechanical technique for releases, enacted by Travis following a
successful build of a tagged release per
`PyPI deployment <https://docs.travis-ci.com/user/deployment/pypi>`_.
-Prior to cutting a release, please check that the CHANGES.rst reflects
-the summary of changes since the last release.
-Ideally, these changelog entries would have been added
-along with the changes, but it's always good to check.
-Think about it from the
-perspective of a user not involved with the development--what would
-that person want to know about what has changed--or from the
-perspective of your future self wanting to know when a particular
-change landed.
-
-To cut a release, install and run ``bump2version {part}`` where ``part``
+Prior to cutting a release, please use `towncrier`_ to update
+``CHANGES.rst`` to summarize the changes since the last release.
+To update the changelog:
+
+1. Install towncrier via ``pip install towncrier`` if not already installed.
+2. Preview the new ``CHANGES.rst`` entry by running
+ ``towncrier --draft --version {new.version.number}`` (enter the desired
+ version number for the next release). If any changes are needed, make
+ them and generate a new preview until the output is acceptable. Run
+ ``git add`` for any modified files.
+3. Run ``towncrier --version {new.version.number}`` to stage the changelog
+ updates in git.
+
+Once the changelog edits are staged and ready to commit, cut a release by
+installing and running ``bump2version {part}`` where ``part``
is major, minor, or patch based on the scope of the changes in the
-release. Then, push the commits to the master branch. If tests pass,
-the release will be uploaded to PyPI (from the Python 3.6 tests).
+release. Then, push the commits to the master branch::
+
+ $ git push origin master
+ $ git push --tags
+
+If tests pass, the release will be uploaded to PyPI (from the Python 3.6
+tests).
+
+.. _towncrier: https://pypi.org/project/towncrier/
Release Frequency
-----------------
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index 76830e41..f7b9351b 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -2424,7 +2424,7 @@ Metadata
Key Aliases Type
============================== ================= =====
name str
-version attr:, str
+version attr:, file:, str
url home-page str
download_url download-url str
project_urls dict
@@ -2444,6 +2444,10 @@ requires list-comma
obsoletes list-comma
============================== ================= =====
+.. note::
+ A version loaded using the ``file:`` directive must comply with PEP 440.
+ It is easy to accidentally put something other than a valid version
+ string in such a file, so validation is stricter in this case.
Options
-------
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 00000000..ec21e7be
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,5 @@
+# Configuration for pull request documentation previews via Netlify
+
+[build]
+ publish = "docs/build/html"
+ command = "pip install tox && tox -e docs"
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 394930ca..4e4409b3 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -377,11 +377,7 @@ def get_build_platform():
XXX Currently this is the same as ``distutils.util.get_platform()``, but it
needs some hacks for Linux and Mac OS X.
"""
- try:
- # Python 2.7 or >=3.2
- from sysconfig import get_platform
- except ImportError:
- from distutils.util import get_platform
+ from sysconfig import get_platform
plat = get_platform()
if sys.platform == "darwin" and not plat.startswith('macosx-'):
diff --git a/setuptools/config.py b/setuptools/config.py
index 8eddcae8..d3f0b123 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -7,6 +7,7 @@ from functools import partial
from importlib import import_module
from distutils.errors import DistutilsOptionError, DistutilsFileError
+from setuptools.extern.packaging.version import LegacyVersion, parse
from setuptools.extern.six import string_types
@@ -101,14 +102,14 @@ def parse_configuration(
If False exceptions are propagated as expected.
:rtype: list
"""
- meta = ConfigMetadataHandler(
- distribution.metadata, command_options, ignore_option_errors)
- meta.parse()
-
options = ConfigOptionsHandler(
distribution, command_options, ignore_option_errors)
options.parse()
+ meta = ConfigMetadataHandler(
+ distribution.metadata, command_options, ignore_option_errors, distribution.package_dir)
+ meta.parse()
+
return meta, options
@@ -280,7 +281,7 @@ class ConfigHandler(object):
return f.read()
@classmethod
- def _parse_attr(cls, value):
+ def _parse_attr(cls, value, package_dir=None):
"""Represents value as a module attribute.
Examples:
@@ -300,7 +301,21 @@ class ConfigHandler(object):
module_name = '.'.join(attrs_path)
module_name = module_name or '__init__'
- sys.path.insert(0, os.getcwd())
+ parent_path = os.getcwd()
+ if package_dir:
+ if attrs_path[0] in package_dir:
+ # A custom path was specified for the module we want to import
+ custom_path = package_dir[attrs_path[0]]
+ parts = custom_path.rsplit('/', 1)
+ if len(parts) > 1:
+ parent_path = os.path.join(os.getcwd(), parts[0])
+ module_name = parts[1]
+ else:
+ module_name = custom_path
+ elif '' in package_dir:
+ # A custom parent directory was specified for all root modules
+ parent_path = os.path.join(os.getcwd(), package_dir[''])
+ sys.path.insert(0, parent_path)
try:
module = import_module(module_name)
value = getattr(module, attr_name)
@@ -399,6 +414,12 @@ class ConfigMetadataHandler(ConfigHandler):
"""
+ def __init__(self, target_obj, options, ignore_option_errors=False,
+ package_dir=None):
+ super(ConfigMetadataHandler, self).__init__(target_obj, options,
+ ignore_option_errors)
+ self.package_dir = package_dir
+
@property
def parsers(self):
"""Metadata item name to parser function mapping."""
@@ -427,7 +448,19 @@ class ConfigMetadataHandler(ConfigHandler):
:rtype: str
"""
- version = self._parse_attr(value)
+ version = self._parse_file(value)
+
+ if version != value:
+ version = version.strip()
+ # Be strict about versions loaded from file because it's easy to
+ # accidentally include newlines and other unintended content
+ if isinstance(parse(version), LegacyVersion):
+ raise DistutilsOptionError('Version loaded from %s does not comply with PEP 440: %s' % (
+ value, version
+ ))
+ return version
+
+ version = self._parse_attr(value, self.package_dir)
if callable(version):
version = version()
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index abb953a8..de7c8b4d 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -10,13 +10,15 @@ class ErrConfigHandler(ConfigHandler):
def make_package_dir(name, base_dir):
- dir_package = base_dir.mkdir(name)
+ dir_package = base_dir
+ for dir_name in name.split('/'):
+ dir_package = dir_package.mkdir(dir_name)
init_file = dir_package.join('__init__.py')
init_file.write('')
return dir_package, init_file
-def fake_env(tmpdir, setup_cfg, setup_py=None):
+def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'):
if setup_py is None:
setup_py = (
@@ -28,7 +30,7 @@ def fake_env(tmpdir, setup_cfg, setup_py=None):
config = tmpdir.join('setup.cfg')
config.write(setup_cfg)
- package_dir, init_file = make_package_dir('fake_package', tmpdir)
+ package_dir, init_file = make_package_dir(package_path, tmpdir)
init_file.write(
'VERSION = (1, 2, 3)\n'
@@ -268,6 +270,68 @@ class TestMetadata:
with get_dist(tmpdir) as dist:
assert dist.metadata.version == '2016.11.26'
+ def test_version_file(self, tmpdir):
+
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = file: fake_package/version.txt\n'
+ )
+ tmpdir.join('fake_package', 'version.txt').write('1.2.3\n')
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
+ tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n')
+ with pytest.raises(DistutilsOptionError):
+ with get_dist(tmpdir) as dist:
+ _ = dist.metadata.version
+
+ def test_version_with_package_dir_simple(self, tmpdir):
+
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = attr: fake_package_simple.VERSION\n'
+ '[options]\n'
+ 'package_dir =\n'
+ ' = src\n',
+ package_path='src/fake_package_simple'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
+ def test_version_with_package_dir_rename(self, tmpdir):
+
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = attr: fake_package_rename.VERSION\n'
+ '[options]\n'
+ 'package_dir =\n'
+ ' fake_package_rename = fake_dir\n',
+ package_path='fake_dir'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
+ def test_version_with_package_dir_complex(self, tmpdir):
+
+ _, config = fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'version = attr: fake_package_complex.VERSION\n'
+ '[options]\n'
+ 'package_dir =\n'
+ ' fake_package_complex = src/fake_dir\n',
+ package_path='src/fake_dir'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.version == '1.2.3'
+
def test_unknown_meta_item(self, tmpdir):
fake_env(
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index 2a070deb..8b3b90f7 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -128,11 +128,11 @@ class TestEggInfo(object):
self._validate_content_order(content, expected_order)
- def test_egg_base_installed_egg_info(self, tmpdir_cwd, env):
+ def test_expected_files_produced(self, tmpdir_cwd, env):
self._create_project()
- self._run_install_command(tmpdir_cwd, env)
- actual = self._find_egg_info_files(env.paths['lib'])
+ self._run_egg_info_command(tmpdir_cwd, env)
+ actual = os.listdir('foo.egg-info')
expected = [
'PKG-INFO',
@@ -154,8 +154,8 @@ class TestEggInfo(object):
'usage.rst': "Run 'hi'",
}
})
- self._run_install_command(tmpdir_cwd, env)
- egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
+ self._run_egg_info_command(tmpdir_cwd, env)
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
sources_txt = os.path.join(egg_info_dir, 'SOURCES.txt')
with open(sources_txt) as f:
assert 'docs/usage.rst' in f.read().split('\n')
@@ -233,27 +233,27 @@ class TestEggInfo(object):
'''
install_requires_deterministic
- install_requires=["fake-factory==0.5.2", "pytz"]
+ install_requires=["wheel>=0.5", "pytest"]
[options]
install_requires =
- fake-factory==0.5.2
- pytz
+ wheel>=0.5
+ pytest
- fake-factory==0.5.2
- pytz
+ wheel>=0.5
+ pytest
''',
'''
install_requires_ordered
- install_requires=["fake-factory>=1.12.3,!=2.0"]
+ install_requires=["pytest>=3.0.2,!=10.9999"]
[options]
install_requires =
- fake-factory>=1.12.3,!=2.0
+ pytest>=3.0.2,!=10.9999
- fake-factory!=2.0,>=1.12.3
+ pytest!=10.9999,>=3.0.2
''',
'''
@@ -394,7 +394,7 @@ class TestEggInfo(object):
self, tmpdir_cwd, env, requires, use_setup_cfg,
expected_requires, install_cmd_kwargs):
self._setup_script_with_requires(requires, use_setup_cfg)
- self._run_install_command(tmpdir_cwd, env, **install_cmd_kwargs)
+ self._run_egg_info_command(tmpdir_cwd, env, **install_cmd_kwargs)
egg_info_dir = os.path.join('.', 'foo.egg-info')
requires_txt = os.path.join(egg_info_dir, 'requires.txt')
if os.path.exists(requires_txt):
@@ -414,14 +414,14 @@ class TestEggInfo(object):
req = 'install_requires={"fake-factory==0.5.2", "pytz"}'
self._setup_script_with_requires(req)
with pytest.raises(AssertionError):
- self._run_install_command(tmpdir_cwd, env)
+ self._run_egg_info_command(tmpdir_cwd, env)
def test_extras_require_with_invalid_marker(self, tmpdir_cwd, env):
tmpl = 'extras_require={{":{marker}": ["barbazquux"]}},'
req = tmpl.format(marker=self.invalid_marker)
self._setup_script_with_requires(req)
with pytest.raises(AssertionError):
- self._run_install_command(tmpdir_cwd, env)
+ self._run_egg_info_command(tmpdir_cwd, env)
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
def test_extras_require_with_invalid_marker_in_req(self, tmpdir_cwd, env):
@@ -429,7 +429,7 @@ class TestEggInfo(object):
req = tmpl.format(marker=self.invalid_marker)
self._setup_script_with_requires(req)
with pytest.raises(AssertionError):
- self._run_install_command(tmpdir_cwd, env)
+ self._run_egg_info_command(tmpdir_cwd, env)
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
def test_provides_extra(self, tmpdir_cwd, env):
@@ -541,15 +541,6 @@ class TestEggInfo(object):
assert 'Requires-Python: >=2.7.12' in pkg_info_lines
assert 'Metadata-Version: 1.2' in pkg_info_lines
- def test_python_requires_install(self, tmpdir_cwd, env):
- self._setup_script_with_requires(
- """python_requires='>=1.2.3',""")
- self._run_install_command(tmpdir_cwd, env)
- egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
- pkginfo = os.path.join(egg_info_dir, 'PKG-INFO')
- with open(pkginfo) as f:
- assert 'Requires-Python: >=1.2.3' in f.read().split('\n')
-
def test_manifest_maker_warning_suppression(self):
fixtures = [
"standard file not found: should have one of foo.py, bar.py",
@@ -559,17 +550,13 @@ class TestEggInfo(object):
for msg in fixtures:
assert manifest_maker._should_suppress_warning(msg)
- def _run_install_command(self, tmpdir_cwd, env, cmd=None, output=None):
+ def _run_egg_info_command(self, tmpdir_cwd, env, cmd=None, output=None):
environ = os.environ.copy().update(
HOME=env.paths['home'],
)
if cmd is None:
cmd = [
- 'install',
- '--home', env.paths['home'],
- '--install-lib', env.paths['lib'],
- '--install-scripts', env.paths['scripts'],
- '--install-data', env.paths['data'],
+ 'egg_info',
]
code, data = environment.run_setup_py(
cmd=cmd,
@@ -581,18 +568,3 @@ class TestEggInfo(object):
raise AssertionError(data)
if output:
assert output in data
-
- def _find_egg_info_files(self, root):
- class DirList(list):
- def __init__(self, files, base):
- super(DirList, self).__init__(files)
- self.base = base
-
- results = (
- DirList(filenames, dirpath)
- for dirpath, dirnames, filenames in os.walk(root)
- if os.path.basename(dirpath) == 'EGG-INFO'
- )
- # expect exactly one result
- result, = results
- return result
diff --git a/setuptools/tests/test_glibc.py b/setuptools/tests/test_glibc.py
new file mode 100644
index 00000000..9cb97964
--- /dev/null
+++ b/setuptools/tests/test_glibc.py
@@ -0,0 +1,37 @@
+import warnings
+
+from setuptools.glibc import check_glibc_version
+
+
+class TestGlibc(object):
+ def test_manylinux1_check_glibc_version(self):
+ """
+ Test that the check_glibc_version function is robust against weird
+ glibc version strings.
+ """
+ for two_twenty in ["2.20",
+ # used by "linaro glibc", see gh-3588
+ "2.20-2014.11",
+ # weird possibilities that I just made up
+ "2.20+dev",
+ "2.20-custom",
+ "2.20.1",
+ ]:
+ assert check_glibc_version(two_twenty, 2, 15)
+ assert check_glibc_version(two_twenty, 2, 20)
+ assert not check_glibc_version(two_twenty, 2, 21)
+ assert not check_glibc_version(two_twenty, 3, 15)
+ assert not check_glibc_version(two_twenty, 1, 15)
+
+ # For strings that we just can't parse at all, we should warn and
+ # return false
+ for bad_string in ["asdf", "", "foo.bar"]:
+ with warnings.catch_warnings(record=True) as ws:
+ warnings.filterwarnings("always")
+ assert not check_glibc_version(bad_string, 2, 5)
+ for w in ws:
+ if "Expected glibc version with" in str(w.message):
+ break
+ else:
+ # Didn't find the warning we were expecting
+ assert False
diff --git a/setuptools/tests/test_pep425tags.py b/setuptools/tests/test_pep425tags.py
new file mode 100644
index 00000000..0f60e0ed
--- /dev/null
+++ b/setuptools/tests/test_pep425tags.py
@@ -0,0 +1,164 @@
+import sys
+
+from mock import patch
+
+from setuptools import pep425tags
+
+
+class TestPEP425Tags(object):
+
+ def mock_get_config_var(self, **kwd):
+ """
+ Patch sysconfig.get_config_var for arbitrary keys.
+ """
+ get_config_var = pep425tags.sysconfig.get_config_var
+
+ def _mock_get_config_var(var):
+ if var in kwd:
+ return kwd[var]
+ return get_config_var(var)
+ return _mock_get_config_var
+
+ def abi_tag_unicode(self, flags, config_vars):
+ """
+ Used to test ABI tags, verify correct use of the `u` flag
+ """
+ config_vars.update({'SOABI': None})
+ base = pep425tags.get_abbr_impl() + pep425tags.get_impl_ver()
+
+ if sys.version_info < (3, 3):
+ config_vars.update({'Py_UNICODE_SIZE': 2})
+ mock_gcf = self.mock_get_config_var(**config_vars)
+ with patch('setuptools.pep425tags.sysconfig.get_config_var', mock_gcf):
+ abi_tag = pep425tags.get_abi_tag()
+ assert abi_tag == base + flags
+
+ config_vars.update({'Py_UNICODE_SIZE': 4})
+ mock_gcf = self.mock_get_config_var(**config_vars)
+ with patch('setuptools.pep425tags.sysconfig.get_config_var',
+ mock_gcf):
+ abi_tag = pep425tags.get_abi_tag()
+ assert abi_tag == base + flags + 'u'
+
+ else:
+ # On Python >= 3.3, UCS-4 is essentially permanently enabled, and
+ # Py_UNICODE_SIZE is None. SOABI on these builds does not include
+ # the 'u' so manual SOABI detection should not do so either.
+ config_vars.update({'Py_UNICODE_SIZE': None})
+ mock_gcf = self.mock_get_config_var(**config_vars)
+ with patch('setuptools.pep425tags.sysconfig.get_config_var',
+ mock_gcf):
+ abi_tag = pep425tags.get_abi_tag()
+ assert abi_tag == base + flags
+
+ def test_broken_sysconfig(self):
+ """
+ Test that pep425tags still works when sysconfig is broken.
+ Can be a problem on Python 2.7
+ Issue #1074.
+ """
+ def raises_ioerror(var):
+ raise IOError("I have the wrong path!")
+
+ with patch('setuptools.pep425tags.sysconfig.get_config_var',
+ raises_ioerror):
+ assert len(pep425tags.get_supported())
+
+ def test_no_hyphen_tag(self):
+ """
+ Test that no tag contains a hyphen.
+ """
+ mock_gcf = self.mock_get_config_var(SOABI='cpython-35m-darwin')
+
+ with patch('setuptools.pep425tags.sysconfig.get_config_var',
+ mock_gcf):
+ supported = pep425tags.get_supported()
+
+ for (py, abi, plat) in supported:
+ assert '-' not in py
+ assert '-' not in abi
+ assert '-' not in plat
+
+ def test_manual_abi_noflags(self):
+ """
+ Test that no flags are set on a non-PyDebug, non-Pymalloc ABI tag.
+ """
+ self.abi_tag_unicode('', {'Py_DEBUG': False, 'WITH_PYMALLOC': False})
+
+ def test_manual_abi_d_flag(self):
+ """
+ Test that the `d` flag is set on a PyDebug, non-Pymalloc ABI tag.
+ """
+ self.abi_tag_unicode('d', {'Py_DEBUG': True, 'WITH_PYMALLOC': False})
+
+ def test_manual_abi_m_flag(self):
+ """
+ Test that the `m` flag is set on a non-PyDebug, Pymalloc ABI tag.
+ """
+ self.abi_tag_unicode('m', {'Py_DEBUG': False, 'WITH_PYMALLOC': True})
+
+ def test_manual_abi_dm_flags(self):
+ """
+ Test that the `dm` flags are set on a PyDebug, Pymalloc ABI tag.
+ """
+ self.abi_tag_unicode('dm', {'Py_DEBUG': True, 'WITH_PYMALLOC': True})
+
+
+class TestManylinux1Tags(object):
+
+ @patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64')
+ @patch('setuptools.glibc.have_compatible_glibc',
+ lambda major, minor: True)
+ def test_manylinux1_compatible_on_linux_x86_64(self):
+ """
+ Test that manylinux1 is enabled on linux_x86_64
+ """
+ assert pep425tags.is_manylinux1_compatible()
+
+ @patch('setuptools.pep425tags.get_platform', lambda: 'linux_i686')
+ @patch('setuptools.glibc.have_compatible_glibc',
+ lambda major, minor: True)
+ def test_manylinux1_compatible_on_linux_i686(self):
+ """
+ Test that manylinux1 is enabled on linux_i686
+ """
+ assert pep425tags.is_manylinux1_compatible()
+
+ @patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64')
+ @patch('setuptools.glibc.have_compatible_glibc',
+ lambda major, minor: False)
+ def test_manylinux1_2(self):
+ """
+ Test that manylinux1 is disabled with incompatible glibc
+ """
+ assert not pep425tags.is_manylinux1_compatible()
+
+ @patch('setuptools.pep425tags.get_platform', lambda: 'arm6vl')
+ @patch('setuptools.glibc.have_compatible_glibc',
+ lambda major, minor: True)
+ def test_manylinux1_3(self):
+ """
+ Test that manylinux1 is disabled on arm6vl
+ """
+ assert not pep425tags.is_manylinux1_compatible()
+
+ @patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64')
+ @patch('setuptools.glibc.have_compatible_glibc',
+ lambda major, minor: True)
+ @patch('sys.platform', 'linux2')
+ def test_manylinux1_tag_is_first(self):
+ """
+ Test that the more specific tag manylinux1 comes first.
+ """
+ groups = {}
+ for pyimpl, abi, arch in pep425tags.get_supported():
+ groups.setdefault((pyimpl, abi), []).append(arch)
+
+ for arches in groups.values():
+ if arches == ['any']:
+ continue
+ # Expect the most specific arch first:
+ if len(arches) == 3:
+ assert arches == ['manylinux1_x86_64', 'linux_x86_64', 'any']
+ else:
+ assert arches == ['manylinux1_x86_64', 'linux_x86_64']
diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py
index 150ac4c1..cf650868 100644
--- a/setuptools/tests/test_wheel.py
+++ b/setuptools/tests/test_wheel.py
@@ -9,12 +9,15 @@ import contextlib
import glob
import inspect
import os
+import shutil
import subprocess
import sys
+import zipfile
import pytest
from pkg_resources import Distribution, PathMetadata, PY_MAJOR
+from setuptools.extern.packaging.utils import canonicalize_name
from setuptools.wheel import Wheel
from .contexts import tempdir
@@ -506,3 +509,33 @@ def test_wheel_install(params):
_check_wheel_install(filename, install_dir,
install_tree, project_name,
version, requires_txt)
+
+
+def test_wheel_install_pep_503():
+ project_name = 'Foo_Bar' # PEP 503 canonicalized name is "foo-bar"
+ version = '1.0'
+ with build_wheel(
+ name=project_name,
+ version=version,
+ ) as filename, tempdir() as install_dir:
+ new_filename = filename.replace(project_name,
+ canonicalize_name(project_name))
+ shutil.move(filename, new_filename)
+ _check_wheel_install(new_filename, install_dir, None,
+ canonicalize_name(project_name),
+ version, None)
+
+
+def test_wheel_no_dist_dir():
+ project_name = 'nodistinfo'
+ version = '1.0'
+ wheel_name = '{0}-{1}-py2.py3-none-any.whl'.format(project_name, version)
+ with tempdir() as source_dir:
+ wheel_path = os.path.join(source_dir, wheel_name)
+ # create an empty zip file
+ zipfile.ZipFile(wheel_path, 'w').close()
+ with tempdir() as install_dir:
+ with pytest.raises(ValueError):
+ _check_wheel_install(wheel_path, install_dir, None,
+ project_name,
+ version, None)
diff --git a/setuptools/wheel.py b/setuptools/wheel.py
index 37dfa531..4a33b203 100644
--- a/setuptools/wheel.py
+++ b/setuptools/wheel.py
@@ -4,10 +4,12 @@ from distutils.util import get_platform
import email
import itertools
import os
+import posixpath
import re
import zipfile
from pkg_resources import Distribution, PathMetadata, parse_version
+from setuptools.extern.packaging.utils import canonicalize_name
from setuptools.extern.six import PY3
from setuptools import Distribution as SetuptoolsDistribution
from setuptools import pep425tags
@@ -77,14 +79,24 @@ class Wheel(object):
platform=(None if self.platform == 'any' else get_platform()),
).egg_name() + '.egg'
+ def get_dist_info(self, zf):
+ # find the correct name of the .dist-info dir in the wheel file
+ for member in zf.namelist():
+ dirname = posixpath.dirname(member)
+ if (dirname.endswith('.dist-info') and
+ canonicalize_name(dirname).startswith(
+ canonicalize_name(self.project_name))):
+ return dirname
+ raise ValueError("unsupported wheel format. .dist-info not found")
+
def install_as_egg(self, destination_eggdir):
'''Install wheel as an egg directory.'''
with zipfile.ZipFile(self.filename) as zf:
dist_basename = '%s-%s' % (self.project_name, self.version)
- dist_info = '%s.dist-info' % dist_basename
+ dist_info = self.get_dist_info(zf)
dist_data = '%s.data' % dist_basename
def get_metadata(name):
- with zf.open('%s/%s' % (dist_info, name)) as fp:
+ with zf.open(posixpath.join(dist_info, name)) as fp:
value = fp.read().decode('utf-8') if PY3 else fp.read()
return email.parser.Parser().parsestr(value)
wheel_metadata = get_metadata('WHEEL')
diff --git a/towncrier_template.rst b/towncrier_template.rst
index 02b2882b..9c23b977 100644
--- a/towncrier_template.rst
+++ b/towncrier_template.rst
@@ -8,7 +8,6 @@
{% for text, values in sections[section][category].items() %}
* {{ values|join(', ') }}: {{ text }}
{% endfor %}
-
{% else %}
* {{ sections[section][category]['']|join(', ') }}