Exemple #1
0
def install_requirements(requirements, install_prefix=ENVIRONMENT):
    """
    Manually install all requirements from binary distributions.

    :param requirements: A list of tuples in the format of the return value of
                         :py:func:`unpack_source_dists()`.
    :param install_prefix: The "prefix" under which the requirements should be
                           installed. This will be a pathname like ``/usr``,
                           ``/usr/local`` or the pathname of a virtual
                           environment.
    :returns: ``True`` if it succeeds in installing all requirements from
              binary distribution archives, ``False`` otherwise.
    """
    install_timer = Timer()
    existing_binary_dists = find_cached_binary_dists()
    pyversion = get_python_version()
    logger.info("Installing from binary distributions ..")
    for name, version, directory in requirements:
        filename = existing_binary_dists.get((name.lower(), version, pyversion))
        if not filename:
            logger.error("No binary distribution of %s (%s) available!", name, version)
            return False
        install_binary_dist(name, filename, install_prefix=install_prefix)
    logger.info("Finished installing all requirements in %s.", install_timer)
    return True
Exemple #2
0
def confirm_installation(project_name, packages, command_line):
    """
    Notify the user that there are missing dependencies and how they can be
    installed. Then ask the user whether we are allowed to install the
    dependencies.

    :param packages: A list of strings with the names of the packages that are
                     missing.
    :param command_line: A list of strings with the command line needed to
                         install the packages.

    Raises :py:class:`RefusedAutomaticInstallation` when the user refuses to
    let pip-accel install any missing dependencies.
    """
    logger.info("%s: You seem to be missing %i dependenc%s: %s",  project_name,
                len(packages), len(packages) == 1 and 'y' or 'ies', " ".join(packages))
    logger.info("%s: I can install %s for you with this command: %s",
                project_name, len(packages) == 1 and 'it' or 'them',
                " ".join(command_line))
    try:
        prompt = "Do you want me to install %s dependenc%s? [y/N] "
        choice = raw_input(prompt % (len(packages) == 1 and 'this' or 'these',
                                     len(packages) == 1 and 'y' or 'ies'))
        if choice.lower().strip() == 'y':
            logger.info("Got permission to install missing dependenc%s.",
                        len(packages) == 1 and 'y' or 'ies')
            return
    except:
        pass
    logger.error("%s: Refused installation of missing dependenc%s!",
                 project_name, len(packages) == 1 and 'y' or 'ies')
    msg = "%s: User canceled automatic installation of missing dependenc%s!"
    raise RefusedAutomaticInstallation, msg % (project_name, 'y' if len(packages) == 1 else 'ies')
Exemple #3
0
def main():
    """
    Main logic of the ``pip-accel`` command.
    """
    arguments = sys.argv[1:]
    # If no arguments are given, the help text of pip-accel is printed.
    if not arguments:
        print_usage()
        sys.exit(0)
    # If no install subcommand is given we pass the command line straight
    # to pip without any changes and exit immediately afterwards.
    elif 'install' not in arguments:
        sys.exit(os.spawnvp(os.P_WAIT, 'pip', ['pip'] + arguments))
    # Initialize logging output.
    coloredlogs.install()
    # Increase verbosity based on -v, --verbose options.
    for argument in arguments:
        if argument == '--verbose' or (len(argument) >= 2 and argument[0] ==
                '-' and argument[1] != '-' and 'v' in argument):
            coloredlogs.increase_verbosity()
    # Make sure the prefix is the same as the environment.
    if not os.path.samefile(sys.prefix, ENVIRONMENT):
        logger.error("You are trying to install packages in environment #1 which is different from environment #2 where pip-accel is installed! Please install pip-accel under environment #1 to install packages there.")
        logger.info("Environment #1: %s ($VIRTUAL_ENV)", ENVIRONMENT)
        logger.info("Environment #2: %s (installation prefix)", sys.prefix)
        sys.exit(1)
    main_timer = Timer()
    initialize_directories()
    build_directory = tempfile.mkdtemp()
    # Execute "pip install" in a loop in order to retry after intermittent
    # error responses from servers (which can happen quite frequently).
    try:
        for i in xrange(1, MAX_RETRIES):
            try:
                requirements = unpack_source_dists(arguments, build_directory)
            except DistributionNotFound:
                logger.warn("We don't have all source distributions yet!")
                download_source_dists(arguments, build_directory)
            else:
                if not requirements:
                    logger.info("No unsatisfied requirements found, probably there's nothing to do.")
                else:
                    install_requirements(requirements)
                    logger.info("Done! Took %s to install %i package%s.", main_timer, len(requirements), '' if len(requirements) == 1 else 's')
                return
            logger.warn("pip failed, retrying (%i/%i) ..", i + 1, MAX_RETRIES)
    except InstallationError:
        # Abort early when pip reports installation errors.
        logger.fatal("pip reported unrecoverable installation errors. Please fix and rerun!")
        sys.exit(1)
    finally:
        # Always cleanup temporary build directory.
        shutil.rmtree(build_directory)
    # Abort when after N retries we still failed to download source distributions.
    logger.fatal("External command failed %i times, aborting!" % MAX_RETRIES)
    sys.exit(1)
Exemple #4
0
def build_binary_dist(name, version, directory, pyversion):
    """
    Convert a single, unpacked source distribution to a binary distribution.

    :param name: The name of the requirement to build.
    :param version: The version of the requirement to build.
    :param directory: The directory where the unpacked sources of the
                      requirement are available.

    :returns: ``True`` if we succeed in building a binary distribution,
              ``False`` otherwise (probably because of missing binary
              dependencies like system libraries).
    """
    # Cleanup previously generated distributions.
    dist_directory = os.path.join(directory, 'dist')
    if os.path.isdir(dist_directory):
        logger.info("Cleaning up previously generated distributions in %s ..", dist_directory)
        shutil.rmtree(dist_directory)
    # Let the user know what's going on.
    build_text = "Building binary distribution of %s (%s) .." % (name, version)
    logger.info("%s", build_text)
    # Compose the command line needed to build the binary distribution.
    command_line = '"%s/bin/python" setup.py bdist_dumb --format=gztar' % ENVIRONMENT
    logger.debug("Executing external command: %s", command_line)
    # Redirect all output of the build to a temporary file.
    fd, temporary_file = tempfile.mkstemp()
    command_line = '%s > "%s" 2>&1' % (command_line, temporary_file)
    # Start the build.
    build = subprocess.Popen(['sh', '-c', command_line], cwd=directory)
    # Wait for the build to finish.
    if INTERACTIVE:
        # Provide feedback to the user in the mean time.
        spinner = Spinner(build_text)
        while build.poll() is None:
            spinner.step()
            time.sleep(0.1)
        spinner.clear()
    else:
        build.wait()
    # Make sure the build succeeded.
    if build.returncode != 0:
        logger.error("Failed to build binary distribution of %s! (version: %s)", name, version)
        with open(temporary_file) as handle:
            logger.info("Build output (will probably provide a hint as to what went wrong):\n%s", handle.read())
        return False
    # Move the generated distribution to the binary index.
    filenames = os.listdir(dist_directory)
    if len(filenames) != 1:
        logger.error("Build process did not result in one binary distribution! (matches: %s)", filenames)
        return False
    cache_file = '%s:%s:%s.tar.gz' % (name, version, pyversion)
    logger.info("Copying binary distribution %s to cache as %s.", filenames[0], cache_file)
    cache_binary_dist(os.path.join(directory, 'dist', filenames[0]),
                      os.path.join(binary_index, cache_file))
    return True
Exemple #5
0
def confirm_installation(project_name, packages, command_line):
    """
    Notify the user that there are missing dependencies and how they can be
    installed. Then ask the user whether we are allowed to install the
    dependencies.

    :param packages: A list of strings with the names of the packages that are
                     missing.
    :param command_line: A list of strings with the command line needed to
                         install the packages.

    Raises :py:class:`RefusedAutomaticInstallation` when the user refuses to
    let pip-accel install any missing dependencies.
    """
    logger.info("%s: You seem to be missing %i dependenc%s: %s", project_name,
                len(packages),
                len(packages) == 1 and 'y' or 'ies', " ".join(packages))
    logger.info("%s: I can install %s for you with this command: %s",
                project_name,
                len(packages) == 1 and 'it' or 'them', " ".join(command_line))
    try:
        prompt = "Do you want me to install %s dependenc%s? [y/N] "
        choice = raw_input(prompt % (len(packages) == 1 and 'this' or 'these',
                                     len(packages) == 1 and 'y' or 'ies'))
        if choice.lower().strip() == 'y':
            logger.info("Got permission to install missing dependenc%s.",
                        len(packages) == 1 and 'y' or 'ies')
            return
    except:
        pass
    logger.error("%s: Refused installation of missing dependenc%s!",
                 project_name,
                 len(packages) == 1 and 'y' or 'ies')
    msg = "%s: User canceled automatic installation of missing dependenc%s!"
    raise RefusedAutomaticInstallation, msg % (project_name, 'y' if
                                               len(packages) == 1 else 'ies')
Exemple #6
0
def main():
    """
    Main logic of the ``pip-accel`` command.
    """
    arguments = sys.argv[1:]
    # If no arguments are given, the help text of pip-accel is printed.
    if not arguments:
        print_usage()
        sys.exit(0)
    # If no install subcommand is given we pass the command line straight
    # to pip without any changes and exit immediately afterwards.
    elif 'install' not in arguments:
        sys.exit(os.spawnvp(os.P_WAIT, 'pip', ['pip'] + arguments))
    # Initialize logging output.
    coloredlogs.install()
    # Increase verbosity based on -v, --verbose options.
    for argument in arguments:
        if argument == '--verbose' or (len(argument) >= 2
                                       and argument[0] == '-'
                                       and argument[1] != '-'
                                       and 'v' in argument):
            coloredlogs.increase_verbosity()
    # Make sure the prefix is the same as the environment.
    if not os.path.samefile(sys.prefix, ENVIRONMENT):
        logger.error(
            "You are trying to install packages in environment #1 which is different from environment #2 where pip-accel is installed! Please install pip-accel under environment #1 to install packages there."
        )
        logger.info("Environment #1: %s ($VIRTUAL_ENV)", ENVIRONMENT)
        logger.info("Environment #2: %s (installation prefix)", sys.prefix)
        sys.exit(1)
    main_timer = Timer()
    initialize_directories()
    build_directory = tempfile.mkdtemp()
    # Execute "pip install" in a loop in order to retry after intermittent
    # error responses from servers (which can happen quite frequently).
    try:
        for i in xrange(1, MAX_RETRIES):
            try:
                requirements = unpack_source_dists(arguments, build_directory)
            except DistributionNotFound:
                logger.warn("We don't have all source distributions yet!")
                download_source_dists(arguments, build_directory)
            else:
                if not requirements:
                    logger.info(
                        "No unsatisfied requirements found, probably there's nothing to do."
                    )
                else:
                    install_requirements(requirements)
                    logger.info("Done! Took %s to install %i package%s.",
                                main_timer, len(requirements),
                                '' if len(requirements) == 1 else 's')
                return
            logger.warn("pip failed, retrying (%i/%i) ..", i + 1, MAX_RETRIES)
    except InstallationError:
        # Abort early when pip reports installation errors.
        logger.fatal(
            "pip reported unrecoverable installation errors. Please fix and rerun!"
        )
        sys.exit(1)
    finally:
        # Always cleanup temporary build directory.
        shutil.rmtree(build_directory)
    # Abort when after N retries we still failed to download source distributions.
    logger.fatal("External command failed %i times, aborting!" % MAX_RETRIES)
    sys.exit(1)
Exemple #7
0
def sanity_check_dependencies(project_name, auto_install=None):
    """
    If ``pip-accel`` fails to build a binary distribution, it will call this
    function as a last chance to install missing dependencies. If this function
    does not raise an exception, ``pip-accel`` will retry the build once.

    :param project_name: The project name of a requirement as found on PyPI.
    :param auto_install: ``True`` if dependencies on system packages may be
                         automatically installed, ``False`` if missing system
                         packages should raise an error, ``None`` if the
                         decision should be based on the environment variable
                         ``PIP_ACCEL_AUTO_INSTALL``.

    If missing system packages are found, this function will try to install
    them. If anything "goes wrong" an exception is raised:

    - If ``auto_install=False`` then :py:class:`DependencyCheckFailed` is
      raised
    - If installation of missing packages fails
      :py:class:`DependencyInstallationFailed` is raised
    - If the user refuses to let pip-accel install missing packages
      :py:class:`RefusedAutomaticInstallation` is raised.

    If all goes well nothing is raised, nor is anything returned.
    """
    # Has the caller forbidden us from automatic installation?
    auto_install_forbidden = (auto_install == False)
    if auto_install is not None:
        # If the caller made an explicit choice, we'll respect that.
        auto_install_allowed = auto_install
    else:
        # Otherwise we check the environment variable.
        auto_install_allowed = 'PIP_ACCEL_AUTO_INSTALL' in os.environ
    logger.info("%s: Checking for missing dependencies ..", project_name)
    known_dependencies = current_platform.find_dependencies(
        project_name.lower())
    if not known_dependencies:
        logger.info(
            "%s: No known dependencies... Maybe you have a suggestion?",
            project_name)
    else:
        installed_packages = set(current_platform.find_installed())
        missing_packages = [
            p for p in known_dependencies if p not in installed_packages
        ]
        if not missing_packages:
            logger.info("%s: All known dependencies are already installed.",
                        project_name)
        elif auto_install_forbidden:
            msg = "Missing %i system package%s (%s) required by %s but automatic installation is disabled!"
            raise DependencyCheckFailed, msg % (
                len(missing_packages), '' if len(missing_packages) == 1 else
                's', ', '.join(missing_packages), project_name)
        else:
            command_line = [
                'sudo'
            ] + current_platform.install_command(missing_packages)
            if not auto_install_allowed:
                confirm_installation(project_name, missing_packages,
                                     command_line)
            logger.info("%s: Missing %i dependenc%s: %s", project_name,
                        len(missing_packages),
                        'y' if len(missing_packages) == 1 else 'ies',
                        " ".join(missing_packages))
            exit_code = os.spawnvp(os.P_WAIT, 'sudo', command_line)
            if exit_code == 0:
                logger.info(
                    "%s: Successfully installed %i missing dependenc%s.",
                    project_name, len(missing_packages),
                    len(missing_packages) == 1 and 'y' or 'ies')
            else:
                logger.error("%s: Failed to install %i missing dependenc%s!",
                             project_name, len(missing_packages),
                             len(missing_packages) == 1 and 'y' or 'ies')
                msg = "Failed to install %i system package%s required by %s! (command failed: %s)"
                raise DependencyInstallationFailed, msg % (
                    len(missing_packages), '' if len(missing_packages) == 1
                    else 's', project_name, command_line)
Exemple #8
0
def sanity_check_dependencies(project_name, auto_install=None):
    """
    If ``pip-accel`` fails to build a binary distribution, it will call this
    function as a last chance to install missing dependencies. If this function
    does not raise an exception, ``pip-accel`` will retry the build once.

    :param project_name: The project name of a requirement as found on PyPI.
    :param auto_install: ``True`` if dependencies on system packages may be
                         automatically installed, ``False`` if missing system
                         packages should raise an error, ``None`` if the
                         decision should be based on the environment variable
                         ``PIP_ACCEL_AUTO_INSTALL``.

    If missing system packages are found, this function will try to install
    them. If anything "goes wrong" an exception is raised:

    - If ``auto_install=False`` then :py:class:`DependencyCheckFailed` is
      raised
    - If installation of missing packages fails
      :py:class:`DependencyInstallationFailed` is raised
    - If the user refuses to let pip-accel install missing packages
      :py:class:`RefusedAutomaticInstallation` is raised.

    If all goes well nothing is raised, nor is anything returned.
    """
    # Has the caller forbidden us from automatic installation?
    auto_install_forbidden = (auto_install == False)
    if auto_install is not None:
        # If the caller made an explicit choice, we'll respect that.
        auto_install_allowed = auto_install
    else:
        # Otherwise we check the environment variable.
        auto_install_allowed = 'PIP_ACCEL_AUTO_INSTALL' in os.environ
    logger.info("%s: Checking for missing dependencies ..", project_name)
    known_dependencies = current_platform.find_dependencies(project_name.lower())
    if not known_dependencies:
        logger.info("%s: No known dependencies... Maybe you have a suggestion?", project_name)
    else:
        installed_packages = set(current_platform.find_installed())
        missing_packages = [p for p in known_dependencies if p not in installed_packages]
        if not missing_packages:
            logger.info("%s: All known dependencies are already installed.", project_name)
        elif auto_install_forbidden:
            msg = "Missing %i system package%s (%s) required by %s but automatic installation is disabled!"
            raise DependencyCheckFailed, msg % (len(missing_packages), '' if len(missing_packages) == 1 else 's',
                                                ', '.join(missing_packages), project_name)
        else:
            command_line = ['sudo'] + current_platform.install_command(missing_packages)
            if not auto_install_allowed:
                confirm_installation(project_name, missing_packages, command_line)
            logger.info("%s: Missing %i dependenc%s: %s",
                        project_name, len(missing_packages),
                        'y' if len(missing_packages) == 1 else 'ies',
                        " ".join(missing_packages))
            exit_code = os.spawnvp(os.P_WAIT, 'sudo', command_line)
            if exit_code == 0:
                logger.info("%s: Successfully installed %i missing dependenc%s.",
                            project_name, len(missing_packages),
                            len(missing_packages) == 1 and 'y' or 'ies')
            else:
                logger.error("%s: Failed to install %i missing dependenc%s!",
                             project_name, len(missing_packages),
                             len(missing_packages) == 1 and 'y' or 'ies')
                msg = "Failed to install %i system package%s required by %s! (command failed: %s)"
                raise DependencyInstallationFailed, msg % (len(missing_packages), '' if len(missing_packages) == 1 else 's',
                                                           project_name, command_line)