[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#771953: unblock: python-pip/1.5.6-4 (pre-upload check)



Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock

Please unblock package python-pip

We expect to shortly have a patch to resolve the RC bug #771794.  We would
also like to include an additional fix for CVE-2014-8991 (which is not RC)
Please see the attached debdiff.  As an aid for review, I'm also attaching
two individual file diffs with the whitespace removed (since this Python,
there are whitespace changes that matter, but it's a lot harder to read).

Please let us know if we can include this.

unblock python-pip/1.5.6-4


*** install.nowhite.diff
--- python-pip-1.5.6/pip/commands/install.py	2014-05-16 22:19:02.000000000 -0400
+++ python-pip-1.5.6.new/pip/commands/install.py	2014-12-03 14:04:16.896561138 -0500
@@ -10,6 +10,7 @@
 from pip.index import PackageFinder
 from pip.exceptions import InstallationError, CommandError, PreviousBuildDirError
 from pip import cmdoptions
+from pip.util import BuildDirectory
 
 
 class InstallCommand(Command):
@@ -188,7 +189,7 @@
         if (
             options.no_install or
             options.no_download or
-            (options.build_dir != build_prefix) or
+            options.build_dir or
             options.no_clean
         ):
             logger.deprecated('1.7', 'DEPRECATION: --no-install, --no-download, --build, '
@@ -197,7 +198,16 @@
         if options.download_dir:
             options.no_install = True
             options.ignore_installed = True
+
+        # If we have --no-install or --no-download and no --build we use the
+        # legacy static build dir
+        if (options.build_dir is None
+                and (options.no_install or options.no_download)):
+            options.build_dir = build_prefix
+
+        if options.build_dir:
         options.build_dir = os.path.abspath(options.build_dir)
+
         options.src_dir = os.path.abspath(options.src_dir)
         install_options = options.install_options or []
         if options.use_user_site:
@@ -237,8 +247,10 @@
 
         finder = self._build_package_finder(options, index_urls, session)
 
+        build_delete = (not (options.no_clean or options.build_dir))
+        with BuildDirectory(options.build_dir, delete=build_delete) as build_dir:
         requirement_set = RequirementSet(
-            build_dir=options.build_dir,
+                build_dir=build_dir,
             src_dir=options.src_dir,
             download_dir=options.download_dir,
             download_cache=options.download_cache,
diff -ruN python-pip-1.5.6/debian/changelog python-pip-1.5.6.new/debian/changelog
--- python-pip-1.5.6/debian/changelog	2014-11-17 14:18:45.000000000 -0500
+++ python-pip-1.5.6.new/debian/changelog	2014-12-03 14:04:16.888561137 -0500
@@ -1,3 +1,12 @@
+python-pip (1.5.6-4) UNRELEASED; urgency=medium
+
+  * Team upload.
+  * Backport upstream fix to use non-predictable download directories
+    - Fixes denial of service vector (CVE-2014-8991) (Closes: #725847)
+    - Fixes retry failures (Closes: #769930)
+
+ -- Scott Kitterman <scott@kitterman.com>  Wed, 03 Dec 2014 13:46:31 -0500
+
 python-pip (1.5.6-3) unstable; urgency=medium
 
   * Team upload.
diff -ruN python-pip-1.5.6/debian/patches/random-install-dir.patch python-pip-1.5.6.new/debian/patches/random-install-dir.patch
--- python-pip-1.5.6/debian/patches/random-install-dir.patch	1969-12-31 19:00:00.000000000 -0500
+++ python-pip-1.5.6.new/debian/patches/random-install-dir.patch	2014-12-03 14:04:16.888561137 -0500
@@ -0,0 +1,394 @@
+Description: Use randomized install directory
+ python-pip (1.5.6-4) UNRELEASED; urgency=medium
+ .
+   * Team upload.
+   * Backport upstream fix to use non-predictable download directories
+     - Fixes denial of service vector (CVE-2014-8991) (Closes: #725847)
+     - Fixes retry failures (Closes: #769930)
+Author: Donald Stufft <donald@stufft.io>
+Bug-Debian: http://bugs.debian.org/725847
+Bug-Debian: http://bugs.debian.org/769930
+Origin: https://github.com/pypa/pip/pull/2122
+Forwarded: not-needed
+Reviewed-By: Scott Kitterman <scott@kitterman.com>
+Last-Update: 2014-12-03
+
+--- python-pip-1.5.6.orig/pip/cmdoptions.py
++++ python-pip-1.5.6/pip/cmdoptions.py
+@@ -9,7 +9,7 @@ To be consistent, all options will follo
+ """
+ import copy
+ from optparse import OptionGroup, SUPPRESS_HELP, Option
+-from pip.locations import build_prefix, default_log_file
++from pip.locations import default_log_file
+ 
+ 
+ def make_option_group(group, parser):
+@@ -297,10 +297,8 @@ build_dir = OptionMaker(
+     '-b', '--build', '--build-dir', '--build-directory',
+     dest='build_dir',
+     metavar='dir',
+-    default=build_prefix,
+-    help='Directory to unpack packages into and build in. '
+-    'The default in a virtualenv is "<venv path>/build". '
+-    'The default for global installs is "<OS temp dir>/pip_build_<username>".')
++    help='Directory to unpack packages into and build in.',
++)
+ 
+ install_options = OptionMaker(
+     '--install-option',
+--- python-pip-1.5.6.orig/pip/commands/install.py
++++ python-pip-1.5.6/pip/commands/install.py
+@@ -10,6 +10,7 @@ from pip.basecommand import Command
+ from pip.index import PackageFinder
+ from pip.exceptions import InstallationError, CommandError, PreviousBuildDirError
+ from pip import cmdoptions
++from pip.util import BuildDirectory
+ 
+ 
+ class InstallCommand(Command):
+@@ -188,7 +189,7 @@ class InstallCommand(Command):
+         if (
+             options.no_install or
+             options.no_download or
+-            (options.build_dir != build_prefix) or
++            options.build_dir or
+             options.no_clean
+         ):
+             logger.deprecated('1.7', 'DEPRECATION: --no-install, --no-download, --build, '
+@@ -197,7 +198,16 @@ class InstallCommand(Command):
+         if options.download_dir:
+             options.no_install = True
+             options.ignore_installed = True
+-        options.build_dir = os.path.abspath(options.build_dir)
++
++        # If we have --no-install or --no-download and no --build we use the
++        # legacy static build dir
++        if (options.build_dir is None
++                and (options.no_install or options.no_download)):
++            options.build_dir = build_prefix
++
++        if options.build_dir:
++            options.build_dir = os.path.abspath(options.build_dir)
++
+         options.src_dir = os.path.abspath(options.src_dir)
+         install_options = options.install_options or []
+         if options.use_user_site:
+@@ -237,69 +247,71 @@ class InstallCommand(Command):
+ 
+         finder = self._build_package_finder(options, index_urls, session)
+ 
+-        requirement_set = RequirementSet(
+-            build_dir=options.build_dir,
+-            src_dir=options.src_dir,
+-            download_dir=options.download_dir,
+-            download_cache=options.download_cache,
+-            upgrade=options.upgrade,
+-            as_egg=options.as_egg,
+-            ignore_installed=options.ignore_installed,
+-            ignore_dependencies=options.ignore_dependencies,
+-            force_reinstall=options.force_reinstall,
+-            use_user_site=options.use_user_site,
+-            target_dir=temp_target_dir,
+-            session=session,
+-            pycompile=options.compile,
+-        )
+-        for name in args:
+-            requirement_set.add_requirement(
+-                InstallRequirement.from_line(name, None))
+-        for name in options.editables:
+-            requirement_set.add_requirement(
+-                InstallRequirement.from_editable(name, default_vcs=options.default_vcs))
+-        for filename in options.requirements:
+-            for req in parse_requirements(filename, finder=finder, options=options, session=session):
+-                requirement_set.add_requirement(req)
+-        if not requirement_set.has_requirements:
+-            opts = {'name': self.name}
+-            if options.find_links:
+-                msg = ('You must give at least one requirement to %(name)s '
+-                       '(maybe you meant "pip %(name)s %(links)s"?)' %
+-                       dict(opts, links=' '.join(options.find_links)))
+-            else:
+-                msg = ('You must give at least one requirement '
+-                       'to %(name)s (see "pip help %(name)s")' % opts)
+-            logger.warn(msg)
+-            return
+-
+-        try:
+-            if not options.no_download:
+-                requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle)
+-            else:
+-                requirement_set.locate_files()
+-
+-            if not options.no_install and not self.bundle:
+-                requirement_set.install(install_options, global_options, root=options.root_path)
+-                installed = ' '.join([req.name for req in
+-                                      requirement_set.successfully_installed])
+-                if installed:
+-                    logger.notify('Successfully installed %s' % installed)
+-            elif not self.bundle:
+-                downloaded = ' '.join([req.name for req in
+-                                       requirement_set.successfully_downloaded])
+-                if downloaded:
+-                    logger.notify('Successfully downloaded %s' % downloaded)
+-            elif self.bundle:
+-                requirement_set.create_bundle(self.bundle_filename)
+-                logger.notify('Created bundle in %s' % self.bundle_filename)
+-        except PreviousBuildDirError:
+-            options.no_clean = True
+-            raise
+-        finally:
+-            # Clean up
+-            if (not options.no_clean) and ((not options.no_install) or options.download_dir):
+-                requirement_set.cleanup_files(bundle=self.bundle)
++        build_delete = (not (options.no_clean or options.build_dir))
++        with BuildDirectory(options.build_dir, delete=build_delete) as build_dir:
++            requirement_set = RequirementSet(
++                build_dir=build_dir,
++                src_dir=options.src_dir,
++                download_dir=options.download_dir,
++                download_cache=options.download_cache,
++                upgrade=options.upgrade,
++                as_egg=options.as_egg,
++                ignore_installed=options.ignore_installed,
++                ignore_dependencies=options.ignore_dependencies,
++                force_reinstall=options.force_reinstall,
++                use_user_site=options.use_user_site,
++                target_dir=temp_target_dir,
++                session=session,
++                pycompile=options.compile,
++            )
++            for name in args:
++                requirement_set.add_requirement(
++                    InstallRequirement.from_line(name, None))
++            for name in options.editables:
++                requirement_set.add_requirement(
++                    InstallRequirement.from_editable(name, default_vcs=options.default_vcs))
++            for filename in options.requirements:
++                for req in parse_requirements(filename, finder=finder, options=options, session=session):
++                    requirement_set.add_requirement(req)
++            if not requirement_set.has_requirements:
++                opts = {'name': self.name}
++                if options.find_links:
++                    msg = ('You must give at least one requirement to %(name)s '
++                           '(maybe you meant "pip %(name)s %(links)s"?)' %
++                           dict(opts, links=' '.join(options.find_links)))
++                else:
++                    msg = ('You must give at least one requirement '
++                           'to %(name)s (see "pip help %(name)s")' % opts)
++                logger.warn(msg)
++                return
++
++            try:
++                if not options.no_download:
++                    requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle)
++                else:
++                    requirement_set.locate_files()
++
++                if not options.no_install and not self.bundle:
++                    requirement_set.install(install_options, global_options, root=options.root_path)
++                    installed = ' '.join([req.name for req in
++                                          requirement_set.successfully_installed])
++                    if installed:
++                        logger.notify('Successfully installed %s' % installed)
++                elif not self.bundle:
++                    downloaded = ' '.join([req.name for req in
++                                           requirement_set.successfully_downloaded])
++                    if downloaded:
++                        logger.notify('Successfully downloaded %s' % downloaded)
++                elif self.bundle:
++                    requirement_set.create_bundle(self.bundle_filename)
++                    logger.notify('Created bundle in %s' % self.bundle_filename)
++            except PreviousBuildDirError:
++                options.no_clean = True
++                raise
++            finally:
++                # Clean up
++                if (not options.no_clean) and ((not options.no_install) or options.download_dir):
++                    requirement_set.cleanup_files(bundle=self.bundle)
+ 
+         if options.target_dir:
+             if not os.path.exists(options.target_dir):
+--- python-pip-1.5.6.orig/pip/commands/wheel.py
++++ python-pip-1.5.6/pip/commands/wheel.py
+@@ -8,7 +8,7 @@ from pip.index import PackageFinder
+ from pip.log import logger
+ from pip.exceptions import CommandError, PreviousBuildDirError
+ from pip.req import InstallRequirement, RequirementSet, parse_requirements
+-from pip.util import normalize_path
++from pip.util import BuildDirectory, normalize_path
+ from pip.wheel import WheelBuilder
+ from pip import cmdoptions
+ 
+@@ -127,6 +127,9 @@ class WheelCommand(Command):
+                         "--extra-index-url is suggested.")
+             index_urls += options.mirrors
+ 
++        if options.build_dir:
++            options.build_dir = os.path.abspath(options.build_dir)
++
+         session = self._build_session(options)
+ 
+         finder = PackageFinder(find_links=options.find_links,
+@@ -141,59 +144,60 @@ class WheelCommand(Command):
+                                session=session,
+                             )
+ 
+-        options.build_dir = os.path.abspath(options.build_dir)
+-        requirement_set = RequirementSet(
+-            build_dir=options.build_dir,
+-            src_dir=None,
+-            download_dir=None,
+-            download_cache=options.download_cache,
+-            ignore_dependencies=options.ignore_dependencies,
+-            ignore_installed=True,
+-            session=session,
+-            wheel_download_dir=options.wheel_dir
+-        )
+-
+-        # make the wheelhouse
+-        if not os.path.exists(options.wheel_dir):
+-            os.makedirs(options.wheel_dir)
+-
+-        #parse args and/or requirements files
+-        for name in args:
+-            requirement_set.add_requirement(
+-                InstallRequirement.from_line(name, None))
+-
+-        for filename in options.requirements:
+-            for req in parse_requirements(
+-                filename,
+-                finder=finder,
+-                options=options,
+-                session=session):
+-                if req.editable:
+-                    logger.notify("ignoring %s" % req.url)
+-                    continue
+-                requirement_set.add_requirement(req)
+-
+-        #fail if no requirements
+-        if not requirement_set.has_requirements:
+-            opts = {'name': self.name}
+-            msg = ('You must give at least one requirement '
+-                   'to %(name)s (see "pip help %(name)s")' % opts)
+-            logger.error(msg)
+-            return
++        build_delete = (not (options.no_clean or options.build_dir))
++        with BuildDirectory(options.build_dir, delete=build_delete) as build_dir:
++            requirement_set = RequirementSet(
++                build_dir=build_dir,
++                src_dir=None,
++                download_dir=None,
++                download_cache=options.download_cache,
++                ignore_dependencies=options.ignore_dependencies,
++                ignore_installed=True,
++                session=session,
++                wheel_download_dir=options.wheel_dir
++            )
+ 
+-        try:
+-            #build wheels
+-            wb = WheelBuilder(
+-                requirement_set,
+-                finder,
+-                options.wheel_dir,
+-                build_options = options.build_options or [],
+-                global_options = options.global_options or []
+-                )
+-            wb.build()
+-        except PreviousBuildDirError:
+-            options.no_clean = True
+-            raise
+-        finally:
+-            if not options.no_clean:
+-                requirement_set.cleanup_files()
++            # make the wheelhouse
++            if not os.path.exists(options.wheel_dir):
++                os.makedirs(options.wheel_dir)
++
++            #parse args and/or requirements files
++            for name in args:
++                requirement_set.add_requirement(
++                    InstallRequirement.from_line(name, None))
++
++            for filename in options.requirements:
++                for req in parse_requirements(
++                    filename,
++                    finder=finder,
++                    options=options,
++                    session=session):
++                    if req.editable:
++                        logger.notify("ignoring %s" % req.url)
++                        continue
++                    requirement_set.add_requirement(req)
++
++            #fail if no requirements
++            if not requirement_set.has_requirements:
++                opts = {'name': self.name}
++                msg = ('You must give at least one requirement '
++                       'to %(name)s (see "pip help %(name)s")' % opts)
++                logger.error(msg)
++                return
++
++            try:
++                #build wheels
++                wb = WheelBuilder(
++                    requirement_set,
++                    finder,
++                    options.wheel_dir,
++                    build_options = options.build_options or [],
++                    global_options = options.global_options or []
++                    )
++                wb.build()
++            except PreviousBuildDirError:
++                options.no_clean = True
++                raise
++            finally:
++                if not options.no_clean:
++                    requirement_set.cleanup_files()
+--- python-pip-1.5.6.orig/pip/util.py
++++ python-pip-1.5.6/pip/util.py
+@@ -8,6 +8,7 @@ import zipfile
+ import tarfile
+ import subprocess
+ import textwrap
++import tempfile
+ 
+ from pip.exceptions import InstallationError, BadCommand, PipError
+ from pip.backwardcompat import(WindowsError, string_types, raw_input,
+@@ -718,3 +719,35 @@ def is_prerelease(vers):
+ 
+     parsed = version._normalized_key(normalized)
+     return any([any([y in set(["a", "b", "c", "rc", "dev"]) for y in x]) for x in parsed])
++
++
++class BuildDirectory(object):
++
++    def __init__(self, name=None, delete=None):
++        # If we were not given an explicit directory, and we were not given an
++        # explicit delete option, then we'll default to deleting.
++        if name is None and delete is None:
++            delete = True
++
++        if name is None:
++            name = tempfile.mkdtemp(prefix="pip-build-")
++            # If we were not given an explicit directory, and we were not given
++            # an explicit delete option, then we'll default to deleting.
++            if delete is None:
++                delete = True
++
++        self.name = name
++        self.delete = delete
++
++    def __repr__(self):
++        return "<{} {!r}>".format(self.__class__.__name__, self.name)
++
++    def __enter__(self):
++        return self.name
++
++    def __exit__(self, exc, value, tb):
++        self.cleanup()
++
++    def cleanup(self):
++        if self.delete:
++            rmtree(self.name)
diff -ruN python-pip-1.5.6/debian/patches/series python-pip-1.5.6.new/debian/patches/series
--- python-pip-1.5.6/debian/patches/series	2014-11-17 01:59:33.000000000 -0500
+++ python-pip-1.5.6.new/debian/patches/series	2014-12-03 14:04:16.888561137 -0500
@@ -1,3 +1,4 @@
 de-vendorize.patch
 use-venv-wheels.patch
 better-error-message.patch
+random-install-dir.patch
diff -ruN python-pip-1.5.6/.pc/applied-patches python-pip-1.5.6.new/.pc/applied-patches
--- python-pip-1.5.6/.pc/applied-patches	2014-12-03 14:04:39.256562386 -0500
+++ python-pip-1.5.6.new/.pc/applied-patches	2014-12-03 14:04:16.884561137 -0500
@@ -1,3 +1,4 @@
 de-vendorize.patch
 use-venv-wheels.patch
 better-error-message.patch
+random-install-dir.patch
diff -ruN python-pip-1.5.6/pip/cmdoptions.py python-pip-1.5.6.new/pip/cmdoptions.py
--- python-pip-1.5.6/pip/cmdoptions.py	2014-05-16 22:19:02.000000000 -0400
+++ python-pip-1.5.6.new/pip/cmdoptions.py	2014-12-03 14:04:16.896561138 -0500
@@ -9,7 +9,7 @@
 """
 import copy
 from optparse import OptionGroup, SUPPRESS_HELP, Option
-from pip.locations import build_prefix, default_log_file
+from pip.locations import default_log_file
 
 
 def make_option_group(group, parser):
@@ -297,10 +297,8 @@
     '-b', '--build', '--build-dir', '--build-directory',
     dest='build_dir',
     metavar='dir',
-    default=build_prefix,
-    help='Directory to unpack packages into and build in. '
-    'The default in a virtualenv is "<venv path>/build". '
-    'The default for global installs is "<OS temp dir>/pip_build_<username>".')
+    help='Directory to unpack packages into and build in.',
+)
 
 install_options = OptionMaker(
     '--install-option',
diff -ruN python-pip-1.5.6/pip/commands/install.py python-pip-1.5.6.new/pip/commands/install.py
--- python-pip-1.5.6/pip/commands/install.py	2014-05-16 22:19:02.000000000 -0400
+++ python-pip-1.5.6.new/pip/commands/install.py	2014-12-03 14:04:16.896561138 -0500
@@ -10,6 +10,7 @@
 from pip.index import PackageFinder
 from pip.exceptions import InstallationError, CommandError, PreviousBuildDirError
 from pip import cmdoptions
+from pip.util import BuildDirectory
 
 
 class InstallCommand(Command):
@@ -188,7 +189,7 @@
         if (
             options.no_install or
             options.no_download or
-            (options.build_dir != build_prefix) or
+            options.build_dir or
             options.no_clean
         ):
             logger.deprecated('1.7', 'DEPRECATION: --no-install, --no-download, --build, '
@@ -197,7 +198,16 @@
         if options.download_dir:
             options.no_install = True
             options.ignore_installed = True
-        options.build_dir = os.path.abspath(options.build_dir)
+
+        # If we have --no-install or --no-download and no --build we use the
+        # legacy static build dir
+        if (options.build_dir is None
+                and (options.no_install or options.no_download)):
+            options.build_dir = build_prefix
+
+        if options.build_dir:
+            options.build_dir = os.path.abspath(options.build_dir)
+
         options.src_dir = os.path.abspath(options.src_dir)
         install_options = options.install_options or []
         if options.use_user_site:
@@ -237,69 +247,71 @@
 
         finder = self._build_package_finder(options, index_urls, session)
 
-        requirement_set = RequirementSet(
-            build_dir=options.build_dir,
-            src_dir=options.src_dir,
-            download_dir=options.download_dir,
-            download_cache=options.download_cache,
-            upgrade=options.upgrade,
-            as_egg=options.as_egg,
-            ignore_installed=options.ignore_installed,
-            ignore_dependencies=options.ignore_dependencies,
-            force_reinstall=options.force_reinstall,
-            use_user_site=options.use_user_site,
-            target_dir=temp_target_dir,
-            session=session,
-            pycompile=options.compile,
-        )
-        for name in args:
-            requirement_set.add_requirement(
-                InstallRequirement.from_line(name, None))
-        for name in options.editables:
-            requirement_set.add_requirement(
-                InstallRequirement.from_editable(name, default_vcs=options.default_vcs))
-        for filename in options.requirements:
-            for req in parse_requirements(filename, finder=finder, options=options, session=session):
-                requirement_set.add_requirement(req)
-        if not requirement_set.has_requirements:
-            opts = {'name': self.name}
-            if options.find_links:
-                msg = ('You must give at least one requirement to %(name)s '
-                       '(maybe you meant "pip %(name)s %(links)s"?)' %
-                       dict(opts, links=' '.join(options.find_links)))
-            else:
-                msg = ('You must give at least one requirement '
-                       'to %(name)s (see "pip help %(name)s")' % opts)
-            logger.warn(msg)
-            return
-
-        try:
-            if not options.no_download:
-                requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle)
-            else:
-                requirement_set.locate_files()
-
-            if not options.no_install and not self.bundle:
-                requirement_set.install(install_options, global_options, root=options.root_path)
-                installed = ' '.join([req.name for req in
-                                      requirement_set.successfully_installed])
-                if installed:
-                    logger.notify('Successfully installed %s' % installed)
-            elif not self.bundle:
-                downloaded = ' '.join([req.name for req in
-                                       requirement_set.successfully_downloaded])
-                if downloaded:
-                    logger.notify('Successfully downloaded %s' % downloaded)
-            elif self.bundle:
-                requirement_set.create_bundle(self.bundle_filename)
-                logger.notify('Created bundle in %s' % self.bundle_filename)
-        except PreviousBuildDirError:
-            options.no_clean = True
-            raise
-        finally:
-            # Clean up
-            if (not options.no_clean) and ((not options.no_install) or options.download_dir):
-                requirement_set.cleanup_files(bundle=self.bundle)
+        build_delete = (not (options.no_clean or options.build_dir))
+        with BuildDirectory(options.build_dir, delete=build_delete) as build_dir:
+            requirement_set = RequirementSet(
+                build_dir=build_dir,
+                src_dir=options.src_dir,
+                download_dir=options.download_dir,
+                download_cache=options.download_cache,
+                upgrade=options.upgrade,
+                as_egg=options.as_egg,
+                ignore_installed=options.ignore_installed,
+                ignore_dependencies=options.ignore_dependencies,
+                force_reinstall=options.force_reinstall,
+                use_user_site=options.use_user_site,
+                target_dir=temp_target_dir,
+                session=session,
+                pycompile=options.compile,
+            )
+            for name in args:
+                requirement_set.add_requirement(
+                    InstallRequirement.from_line(name, None))
+            for name in options.editables:
+                requirement_set.add_requirement(
+                    InstallRequirement.from_editable(name, default_vcs=options.default_vcs))
+            for filename in options.requirements:
+                for req in parse_requirements(filename, finder=finder, options=options, session=session):
+                    requirement_set.add_requirement(req)
+            if not requirement_set.has_requirements:
+                opts = {'name': self.name}
+                if options.find_links:
+                    msg = ('You must give at least one requirement to %(name)s '
+                           '(maybe you meant "pip %(name)s %(links)s"?)' %
+                           dict(opts, links=' '.join(options.find_links)))
+                else:
+                    msg = ('You must give at least one requirement '
+                           'to %(name)s (see "pip help %(name)s")' % opts)
+                logger.warn(msg)
+                return
+
+            try:
+                if not options.no_download:
+                    requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle)
+                else:
+                    requirement_set.locate_files()
+
+                if not options.no_install and not self.bundle:
+                    requirement_set.install(install_options, global_options, root=options.root_path)
+                    installed = ' '.join([req.name for req in
+                                          requirement_set.successfully_installed])
+                    if installed:
+                        logger.notify('Successfully installed %s' % installed)
+                elif not self.bundle:
+                    downloaded = ' '.join([req.name for req in
+                                           requirement_set.successfully_downloaded])
+                    if downloaded:
+                        logger.notify('Successfully downloaded %s' % downloaded)
+                elif self.bundle:
+                    requirement_set.create_bundle(self.bundle_filename)
+                    logger.notify('Created bundle in %s' % self.bundle_filename)
+            except PreviousBuildDirError:
+                options.no_clean = True
+                raise
+            finally:
+                # Clean up
+                if (not options.no_clean) and ((not options.no_install) or options.download_dir):
+                    requirement_set.cleanup_files(bundle=self.bundle)
 
         if options.target_dir:
             if not os.path.exists(options.target_dir):
diff -ruN python-pip-1.5.6/pip/commands/wheel.py python-pip-1.5.6.new/pip/commands/wheel.py
--- python-pip-1.5.6/pip/commands/wheel.py	2014-12-03 14:04:39.000000000 -0500
+++ python-pip-1.5.6.new/pip/commands/wheel.py	2014-12-03 14:04:16.896561138 -0500
@@ -8,7 +8,7 @@
 from pip.log import logger
 from pip.exceptions import CommandError, PreviousBuildDirError
 from pip.req import InstallRequirement, RequirementSet, parse_requirements
-from pip.util import normalize_path
+from pip.util import BuildDirectory, normalize_path
 from pip.wheel import WheelBuilder
 from pip import cmdoptions
 
@@ -127,6 +127,9 @@
                         "--extra-index-url is suggested.")
             index_urls += options.mirrors
 
+        if options.build_dir:
+            options.build_dir = os.path.abspath(options.build_dir)
+
         session = self._build_session(options)
 
         finder = PackageFinder(find_links=options.find_links,
@@ -141,59 +144,60 @@
                                session=session,
                             )
 
-        options.build_dir = os.path.abspath(options.build_dir)
-        requirement_set = RequirementSet(
-            build_dir=options.build_dir,
-            src_dir=None,
-            download_dir=None,
-            download_cache=options.download_cache,
-            ignore_dependencies=options.ignore_dependencies,
-            ignore_installed=True,
-            session=session,
-            wheel_download_dir=options.wheel_dir
-        )
-
-        # make the wheelhouse
-        if not os.path.exists(options.wheel_dir):
-            os.makedirs(options.wheel_dir)
-
-        #parse args and/or requirements files
-        for name in args:
-            requirement_set.add_requirement(
-                InstallRequirement.from_line(name, None))
-
-        for filename in options.requirements:
-            for req in parse_requirements(
-                filename,
-                finder=finder,
-                options=options,
-                session=session):
-                if req.editable:
-                    logger.notify("ignoring %s" % req.url)
-                    continue
-                requirement_set.add_requirement(req)
-
-        #fail if no requirements
-        if not requirement_set.has_requirements:
-            opts = {'name': self.name}
-            msg = ('You must give at least one requirement '
-                   'to %(name)s (see "pip help %(name)s")' % opts)
-            logger.error(msg)
-            return
+        build_delete = (not (options.no_clean or options.build_dir))
+        with BuildDirectory(options.build_dir, delete=build_delete) as build_dir:
+            requirement_set = RequirementSet(
+                build_dir=build_dir,
+                src_dir=None,
+                download_dir=None,
+                download_cache=options.download_cache,
+                ignore_dependencies=options.ignore_dependencies,
+                ignore_installed=True,
+                session=session,
+                wheel_download_dir=options.wheel_dir
+            )
 
-        try:
-            #build wheels
-            wb = WheelBuilder(
-                requirement_set,
-                finder,
-                options.wheel_dir,
-                build_options = options.build_options or [],
-                global_options = options.global_options or []
-                )
-            wb.build()
-        except PreviousBuildDirError:
-            options.no_clean = True
-            raise
-        finally:
-            if not options.no_clean:
-                requirement_set.cleanup_files()
+            # make the wheelhouse
+            if not os.path.exists(options.wheel_dir):
+                os.makedirs(options.wheel_dir)
+
+            #parse args and/or requirements files
+            for name in args:
+                requirement_set.add_requirement(
+                    InstallRequirement.from_line(name, None))
+
+            for filename in options.requirements:
+                for req in parse_requirements(
+                    filename,
+                    finder=finder,
+                    options=options,
+                    session=session):
+                    if req.editable:
+                        logger.notify("ignoring %s" % req.url)
+                        continue
+                    requirement_set.add_requirement(req)
+
+            #fail if no requirements
+            if not requirement_set.has_requirements:
+                opts = {'name': self.name}
+                msg = ('You must give at least one requirement '
+                       'to %(name)s (see "pip help %(name)s")' % opts)
+                logger.error(msg)
+                return
+
+            try:
+                #build wheels
+                wb = WheelBuilder(
+                    requirement_set,
+                    finder,
+                    options.wheel_dir,
+                    build_options = options.build_options or [],
+                    global_options = options.global_options or []
+                    )
+                wb.build()
+            except PreviousBuildDirError:
+                options.no_clean = True
+                raise
+            finally:
+                if not options.no_clean:
+                    requirement_set.cleanup_files()
diff -ruN python-pip-1.5.6/pip/util.py python-pip-1.5.6.new/pip/util.py
--- python-pip-1.5.6/pip/util.py	2014-12-03 14:04:39.000000000 -0500
+++ python-pip-1.5.6.new/pip/util.py	2014-12-03 14:04:16.892561137 -0500
@@ -8,6 +8,7 @@
 import tarfile
 import subprocess
 import textwrap
+import tempfile
 
 from pip.exceptions import InstallationError, BadCommand, PipError
 from pip.backwardcompat import(WindowsError, string_types, raw_input,
@@ -718,3 +719,35 @@
 
     parsed = version._normalized_key(normalized)
     return any([any([y in set(["a", "b", "c", "rc", "dev"]) for y in x]) for x in parsed])
+
+
+class BuildDirectory(object):
+
+    def __init__(self, name=None, delete=None):
+        # If we were not given an explicit directory, and we were not given an
+        # explicit delete option, then we'll default to deleting.
+        if name is None and delete is None:
+            delete = True
+
+        if name is None:
+            name = tempfile.mkdtemp(prefix="pip-build-")
+            # If we were not given an explicit directory, and we were not given
+            # an explicit delete option, then we'll default to deleting.
+            if delete is None:
+                delete = True
+
+        self.name = name
+        self.delete = delete
+
+    def __repr__(self):
+        return "<{} {!r}>".format(self.__class__.__name__, self.name)
+
+    def __enter__(self):
+        return self.name
+
+    def __exit__(self, exc, value, tb):
+        self.cleanup()
+
+    def cleanup(self):
+        if self.delete:
+            rmtree(self.name)
--- python-pip-1.5.6/pip/commands/wheel.py	2014-12-03 14:04:39.000000000 -0500
+++ python-pip-1.5.6.new/pip/commands/wheel.py	2014-12-03 14:04:16.896561138 -0500
@@ -8,7 +8,7 @@
 from pip.log import logger
 from pip.exceptions import CommandError, PreviousBuildDirError
 from pip.req import InstallRequirement, RequirementSet, parse_requirements
-from pip.util import normalize_path
+from pip.util import BuildDirectory, normalize_path
 from pip.wheel import WheelBuilder
 from pip import cmdoptions
 
@@ -127,6 +127,9 @@
                         "--extra-index-url is suggested.")
             index_urls += options.mirrors
 
+        if options.build_dir:
+            options.build_dir = os.path.abspath(options.build_dir)
+
         session = self._build_session(options)
 
         finder = PackageFinder(find_links=options.find_links,
@@ -141,9 +144,10 @@
                                session=session,
                             )
 
-        options.build_dir = os.path.abspath(options.build_dir)
+        build_delete = (not (options.no_clean or options.build_dir))
+        with BuildDirectory(options.build_dir, delete=build_delete) as build_dir:
         requirement_set = RequirementSet(
-            build_dir=options.build_dir,
+                build_dir=build_dir,
             src_dir=None,
             download_dir=None,
             download_cache=options.download_cache,
--- python-pip-1.5.6/pip/commands/install.py	2014-05-16 22:19:02.000000000 -0400
+++ python-pip-1.5.6.new/pip/commands/install.py	2014-12-03 14:04:16.896561138 -0500
@@ -10,6 +10,7 @@
 from pip.index import PackageFinder
 from pip.exceptions import InstallationError, CommandError, PreviousBuildDirError
 from pip import cmdoptions
+from pip.util import BuildDirectory
 
 
 class InstallCommand(Command):
@@ -188,7 +189,7 @@
         if (
             options.no_install or
             options.no_download or
-            (options.build_dir != build_prefix) or
+            options.build_dir or
             options.no_clean
         ):
             logger.deprecated('1.7', 'DEPRECATION: --no-install, --no-download, --build, '
@@ -197,7 +198,16 @@
         if options.download_dir:
             options.no_install = True
             options.ignore_installed = True
+
+        # If we have --no-install or --no-download and no --build we use the
+        # legacy static build dir
+        if (options.build_dir is None
+                and (options.no_install or options.no_download)):
+            options.build_dir = build_prefix
+
+        if options.build_dir:
         options.build_dir = os.path.abspath(options.build_dir)
+
         options.src_dir = os.path.abspath(options.src_dir)
         install_options = options.install_options or []
         if options.use_user_site:
@@ -237,8 +247,10 @@
 
         finder = self._build_package_finder(options, index_urls, session)
 
+        build_delete = (not (options.no_clean or options.build_dir))
+        with BuildDirectory(options.build_dir, delete=build_delete) as build_dir:
         requirement_set = RequirementSet(
-            build_dir=options.build_dir,
+                build_dir=build_dir,
             src_dir=options.src_dir,
             download_dir=options.download_dir,
             download_cache=options.download_cache,

Reply to: