summaryrefslogtreecommitdiffstats
path: root/Lib/packaging/install.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/packaging/install.py')
-rw-r--r--Lib/packaging/install.py529
1 files changed, 0 insertions, 529 deletions
diff --git a/Lib/packaging/install.py b/Lib/packaging/install.py
deleted file mode 100644
index 776ba40..0000000
--- a/Lib/packaging/install.py
+++ /dev/null
@@ -1,529 +0,0 @@
-"""Building blocks for installers.
-
-When used as a script, this module installs a release thanks to info
-obtained from an index (e.g. PyPI), with dependencies.
-
-This is a higher-level module built on packaging.database and
-packaging.pypi.
-"""
-import os
-import sys
-import stat
-import errno
-import shutil
-import logging
-import tempfile
-from sysconfig import get_config_var, get_path, is_python_build
-
-from packaging import logger
-from packaging.dist import Distribution
-from packaging.util import (_is_archive_file, ask, get_install_method,
- egginfo_to_distinfo)
-from packaging.pypi import wrapper
-from packaging.version import get_version_predicate
-from packaging.database import get_distributions, get_distribution
-from packaging.depgraph import generate_graph
-
-from packaging.errors import (PackagingError, InstallationException,
- InstallationConflict, CCompilerError)
-from packaging.pypi.errors import ProjectNotFound, ReleaseNotFound
-from packaging import database
-
-
-__all__ = ['install_dists', 'install_from_infos', 'get_infos', 'remove',
- 'install', 'install_local_project']
-
-
-def _move_files(files, destination):
- """Move the list of files in the destination folder, keeping the same
- structure.
-
- Return a list of tuple (old, new) emplacement of files
-
- :param files: a list of files to move.
- :param destination: the destination directory to put on the files.
- """
-
- for old in files:
- filename = os.path.split(old)[-1]
- new = os.path.join(destination, filename)
- # try to make the paths.
- try:
- os.makedirs(os.path.dirname(new))
- except OSError as e:
- if e.errno != errno.EEXIST:
- raise
- os.rename(old, new)
- yield old, new
-
-
-def _run_distutils_install(path):
- # backward compat: using setuptools or plain-distutils
- cmd = '%s setup.py install --record=%s'
- record_file = os.path.join(path, 'RECORD')
- os.system(cmd % (sys.executable, record_file))
- if not os.path.exists(record_file):
- raise ValueError('failed to install')
- else:
- egginfo_to_distinfo(record_file, remove_egginfo=True)
-
-
-def _run_setuptools_install(path):
- cmd = '%s setup.py install --record=%s --single-version-externally-managed'
- record_file = os.path.join(path, 'RECORD')
-
- os.system(cmd % (sys.executable, record_file))
- if not os.path.exists(record_file):
- raise ValueError('failed to install')
- else:
- egginfo_to_distinfo(record_file, remove_egginfo=True)
-
-
-def _run_packaging_install(path):
- # XXX check for a valid setup.cfg?
- dist = Distribution()
- dist.parse_config_files()
- try:
- dist.run_command('install_dist')
- name = dist.metadata['Name']
- return database.get_distribution(name) is not None
- except (IOError, os.error, PackagingError, CCompilerError) as msg:
- raise ValueError("Failed to install, " + str(msg))
-
-
-def _install_dist(dist, path):
- """Install a distribution into a path.
-
- This:
-
- * unpack the distribution
- * copy the files in "path"
- * determine if the distribution is packaging or distutils1.
- """
- where = dist.unpack()
-
- if where is None:
- raise ValueError('Cannot locate the unpacked archive')
-
- return _run_install_from_archive(where)
-
-
-def install_local_project(path):
- """Install a distribution from a source directory.
-
- If the source directory contains a setup.py install using distutils1.
- If a setup.cfg is found, install using the install_dist command.
-
- Returns True on success, False on Failure.
- """
- path = os.path.abspath(path)
- if os.path.isdir(path):
- logger.info('Installing from source directory: %r', path)
- return _run_install_from_dir(path)
- elif _is_archive_file(path):
- logger.info('Installing from archive: %r', path)
- _unpacked_dir = tempfile.mkdtemp()
- try:
- shutil.unpack_archive(path, _unpacked_dir)
- return _run_install_from_archive(_unpacked_dir)
- finally:
- shutil.rmtree(_unpacked_dir)
- else:
- logger.warning('No project to install.')
- return False
-
-
-def _run_install_from_archive(source_dir):
- # XXX need a better way
- for item in os.listdir(source_dir):
- fullpath = os.path.join(source_dir, item)
- if os.path.isdir(fullpath):
- source_dir = fullpath
- break
- return _run_install_from_dir(source_dir)
-
-
-install_methods = {
- 'packaging': _run_packaging_install,
- 'setuptools': _run_setuptools_install,
- 'distutils': _run_distutils_install}
-
-
-def _run_install_from_dir(source_dir):
- old_dir = os.getcwd()
- os.chdir(source_dir)
- install_method = get_install_method(source_dir)
- func = install_methods[install_method]
- try:
- func = install_methods[install_method]
- try:
- func(source_dir)
- return True
- except ValueError as err:
- # failed to install
- logger.info(str(err))
- return False
- finally:
- os.chdir(old_dir)
-
-
-def install_dists(dists, path, paths=None):
- """Install all distributions provided in dists, with the given prefix.
-
- If an error occurs while installing one of the distributions, uninstall all
- the installed distribution (in the context if this function).
-
- Return a list of installed dists.
-
- :param dists: distributions to install
- :param path: base path to install distribution in
- :param paths: list of paths (defaults to sys.path) to look for info
- """
-
- installed_dists = []
- for dist in dists:
- logger.info('Installing %r %s...', dist.name, dist.version)
- try:
- _install_dist(dist, path)
- installed_dists.append(dist)
- except Exception as e:
- logger.info('Failed: %s', e)
-
- # reverting
- for installed_dist in installed_dists:
- logger.info('Reverting %r', installed_dist)
- remove(installed_dist.name, paths)
- raise e
- return installed_dists
-
-
-def install_from_infos(install_path=None, install=[], remove=[], conflicts=[],
- paths=None):
- """Install and remove the given distributions.
-
- The function signature is made to be compatible with the one of get_infos.
- The aim of this script is to povide a way to install/remove what's asked,
- and to rollback if needed.
-
- So, it's not possible to be in an inconsistant state, it could be either
- installed, either uninstalled, not half-installed.
-
- The process follow those steps:
-
- 1. Move all distributions that will be removed in a temporary location
- 2. Install all the distributions that will be installed in a temp. loc.
- 3. If the installation fails, rollback (eg. move back) those
- distributions, or remove what have been installed.
- 4. Else, move the distributions to the right locations, and remove for
- real the distributions thats need to be removed.
-
- :param install_path: the installation path where we want to install the
- distributions.
- :param install: list of distributions that will be installed; install_path
- must be provided if this list is not empty.
- :param remove: list of distributions that will be removed.
- :param conflicts: list of conflicting distributions, eg. that will be in
- conflict once the install and remove distribution will be
- processed.
- :param paths: list of paths (defaults to sys.path) to look for info
- """
- # first of all, if we have conflicts, stop here.
- if conflicts:
- raise InstallationConflict(conflicts)
-
- if install and not install_path:
- raise ValueError("Distributions are to be installed but `install_path`"
- " is not provided.")
-
- # before removing the files, we will start by moving them away
- # then, if any error occurs, we could replace them in the good place.
- temp_files = {} # contains lists of {dist: (old, new)} paths
- temp_dir = None
- if remove:
- temp_dir = tempfile.mkdtemp()
- for dist in remove:
- files = dist.list_installed_files()
- temp_files[dist] = _move_files(files, temp_dir)
- try:
- if install:
- install_dists(install, install_path, paths)
- except:
- # if an error occurs, put back the files in the right place.
- for files in temp_files.values():
- for old, new in files:
- shutil.move(new, old)
- if temp_dir:
- shutil.rmtree(temp_dir)
- # now re-raising
- raise
-
- # we can remove them for good
- for files in temp_files.values():
- for old, new in files:
- os.remove(new)
- if temp_dir:
- shutil.rmtree(temp_dir)
-
-
-def _get_setuptools_deps(release):
- # NotImplementedError
- pass
-
-
-def get_infos(requirements, index=None, installed=None, prefer_final=True):
- """Return the informations on what's going to be installed and upgraded.
-
- :param requirements: is a *string* containing the requirements for this
- project (for instance "FooBar 1.1" or "BarBaz (<1.2)")
- :param index: If an index is specified, use this one, otherwise, use
- :class index.ClientWrapper: to get project metadatas.
- :param installed: a list of already installed distributions.
- :param prefer_final: when picking up the releases, prefer a "final" one
- over a beta/alpha/etc one.
-
- The results are returned in a dict, containing all the operations
- needed to install the given requirements::
-
- >>> get_install_info("FooBar (<=1.2)")
- {'install': [<FooBar 1.1>], 'remove': [], 'conflict': []}
-
- Conflict contains all the conflicting distributions, if there is a
- conflict.
- """
- # this function does several things:
- # 1. get a release specified by the requirements
- # 2. gather its metadata, using setuptools compatibility if needed
- # 3. compare this tree with what is currently installed on the system,
- # return the requirements of what is missing
- # 4. do that recursively and merge back the results
- # 5. return a dict containing information about what is needed to install
- # or remove
-
- if not installed:
- logger.debug('Reading installed distributions')
- installed = list(get_distributions(use_egg_info=True))
-
- infos = {'install': [], 'remove': [], 'conflict': []}
- # Is a compatible version of the project already installed ?
- predicate = get_version_predicate(requirements)
- found = False
-
- # check that the project isn't already installed
- for installed_project in installed:
- # is it a compatible project ?
- if predicate.name.lower() != installed_project.name.lower():
- continue
- found = True
- logger.info('Found %r %s', installed_project.name,
- installed_project.version)
-
- # if we already have something installed, check it matches the
- # requirements
- if predicate.match(installed_project.version):
- return infos
- break
-
- if not found:
- logger.debug('Project not installed')
-
- if not index:
- index = wrapper.ClientWrapper()
-
- if not installed:
- installed = get_distributions(use_egg_info=True)
-
- # Get all the releases that match the requirements
- try:
- release = index.get_release(requirements)
- except (ReleaseNotFound, ProjectNotFound):
- raise InstallationException('Release not found: %r' % requirements)
-
- if release is None:
- logger.info('Could not find a matching project')
- return infos
-
- metadata = release.fetch_metadata()
-
- # we need to build setuptools deps if any
- if 'requires_dist' not in metadata:
- metadata['requires_dist'] = _get_setuptools_deps(release)
-
- # build the dependency graph with local and required dependencies
- dists = list(installed)
- dists.append(release)
- depgraph = generate_graph(dists)
-
- # Get what the missing deps are
- dists = depgraph.missing[release]
- if dists:
- logger.info("Missing dependencies found, retrieving metadata")
- # we have missing deps
- for dist in dists:
- _update_infos(infos, get_infos(dist, index, installed))
-
- # Fill in the infos
- existing = [d for d in installed if d.name == release.name]
- if existing:
- infos['remove'].append(existing[0])
- infos['conflict'].extend(depgraph.reverse_list[existing[0]])
- infos['install'].append(release)
- return infos
-
-
-def _update_infos(infos, new_infos):
- """extends the lists contained in the `info` dict with those contained
- in the `new_info` one
- """
- for key, value in infos.items():
- if key in new_infos:
- infos[key].extend(new_infos[key])
-
-
-def remove(project_name, paths=None, auto_confirm=True):
- """Removes a single project from the installation.
-
- Returns True on success
- """
- dist = get_distribution(project_name, use_egg_info=True, paths=paths)
- if dist is None:
- raise PackagingError('Distribution %r not found' % project_name)
- files = dist.list_installed_files(local=True)
- rmdirs = []
- rmfiles = []
- tmp = tempfile.mkdtemp(prefix=project_name + '-uninstall')
-
- def _move_file(source, target):
- try:
- os.rename(source, target)
- except OSError as err:
- return err
- return None
-
- success = True
- error = None
- try:
- for file_, md5, size in files:
- if os.path.isfile(file_):
- dirname, filename = os.path.split(file_)
- tmpfile = os.path.join(tmp, filename)
- try:
- error = _move_file(file_, tmpfile)
- if error is not None:
- success = False
- break
- finally:
- if not os.path.isfile(file_):
- os.rename(tmpfile, file_)
- if file_ not in rmfiles:
- rmfiles.append(file_)
- if dirname not in rmdirs:
- rmdirs.append(dirname)
- finally:
- shutil.rmtree(tmp)
-
- if not success:
- logger.info('%r cannot be removed.', project_name)
- logger.info('Error: %s', error)
- return False
-
- logger.info('Removing %r: ', project_name)
-
- for file_ in rmfiles:
- logger.info(' %s', file_)
-
- # Taken from the pip project
- if auto_confirm:
- response = 'y'
- else:
- response = ask('Proceed (y/n)? ', ('y', 'n'))
-
- if response == 'y':
- file_count = 0
- for file_ in rmfiles:
- os.remove(file_)
- file_count += 1
-
- dir_count = 0
- for dirname in rmdirs:
- if not os.path.exists(dirname):
- # could
- continue
-
- files_count = 0
- for root, dir, files in os.walk(dirname):
- files_count += len(files)
-
- if files_count > 0:
- # XXX Warning
- continue
-
- # empty dirs with only empty dirs
- if os.stat(dirname).st_mode & stat.S_IWUSR:
- # XXX Add a callable in shutil.rmtree to count
- # the number of deleted elements
- shutil.rmtree(dirname)
- dir_count += 1
-
- # removing the top path
- # XXX count it ?
- if os.path.exists(dist.path):
- shutil.rmtree(dist.path)
-
- logger.info('Success: removed %d files and %d dirs',
- file_count, dir_count)
-
- return True
-
-
-def install(project):
- """Installs a project.
-
- Returns True on success, False on failure
- """
- if is_python_build():
- # Python would try to install into the site-packages directory under
- # $PREFIX, but when running from an uninstalled code checkout we don't
- # want to create directories under the installation root
- message = ('installing third-party projects from an uninstalled '
- 'Python is not supported')
- logger.error(message)
- return False
-
- logger.info('Checking the installation location...')
- purelib_path = get_path('purelib')
-
- # trying to write a file there
- try:
- with tempfile.NamedTemporaryFile(suffix=project,
- dir=purelib_path) as testfile:
- testfile.write(b'test')
- except OSError:
- # FIXME this should check the errno, or be removed altogether (race
- # condition: the directory permissions could be changed between here
- # and the actual install)
- logger.info('Unable to write in "%s". Do you have the permissions ?'
- % purelib_path)
- return False
-
- logger.info('Getting information about %r...', project)
- try:
- info = get_infos(project)
- except InstallationException:
- logger.info('Cound not find %r', project)
- return False
-
- if info['install'] == []:
- logger.info('Nothing to install')
- return False
-
- install_path = get_config_var('base')
- try:
- install_from_infos(install_path,
- info['install'], info['remove'], info['conflict'])
-
- except InstallationConflict as e:
- if logger.isEnabledFor(logging.INFO):
- projects = ('%r %s' % (p.name, p.version) for p in e.args[0])
- logger.info('%r conflicts with %s', project, ','.join(projects))
-
- return True