summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcus Smith <qwcode@gmail.com>2014-04-25 11:58:31 -0700
committerMarcus Smith <qwcode@gmail.com>2014-04-25 11:58:31 -0700
commit7f355a64b59eaa7e4cf70e6ccb6fb1a0a5f2118a (patch)
tree7a0d30296ab14cd8e6637d4c4f7bdb1d048549f9
parent627cb5591a183de8080b27fbb3c795e31f609fbe (diff)
parent79408cbc6fa5d61b74b046105aee61f12311adc9 (diff)
downloadpip-7f355a64b59eaa7e4cf70e6ccb6fb1a0a5f2118a.tar.gz
Merge pull request #1743 from qwcode/empty_dirs
don't install empty dirs during wheel installs
-rwxr-xr-x.travis/py34.sh13
-rw-r--r--pip/wheel.py18
-rw-r--r--tests/data/packages/sample-1.2.0-py2.py3-none-any.whlbin0 -> 3581 bytes
-rw-r--r--tests/data/src/sample/MANIFEST.in8
-rw-r--r--tests/data/src/sample/data/data_file1
-rw-r--r--tests/data/src/sample/sample/__init__.py5
-rw-r--r--tests/data/src/sample/sample/package_data.dat1
-rw-r--r--tests/data/src/sample/setup.cfg5
-rw-r--r--tests/data/src/sample/setup.py103
-rw-r--r--tests/functional/test_uninstall.py11
-rw-r--r--tests/functional/test_wheel.py12
-rw-r--r--tests/unit/test_locations.py2
-rw-r--r--tests/unit/test_wheel.py63
13 files changed, 214 insertions, 28 deletions
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
--- /dev/null
+++ b/tests/data/packages/sample-1.2.0-py2.py3-none-any.whl
Binary files 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 '<sys.prefix>/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'))
+