diff options
| author | Tarek Ziade <tarek@ziade.org> | 2011-01-03 18:09:24 +0100 |
|---|---|---|
| committer | Tarek Ziade <tarek@ziade.org> | 2011-01-03 18:09:24 +0100 |
| commit | 1b60c948f776068f7ac7ca8b8814b0dabe5d2e49 (patch) | |
| tree | ac7e11442c10e997072b097966f5f8171f0f9359 /distutils2/install.py | |
| parent | 03dae7f7be990bc07f76db4007d782f28fa91b1d (diff) | |
| download | disutils2-1b60c948f776068f7ac7ca8b8814b0dabe5d2e49.tar.gz | |
savepoint
Diffstat (limited to 'distutils2/install.py')
| -rw-r--r-- | distutils2/install.py | 229 |
1 files changed, 211 insertions, 18 deletions
diff --git a/distutils2/install.py b/distutils2/install.py index c20538a..e42ecf6 100644 --- a/distutils2/install.py +++ b/distutils2/install.py @@ -1,14 +1,18 @@ from tempfile import mkdtemp -import logging import shutil import os import errno import itertools +import sys +import tarfile +from distutils2 import logger from distutils2._backport.pkgutil import get_distributions +from distutils2._backport.sysconfig import get_config_var from distutils2.depgraph import generate_graph from distutils2.index import wrapper from distutils2.index.errors import ProjectNotFound, ReleaseNotFound +from distutils2.version import get_version_predicate """Provides installations scripts. @@ -53,7 +57,129 @@ def move_files(files, destination=None): else: raise e os.rename(old, new) - yield(old, new) + yield old, new + + + +# ripped from shutil +def _ensure_directory(path): + """Ensure that the parent directory of `path` exists""" + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + +def _unpack_zipfile(filename, extract_dir): + try: + import zipfile + except ImportError: + raise ReadError('zlib not supported, cannot unpack this archive.') + + if not zipfile.is_zipfile(filename): + raise ReadError("%s is not a zip file" % filename) + + zip = zipfile.ZipFile(filename) + try: + for info in zip.infolist(): + name = info.filename + if name.startswith('/') or '..' in name: + continue + + target = os.path.join(extract_dir, *name.split('/')) + if not target: + continue + + _ensure_directory(target) + if not name.endswith('/'): + # file + data = zip.read(info.filename) + f = open(target,'wb') + try: + f.write(data) + finally: + f.close() + del data + finally: + zip.close() + +def _unpack_tarfile(filename, extract_dir): + try: + tarobj = tarfile.open(filename) + except tarfile.TarError: + raise ReadError( + "%s is not a compressed or uncompressed tar file" % filename) + try: + tarobj.extractall(extract_dir) + finally: + tarobj.close() + + +_UNPACKERS = ( + (['.tar.gz', '.tgz', '.tar'], _unpack_tarfile), + (['.zip', '.egg'], _unpack_zipfile)) + + +def _unpack(filename, extract_dir=None): + if extract_dir is None: + extract_dir = os.path.dirname(filename) + + for formats, func in _UNPACKERS: + for format in formats: + if filename.endswith(format): + func(filename, extract_dir) + return extract_dir + + raise ValueError('Unknown archive format: %s' % filename) + + +def _install_dist(dist, path): + """Install a distribution into a path""" + def _run_d1_install(archive_dir, path): + # backward compat: using setuptools or plain-distutils + cmd = '%s setup.py install --root=%s --record=%s' + setup_py = os.path.join(archive_dir, 'setup.py') + if 'setuptools' in open(setup_py).read(): + cmd += ' --single-version-externally-managed' + + # how to place this file in the egg-info dir + # for non-distutils2 projects ? + record_file = os.path.join(archive_dir, 'RECORD') + os.system(cmd % (sys.executable, path, record_file)) + if not os.path.exists(record_file): + raise ValueError('Failed to install.') + return open(record_file).read().split('\n') + + def _run_d2_install(archive_dir, path): + # using our own install command + raise NotImplementedError() + + # download + archive = dist.download() + + # unarchive + where = _unpack(archive) + + # get into the dir + archive_dir = None + for item in os.listdir(where): + fullpath = os.path.join(where, item) + if os.path.isdir(fullpath): + archive_dir = fullpath + break + + if archive_dir is None: + raise ValueError('Cannot locate the unpacked archive') + + # install + old_dir = os.getcwd() + os.chdir(archive_dir) + try: + # distutils2 or distutils1 ? + if 'setup.py' in os.listdir(archive_dir): + return _run_d1_install(archive_dir, path) + else: + return _run_d2_install(archive_dir, path) + finally: + os.chdir(old_dir) def install_dists(dists, path=None): @@ -65,19 +191,23 @@ def install_dists(dists, path=None): Return a list of installed files. :param dists: distributions to install - :param path: base path to install distribution on + :param path: base path to install distribution in """ if not path: path = mkdtemp() installed_dists, installed_files = [], [] for d in dists: + logger.info('Installing %s %s' % (d.name, d.version)) try: - installed_files.extend(d.install(path)) + installed_files.extend(_install_dist(d, path)) installed_dists.append(d) except Exception, e : + logger.info('Failed. %s' % str(e)) + + # reverting for d in installed_dists: - d.uninstall() + _uninstall(d) raise e return installed_files @@ -123,16 +253,26 @@ def install_from_infos(install=[], remove=[], conflicts=[], install_path=None): try: if install: installed_files = install_dists(install, install_path) # install to tmp first - for files in temp_files.values(): - for old, new in files: - os.remove(new) - except Exception,e: + except Exception: # if an error occurs, put back the files in the good place. for files in temp_files.values(): for old, new in files: shutil.move(new, old) + # now re-raising + raise + + # we can emove them for good + for files in temp_files.values(): + for old, new in files: + os.remove(new) + + +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. @@ -155,12 +295,34 @@ def get_infos(requirements, index=None, installed=None, prefer_final=True): conflict. """ - if not index: - index = wrapper.ClientWrapper() if not installed: + logger.info('Reading installed distributions') installed = get_distributions(use_egg_info=True) + infos = {'install': [], 'remove': [], 'conflict': []} + # Is a compatible version of the project is already installed ? + predicate = get_version_predicate(requirements) + found = False + installed = list(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 %s %s' % (installed_project.name, + installed_project.metadata.version)) + if predicate.match(installed_project.metadata.version): + return infos + + break + + if not found: + logger.info('Project not installed.') + + if not index: + index = wrapper.ClientWrapper() + # Get all the releases that match the requirements try: releases = index.get_releases(requirements) @@ -170,28 +332,36 @@ def get_infos(requirements, index=None, installed=None, prefer_final=True): # Pick up a release, and try to get the dependency tree release = releases.get_last(requirements, prefer_final=prefer_final) - # Iter since we found something without conflicts + if release is None: + logger.info('Could not find a matching project') + return infos + + import pdb; pdb.set_trace() + + # this works for Metadata 1.2 metadata = release.fetch_metadata() - # Get the distributions already_installed on the system - # and add the one we want to install + # for earlier, we need to build setuptools deps if any + if 'requires_dist' not in metadata: + deps = _get_setuptools_deps(release) + else: + deps = metadata['requires_dist'] distributions = itertools.chain(installed, [release]) depgraph = generate_graph(distributions) # Store all the already_installed packages in a list, in case of rollback. - infos = {'install': [], 'remove': [], 'conflict': []} - # Get what the missing deps are - for dists in depgraph.missing.values(): + for dists in depgraph.missing[release]: if dists: - logging.info("missing dependencies found, installing them") + logger.info("missing dependencies found, installing them") # 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]]) @@ -214,5 +384,28 @@ def main(**attrs): attrs['requirements'] = sys.argv[1] get_infos(**attrs) + +def install(project): + logger.info('Getting information about "%s".' % project) + try: + info = get_infos(project) + except InstallationException: + logger.info('Cound not find "%s".' % project) + return + + if info['install'] == []: + logger.info('Nothing to install.') + return + + install_path = get_config_var('base') + try: + install_from_infos(info['install'], info['remove'], info['conflict'], + install_path=install_path) + + except InstallationConflict, e: + projects = ['%s %s' % (p.name, p.metadata.version) for p in e.args[0]] + logger.info('"%s" conflicts with "%s"' % (project, ','.join(projects))) + + if __name__ == '__main__': main() |
