diff --git a/.travis/py34.sh b/.travis/py34.sh index 41a8fc6f7..a5590087a 100755 --- a/.travis/py34.sh +++ b/.travis/py34.sh @@ -1,11 +1,4 @@ #!/bin/sh - -# Get the Source Code -cd .. -hg clone http://hg.python.org/cpython - -# Build Python -cd cpython -./configure -make -j8 -sudo make install +sudo add-apt-repository -y ppa:fkrull/deadsnakes +sudo apt-get update +sudo apt-get install python3.4 diff --git a/pip/wheel.py b/pip/wheel.py index 839259df4..4e9803f23 100644 --- a/pip/wheel.py +++ b/pip/wheel.py @@ -134,10 +134,11 @@ def get_entrypoints(filename): def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None, - pycompile=True): + pycompile=True, scheme=None): """Install a wheel""" - scheme = distutils_scheme(name, user=user, home=home, root=root) + if not scheme: + scheme = distutils_scheme(name, user=user, home=home, root=root) if root_is_purelib(name, wheeldir): lib_dir = scheme['purelib'] @@ -177,6 +178,7 @@ def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None, for dir, subdirs, files in os.walk(source): basedir = dir[len(source):].lstrip(os.path.sep) + destdir = os.path.join(dest, basedir) if is_base and basedir.split(os.path.sep, 1)[0].endswith('.data'): continue for s in subdirs: @@ -190,15 +192,21 @@ def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None, and s.lower().startswith(req.project_name.replace('-', '_').lower())): assert not info_dir, 'Multiple .dist-info directories' info_dir.append(destsubdir) - if not os.path.exists(destsubdir): - os.makedirs(destsubdir) for f in files: # Skip unwanted files if filter and filter(f): continue srcfile = os.path.join(dir, f) destfile = os.path.join(dest, basedir, f) - shutil.move(srcfile, destfile) + # directory creation is lazy and after the file filtering above + # to ensure we don't install empty dirs; empty dirs can't be + # uninstalled. + if not os.path.exists(destdir): + os.makedirs(destdir) + # use copy2 (not move) to be extra sure we're not moving + # directories over; copy2 fails for directories. this would + # fail tests (not during released/user execution) + shutil.copy2(srcfile, destfile) changed = False if fixer: changed = fixer(destfile) diff --git a/tests/data/packages/sample-1.2.0-py2.py3-none-any.whl b/tests/data/packages/sample-1.2.0-py2.py3-none-any.whl new file mode 100644 index 000000000..5a64d6b41 Binary files /dev/null and b/tests/data/packages/sample-1.2.0-py2.py3-none-any.whl differ diff --git a/tests/data/src/sample/MANIFEST.in b/tests/data/src/sample/MANIFEST.in new file mode 100644 index 000000000..df5350843 --- /dev/null +++ b/tests/data/src/sample/MANIFEST.in @@ -0,0 +1,8 @@ +include DESCRIPTION.rst + +# Include the test suite (FIXME: does not work yet) +# recursive-include tests * + +# If using Python 2.6 or less, then have to include package data, even though +# it's already declared in setup.py +include sample/*.dat diff --git a/tests/data/src/sample/data/data_file b/tests/data/src/sample/data/data_file new file mode 100644 index 000000000..7c0646bfd --- /dev/null +++ b/tests/data/src/sample/data/data_file @@ -0,0 +1 @@ +some data \ No newline at end of file diff --git a/tests/data/src/sample/sample/__init__.py b/tests/data/src/sample/sample/__init__.py new file mode 100644 index 000000000..c1699a747 --- /dev/null +++ b/tests/data/src/sample/sample/__init__.py @@ -0,0 +1,5 @@ +__version__ = '1.2.0' + +def main(): + """Entry point for the application script""" + print("Call your main application code here") diff --git a/tests/data/src/sample/sample/package_data.dat b/tests/data/src/sample/sample/package_data.dat new file mode 100644 index 000000000..7c0646bfd --- /dev/null +++ b/tests/data/src/sample/sample/package_data.dat @@ -0,0 +1 @@ +some data \ No newline at end of file diff --git a/tests/data/src/sample/setup.cfg b/tests/data/src/sample/setup.cfg new file mode 100644 index 000000000..79bc67848 --- /dev/null +++ b/tests/data/src/sample/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +# This flag says that the code is written to work on both Python 2 and Python +# 3. If at all possible, it is good practice to do this. If you cannot, you +# will need to generate wheels for each Python version that you support. +universal=1 diff --git a/tests/data/src/sample/setup.py b/tests/data/src/sample/setup.py new file mode 100644 index 000000000..c86ab8a3c --- /dev/null +++ b/tests/data/src/sample/setup.py @@ -0,0 +1,103 @@ +from setuptools import setup, find_packages +import codecs +import os +import re + +here = os.path.abspath(os.path.dirname(__file__)) + +# Read the version number from a source file. +# Why read it, and not import? +# see https://groups.google.com/d/topic/pypa-dev/0PkjVpcxTzQ/discussion +def find_version(*file_paths): + # Open in Latin-1 so that we avoid encoding errors. + # Use codecs.open for Python 2 compatibility + with codecs.open(os.path.join(here, *file_paths), 'r', 'latin1') as f: + version_file = f.read() + + # The version line must have the form + # __version__ = 'ver' + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + + +# Get the long description from the relevant file +with codecs.open('DESCRIPTION.rst', encoding='utf-8') as f: + long_description = f.read() + +setup( + name="sample", + version=find_version('sample', '__init__.py'), + description="A sample Python project", + long_description=long_description, + + # The project URL. + url='https://github.com/pypa/sampleproject', + + # Author details + author='The Python Packaging Authority', + author_email='pypa-dev@googlegroups.com', + + # Choose your license + license='MIT', + + classifiers=[ + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 3 - Alpha', + + # Indicate who your project is intended for + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Build Tools', + + # Pick your license as you wish (should match "license" above) + 'License :: OSI Approved :: MIT License', + + # Specify the Python versions you support here. In particular, ensure + # that you indicate whether you support Python 2, Python 3 or both. + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.1', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + ], + + # What does your project relate to? + keywords='sample setuptools development', + + # You can just specify the packages manually here if your project is + # simple. Or you can use find_packages. + packages=find_packages(exclude=["contrib", "docs", "tests*"]), + + # List run-time dependencies here. These will be installed by pip when your + # project is installed. + install_requires = ['peppercorn'], + + # If there are data files included in your packages that need to be + # installed, specify them here. If using Python 2.6 or less, then these + # have to be included in MANIFEST.in as well. + package_data={ + 'sample': ['package_data.dat'], + }, + + # Although 'package_data' is the preferred approach, in some case you may + # need to place data files outside of your packages. + # see http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files + # In this case, 'data_file' will be installed into '/my_data' + data_files=[('my_data', ['data/data_file'])], + + # To provide executable scripts, use entry points in preference to the + # "scripts" keyword. Entry points provide cross-platform support and allow + # pip to create the appropriate form of executable for the target platform. + entry_points={ + 'console_scripts': [ + 'sample=sample:main', + ], + }, +) diff --git a/tests/functional/test_uninstall.py b/tests/functional/test_uninstall.py index 9b408de6b..3e5add755 100644 --- a/tests/functional/test_uninstall.py +++ b/tests/functional/test_uninstall.py @@ -228,3 +228,14 @@ def test_uninstallpathset_non_local(mock_logger): uninstall_set = UninstallPathSet(test_dist) uninstall_set.remove() #with no files added to set; which is the case when trying to remove non-local dists mock_logger.notify.assert_any_call("Not uninstalling pip at %s, outside environment %s" % (nonlocal_path, sys.prefix)), mock_logger.notify.mock_calls + +def test_uninstall_wheel(script, data): + """ + Test uninstalling a wheel + """ + package = data.packages.join("simple.dist-0.1-py2.py3-none-any.whl") + result = script.pip('install', package, '--no-index') + dist_info_folder = script.site_packages / 'simple.dist-0.1.dist-info' + assert dist_info_folder in result.files_created + result2 = script.pip('uninstall', 'simple.dist', '-y') + assert_all_changes(result, result2, []) diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index 7f0607fba..1a7bb63bb 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -9,7 +9,7 @@ from pip import wheel from pip.download import path_to_url as path_to_url_d from pip.locations import write_delete_marker_file from pip.status_codes import PREVIOUS_BUILD_DIR_ERROR -from tests.lib import pyversion_nodot, path_to_url +from tests.lib import pyversion, path_to_url def test_pip_wheel_fails_without_wheel(script, data): @@ -26,7 +26,7 @@ def test_pip_wheel_success(script, data): """ script.pip('install', 'wheel') result = script.pip('wheel', '--no-index', '-f', data.find_links, 'simple==3.0') - wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion_nodot + wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion[0] wheel_file_path = script.scratch/'wheelhouse'/wheel_file_name assert wheel_file_path in result.files_created, result.stdout assert "Successfully built simple" in result.stdout, result.stdout @@ -52,7 +52,7 @@ def test_pip_wheel_fail(script, data): """ script.pip('install', 'wheel') result = script.pip('wheel', '--no-index', '-f', data.find_links, 'wheelbroken==0.1') - wheel_file_name = 'wheelbroken-0.1-py%s-none-any.whl' % pyversion_nodot + wheel_file_name = 'wheelbroken-0.1-py%s-none-any.whl' % pyversion[0] wheel_file_path = script.scratch/'wheelhouse'/wheel_file_name assert wheel_file_path not in result.files_created, (wheel_file_path, result.files_created) assert "FakeError" in result.stdout, result.stdout @@ -73,7 +73,7 @@ def test_pip_wheel_ignore_wheels_editables(script, data): simple """ % (local_wheel, local_editable))) result = script.pip('wheel', '--no-index', '-f', data.find_links, '-r', script.scratch_path / 'reqs.txt') - wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion_nodot + wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion[0] wheel_file_path = script.scratch/'wheelhouse'/wheel_file_name assert wheel_file_path in result.files_created, (wheel_file_path, result.files_created) assert "Successfully built simple" in result.stdout, result.stdout @@ -102,9 +102,9 @@ def test_pip_wheel_source_deps(script, data): # 'requires_source' is a wheel that depends on the 'source' project script.pip('install', 'wheel') result = script.pip('wheel', '--use-wheel', '--no-index', '-f', data.find_links, 'requires_source') - wheel_file_name = 'source-1.0-py%s-none-any.whl' % pyversion_nodot + wheel_file_name = 'source-1.0-py%s-none-any.whl' % pyversion[0] wheel_file_path = script.scratch/'wheelhouse'/wheel_file_name - assert wheel_file_path in result.files_created, result.stdout + assert wheel_file_path in result.files_created, result.files_created assert "Successfully built source" in result.stdout, result.stdout diff --git a/tests/unit/test_locations.py b/tests/unit/test_locations.py index 01e6f7585..1e1cc2437 100644 --- a/tests/unit/test_locations.py +++ b/tests/unit/test_locations.py @@ -28,7 +28,7 @@ class TestLocations: self.username = "example" self.patch() - def tearDown(self): + def teardown(self): self.revert_patch() shutil.rmtree(self.tempdir, ignore_errors=True) diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index 9b1061b9f..074a16d19 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -2,14 +2,12 @@ import os import pytest +from mock import patch -import pkg_resources -from mock import patch, Mock +from pip._vendor import pkg_resources from pip import wheel -from pip.exceptions import ( - InstallationError, InvalidWheelFilename, UnsupportedWheel, -) -from pip.index import PackageFinder +from pip.exceptions import InvalidWheelFilename, UnsupportedWheel +from pip.req import InstallRequirement from pip.util import unpack_file from tests.lib import assert_raises_regexp @@ -225,3 +223,56 @@ class TestPEP425Tags(object): with patch('pip.pep425tags.sysconfig.get_config_var', raises_ioerror): assert len(pip.pep425tags.get_supported()) +class TestMoveWheelFiles(object): + """ + Tests for moving files from wheel src to scheme paths + """ + + def prep(self, data, tmpdir): + self.name = 'sample' + self.wheelpath = data.packages.join('sample-1.2.0-py2.py3-none-any.whl') + self.req = pkg_resources.Requirement.parse('sample') + self.src = os.path.join(tmpdir, 'src') + self.dest = os.path.join(tmpdir, 'dest') + unpack_file(self.wheelpath, self.src, None, None) + self.scheme = { + 'scripts': os.path.join(self.dest, 'bin'), + 'purelib': os.path.join(self.dest, 'lib'), + 'data': os.path.join(self.dest, 'data'), + } + self.src_dist_info = os.path.join( + self.src, 'sample-1.2.0.dist-info') + self.dest_dist_info = os.path.join( + self.scheme['purelib'], 'sample-1.2.0.dist-info') + + def assert_installed(self): + # lib + assert os.path.isdir( + os.path.join(self.scheme['purelib'], 'sample')) + # dist-info + metadata = os.path.join(self.dest_dist_info, 'METADATA') + assert os.path.isfile(metadata) + # data files + data_file = os.path.join(self.scheme['data'], 'my_data', 'data_file') + assert os.path.isfile(data_file) + # package data + pkg_data = os.path.join(self.scheme['purelib'], 'sample', 'package_data.dat') + + def test_std_install(self, data, tmpdir): + self.prep(data, tmpdir) + wheel.move_wheel_files(self.name, self.req, self.src, scheme=self.scheme) + self.assert_installed() + + def test_dist_info_contains_empty_dir(self, data, tmpdir): + """ + Test that empty dirs are not installed + """ + # e.g. https://github.com/pypa/pip/issues/1632#issuecomment-38027275 + self.prep(data, tmpdir) + src_empty_dir = os.path.join(self.src_dist_info, 'empty_dir', 'empty_dir') + os.makedirs(src_empty_dir) + assert os.path.isdir(src_empty_dir) + wheel.move_wheel_files(self.name, self.req, self.src, scheme=self.scheme) + self.assert_installed() + assert not os.path.isdir(os.path.join(self.dest_dist_info, 'empty_dir')) +