1
1
Fork 0
mirror of https://github.com/pypa/pip synced 2023-12-13 21:30:23 +01:00

Use a secure randomized build directory when possible

This commit is contained in:
Donald Stufft 2014-11-11 19:19:32 -05:00
parent 043af838a5
commit 01610be0d5
7 changed files with 250 additions and 180 deletions

View file

@ -98,6 +98,9 @@
looking for any file located inside of it instead of relying on the record looking for any file located inside of it instead of relying on the record
file listing a directory. (:pull:`2076`) file listing a directory. (:pull:`2076`)
* Fixed :issue:`1964`, :issue:`1935`, :issue:`676`, Use a randomized and secure
default build directory when possible. (:pull:`2122`)
**1.5.6 (2014-05-16)** **1.5.6 (2014-05-16)**

View file

@ -11,9 +11,7 @@ from __future__ import absolute_import
import copy import copy
from optparse import OptionGroup, SUPPRESS_HELP, Option from optparse import OptionGroup, SUPPRESS_HELP, Option
from pip.locations import ( from pip.locations import CA_BUNDLE_PATH, USER_CACHE_DIR, src_prefix
CA_BUNDLE_PATH, USER_CACHE_DIR, build_prefix, src_prefix,
)
def make_option_group(group, parser): def make_option_group(group, parser):
@ -360,10 +358,8 @@ build_dir = OptionMaker(
'-b', '--build', '--build-dir', '--build-directory', '-b', '--build', '--build-dir', '--build-directory',
dest='build_dir', dest='build_dir',
metavar='dir', metavar='dir',
default=build_prefix, help='Directory to unpack packages into and build in.'
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>".')
install_options = OptionMaker( install_options = OptionMaker(
'--install-option', '--install-option',

View file

@ -7,13 +7,14 @@ import shutil
import warnings import warnings
from pip.req import InstallRequirement, RequirementSet, parse_requirements from pip.req import InstallRequirement, RequirementSet, parse_requirements
from pip.locations import virtualenv_no_global, distutils_scheme from pip.locations import build_prefix, virtualenv_no_global, distutils_scheme
from pip.basecommand import Command from pip.basecommand import Command
from pip.index import PackageFinder from pip.index import PackageFinder
from pip.exceptions import ( from pip.exceptions import (
InstallationError, CommandError, PreviousBuildDirError, InstallationError, CommandError, PreviousBuildDirError,
) )
from pip import cmdoptions from pip import cmdoptions
from pip.utils.build import BuildDirectory
from pip.utils.deprecation import RemovedInPip7Warning, RemovedInPip8Warning from pip.utils.deprecation import RemovedInPip7Warning, RemovedInPip8Warning
@ -209,7 +210,16 @@ class InstallCommand(Command):
if options.download_dir: if options.download_dir:
options.no_install = True options.no_install = True
options.ignore_installed = 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.build_dir = os.path.abspath(options.build_dir)
options.src_dir = os.path.abspath(options.src_dir) options.src_dir = os.path.abspath(options.src_dir)
install_options = options.install_options or [] install_options = options.install_options or []
if options.use_user_site: if options.use_user_site:
@ -268,8 +278,11 @@ class InstallCommand(Command):
finder = self._build_package_finder(options, index_urls, session) 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( requirement_set = RequirementSet(
build_dir=options.build_dir, build_dir=build_dir,
src_dir=options.src_dir, src_dir=options.src_dir,
download_dir=options.download_dir, download_dir=options.download_dir,
upgrade=options.upgrade, upgrade=options.upgrade,
@ -282,9 +295,11 @@ class InstallCommand(Command):
session=session, session=session,
pycompile=options.compile, pycompile=options.compile,
) )
for name in args: for name in args:
requirement_set.add_requirement( requirement_set.add_requirement(
InstallRequirement.from_line(name, None)) InstallRequirement.from_line(name, None))
for name in options.editables: for name in options.editables:
requirement_set.add_requirement( requirement_set.add_requirement(
InstallRequirement.from_editable( InstallRequirement.from_editable(
@ -292,16 +307,19 @@ class InstallCommand(Command):
default_vcs=options.default_vcs default_vcs=options.default_vcs
) )
) )
for filename in options.requirements: for filename in options.requirements:
for req in parse_requirements( for req in parse_requirements(
filename, filename,
finder=finder, options=options, session=session): finder=finder, options=options, session=session):
requirement_set.add_requirement(req) requirement_set.add_requirement(req)
if not requirement_set.has_requirements: if not requirement_set.has_requirements:
opts = {'name': self.name} opts = {'name': self.name}
if options.find_links: if options.find_links:
msg = ('You must give at least one requirement to %(name)s' msg = ('You must give at least one requirement to '
' (maybe you meant "pip %(name)s %(links)s"?)' % '%(name)s (maybe you meant "pip %(name)s '
'%(links)s"?)' %
dict(opts, links=' '.join(options.find_links))) dict(opts, links=' '.join(options.find_links)))
else: else:
msg = ('You must give at least one requirement ' msg = ('You must give at least one requirement '
@ -333,7 +351,9 @@ class InstallCommand(Command):
for req in requirement_set.successfully_downloaded for req in requirement_set.successfully_downloaded
]) ])
if downloaded: if downloaded:
logger.info('Successfully downloaded %s', downloaded) logger.info(
'Successfully downloaded %s', downloaded
)
except PreviousBuildDirError: except PreviousBuildDirError:
options.no_clean = True options.no_clean = True
raise raise
@ -347,7 +367,9 @@ class InstallCommand(Command):
if options.target_dir: if options.target_dir:
if not os.path.exists(options.target_dir): if not os.path.exists(options.target_dir):
os.makedirs(options.target_dir) os.makedirs(options.target_dir)
lib_dir = distutils_scheme('', home=temp_target_dir)['purelib'] lib_dir = distutils_scheme('', home=temp_target_dir)['purelib']
for item in os.listdir(lib_dir): for item in os.listdir(lib_dir):
target_item_dir = os.path.join(options.target_dir, item) target_item_dir = os.path.join(options.target_dir, item)
if os.path.exists(target_item_dir): if os.path.exists(target_item_dir):

View file

@ -10,6 +10,7 @@ from pip.index import PackageFinder
from pip.exceptions import CommandError, PreviousBuildDirError from pip.exceptions import CommandError, PreviousBuildDirError
from pip.req import InstallRequirement, RequirementSet, parse_requirements from pip.req import InstallRequirement, RequirementSet, parse_requirements
from pip.utils import normalize_path from pip.utils import normalize_path
from pip.utils.build import BuildDirectory
from pip.utils.deprecation import RemovedInPip7Warning, RemovedInPip8Warning from pip.utils.deprecation import RemovedInPip7Warning, RemovedInPip8Warning
from pip.wheel import WheelBuilder from pip.wheel import WheelBuilder
from pip import cmdoptions from pip import cmdoptions
@ -157,6 +158,9 @@ class WheelCommand(Command):
RemovedInPip8Warning, RemovedInPip8Warning,
) )
if options.build_dir:
options.build_dir = os.path.abspath(options.build_dir)
with self._build_session(options) as session: with self._build_session(options) as session:
finder = PackageFinder( finder = PackageFinder(
@ -171,9 +175,11 @@ class WheelCommand(Command):
session=session, 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( requirement_set = RequirementSet(
build_dir=options.build_dir, build_dir=build_dir,
src_dir=options.src_dir, src_dir=options.src_dir,
download_dir=None, download_dir=None,
ignore_dependencies=options.ignore_dependencies, ignore_dependencies=options.ignore_dependencies,
@ -224,7 +230,9 @@ class WheelCommand(Command):
global_options=options.global_options or [], global_options=options.global_options or [],
) )
if not wb.build(): if not wb.build():
raise CommandError("Failed to build one or more wheels") raise CommandError(
"Failed to build one or more wheels"
)
except PreviousBuildDirError: except PreviousBuildDirError:
options.no_clean = True options.no_clean = True
raise raise

37
pip/utils/build.py Normal file
View file

@ -0,0 +1,37 @@
from __future__ import absolute_import
import tempfile
from pip.utils import rmtree
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)

View file

@ -26,12 +26,12 @@ def test_no_clean_option_blocks_cleaning_after_install(script, data):
""" """
Test --no-clean option blocks cleaning after install Test --no-clean option blocks cleaning after install
""" """
result = script.pip( build = script.base_path / 'pip-build'
'install', '--no-clean', '--no-index', script.pip(
'--find-links=%s' % data.find_links, 'simple' 'install', '--no-clean', '--no-index', '--build', build,
'--find-links=%s' % data.find_links, 'simple',
) )
build = script.venv_path / 'build' / 'simple' assert exists(build)
assert exists(build), "build/simple should still exist %s" % str(result)
def test_cleanup_after_install_editable_from_hg(script, tmpdir): def test_cleanup_after_install_editable_from_hg(script, tmpdir):
@ -157,15 +157,17 @@ def test_cleanup_prevented_upon_build_dir_exception(script, data):
""" """
Test no cleanup occurs after a PreviousBuildDirError Test no cleanup occurs after a PreviousBuildDirError
""" """
build = script.venv_path / 'build' / 'simple' build = script.venv_path / 'build'
os.makedirs(build) build_simple = build / 'simple'
write_delete_marker_file(script.venv_path / 'build') os.makedirs(build_simple)
build.join("setup.py").write("#") write_delete_marker_file(build)
build_simple.join("setup.py").write("#")
result = script.pip( result = script.pip(
'install', '-f', data.find_links, '--no-index', 'simple', 'install', '-f', data.find_links, '--no-index', 'simple',
'--build', build,
expect_error=True, expect_error=True,
) )
assert result.returncode == PREVIOUS_BUILD_DIR_ERROR assert result.returncode == PREVIOUS_BUILD_DIR_ERROR
assert "pip can't proceed" in result.stdout, result.stdout assert "pip can't proceed" in result.stdout, result.stdout
assert exists(build) assert exists(build_simple)

View file

@ -86,11 +86,12 @@ def test_no_clean_option_blocks_cleaning_after_wheel(script, data):
Test --no-clean option blocks cleaning after wheel build Test --no-clean option blocks cleaning after wheel build
""" """
script.pip('install', 'wheel') script.pip('install', 'wheel')
build = script.venv_path / 'build'
result = script.pip( result = script.pip(
'wheel', '--no-clean', '--no-index', 'wheel', '--no-clean', '--no-index', '--build', build,
'--find-links=%s' % data.find_links, 'simple', '--find-links=%s' % data.find_links, 'simple',
) )
build = script.venv_path / 'build' / 'simple' build = build / 'simple'
assert exists(build), "build/simple should still exist %s" % str(result) assert exists(build), "build/simple should still exist %s" % str(result)
@ -128,6 +129,7 @@ def test_pip_wheel_fail_cause_of_previous_build_dir(script, data):
# When I call pip trying to install things again # When I call pip trying to install things again
result = script.pip( result = script.pip(
'wheel', '--no-index', '--find-links=%s' % data.find_links, 'wheel', '--no-index', '--find-links=%s' % data.find_links,
'--build', script.venv_path / 'build',
'simple==3.0', expect_error=True, 'simple==3.0', expect_error=True,
) )