diff options
| -rw-r--r-- | .travis.yml | 3 | ||||
| -rw-r--r-- | changelog.d/1352.misc.rst | 1 | ||||
| -rw-r--r-- | changelog.d/1353.doc.rst | 1 | ||||
| -rw-r--r-- | changelog.d/1356.doc.rst | 1 | ||||
| -rw-r--r-- | changelog.d/1359.change.rst | 2 | ||||
| -rw-r--r-- | changelog.d/1360.change.rst | 1 | ||||
| -rw-r--r-- | changelog.d/1365.change.rst | 2 | ||||
| -rw-r--r-- | changelog.d/1368.misc.rst | 1 | ||||
| -rw-r--r-- | changelog.d/1369.misc.rst | 1 | ||||
| -rw-r--r-- | changelog.d/1372.misc.rst | 2 | ||||
| -rw-r--r-- | changelog.d/1376.doc.rst | 1 | ||||
| -rw-r--r-- | docs/releases.txt | 37 | ||||
| -rw-r--r-- | docs/setuptools.txt | 6 | ||||
| -rw-r--r-- | netlify.toml | 5 | ||||
| -rw-r--r-- | pkg_resources/__init__.py | 6 | ||||
| -rw-r--r-- | setuptools/config.py | 47 | ||||
| -rw-r--r-- | setuptools/tests/test_config.py | 70 | ||||
| -rw-r--r-- | setuptools/tests/test_egg_info.py | 66 | ||||
| -rw-r--r-- | setuptools/tests/test_glibc.py | 37 | ||||
| -rw-r--r-- | setuptools/tests/test_pep425tags.py | 164 | ||||
| -rw-r--r-- | setuptools/tests/test_wheel.py | 33 | ||||
| -rw-r--r-- | setuptools/wheel.py | 16 | ||||
| -rw-r--r-- | towncrier_template.rst | 1 |
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(', ') }} |
