示例#1
0
def install_requirement(requirements, target_repo=None, local_repos=None,
        remote_repos=None, interactive=True, dry_run=False, verbose=False,
        term_width=0):
    """
    Find and install packages which match the requirements.

    This may either upgrade or downgrade packages when needed.

    """
    if target_repo is None:
        target_repo = get_site_packages()
    if remote_repos is None:
        remote_repos = [HTMLRepository("http://pypi.python.org/simple")]
    if local_repos is None:
        local_repos = get_local_repos()
    # FIXME: DMP why did we not use the specified set of local repos?
    # Commenting out for now.
    #local = RepositoryUnion(get_local_repos())
    local = RepositoryUnion(local_repos)
    available = RepositoryUnion(local_repos+remote_repos)

    # Generate proposals
    installed = dict((key, project.active_package)
                     for key, project in local.projects.items()
                     if project.active_package is not None)
    to_install = []
    for requirement in requirements:

        # Ensure we can find at least one distribution matching the
        # requirement.
        try:
            packages = [package
                for package in available.projects[requirement.key].packages
                    if package.distribution in requirement]
        except KeyError:
            if verbose:
                print "Could not find suitable distribution for %s" % \
                    requirement
            # FIXME: Should we really return here?  I guess we're trying to say
            # we couldn't find ANY match for ALL requirements by doing so?
            return
        if not packages:
            warning("Could not find a package which matches requirement: "
                "%s" % requirement)
            continue

        # If we're running in interactive mode, let the user pick a
        # distribution if there is more than one to pick from.  Otherwise,
        # we just go with the first one.
        if interactive and len(packages) > 1:
            selection = user_select(["version", "active", "location"],
                [pkg.metadata for pkg in packages], "Select package: ",
                max_width=term_width)
            #selection = user_select(["Package '%s' at %s%s" % (pkg.name,
            #    pkg.location, " (Active)" if pkg.active else "")
            #    for pkg in packages], "Select package: ")
            if selection == None:
                if verbose:
                    info("User selected no package for requirement %s" %
                        requirement)
                continue
            package = packages[selection]
        else:
            package = packages[0]

        # If the selected distribution is already active, we have nothing to
        # install.
        if package.active:
            if verbose:
                info("Package %s satisfies %s and is already active" %
                    (package.name, requirement))
        else:
            to_install.append(package)
    if not to_install:
        return

    upgrades = upgrade(to_install, installed, available)
    try:
        proposal, reasoning = upgrades.next()
    except StopIteration:
        info("Unable to create a consistent installation plan.")
        return

    if interactive:
        response = False
        while not response:
            print
            print "Proposal:"
            for project, package in proposal.items():
                if package.active:
                    continue
                for repo in local_repos:
                    if project in repo and repo[project].active:
                        active_project = repo[project]
                        break
                else:
                    active_project = None

                if active_project is None:
                    print ("  Install %s from %s" % (package.name,
                        package.location))[:term_width]
                else:
                    print ("  Upgrade %s from %s to %s from %s" % (
                        active_project.name,
                        active_project.active_package.version, package.version,
                        package.location))[:term_width]
            response = query_user("Accept proposed installation plan (y/n)? ",
                                  default="y")
            if not response:
                try:
                    proposal, reasoning = upgrades.next()
                except StopIteration:
                    info("No proposed installation plan was acceptable "
                         "to the user.")
                    return

    # first activate any local packages
    active_environments = set()
    for key, package in proposal.items():
        if isinstance(package, EasyInstallPackage):
            package.activate(save=False, dry_run=dry_run)
            active_environments.add(package.repository.active)
    for env in active_environments:
        if not dry_run:
            env.save()
        else:
            print "Saving .pth file."

    for key, package in proposal.items():
        if isinstance(package, RemotePackage):
            package.install(target_repo, dry_run=dry_run)
示例#2
0
def rollback_menu(remote_repos=None, interactive=True,
    dry_run=False, term_width=0, show_all=False, num_entries=5,
    show_dates=False):
    """
    Show a menu with possible rollback options and perform the appropriate
    action based on the user's input.
    """
    # Create a list of metadata for the possible rollback dates so that we
    # can create an auto-generated user selection layout.  Based on the
    # command-line options, we can limit the list of rollback points that
    # are shown.  If the ensetuptools.cache doesn't exist, let the user know
    # why they can not do a rollback.
    cached_states = retrieve_states()
    if not cached_states:
        print ("A rollback can not be performed because there are "
            "no cached rollback points.")
        return
    if not show_all:
        cached_states = cached_states[:num_entries]
    metadata = []
    local_time = time.localtime()
    for i, state in enumerate(cached_states):
        # Create a date display from the difference between the timestamp of
        # the rollback point and the current time.  The difference we retrieve
        # is the time sine the epoch (i.e. January 1, 1970), so we make our
        # calculations from that.
        timestamp = state[0]
        time_tuple = time.strptime(timestamp, "%Y%m%d%H%M%S")
        date_display = date_display_diff(local_time, time_tuple)

        # If the user specified to display the full date/timestamp with the
        # rollback points, then tack it onto the simple display.
        if show_dates:
            date_display = "%s (%s)" % (date_display,
                                        time.strftime("%Y/%m/%d %H:%M:%S",
                                                      time_tuple))

        # Find the differences between two rollback points (i.e. packages
        # added, removed, or modified) and calculate a nice diff that can
        # be displayed in the table.
        # We need to stop calculating these diffs once we reach the last
        # item though because there are no entries after it.
        option_diff = ""
        if i < len(cached_states)-1:
            project_list_1 = cached_states[i][1]
            project_list_2 = cached_states[i+1][1]
            diff_list_1 = [project for project in project_list_1
                           if not project in project_list_2]
            diff_list_2 = [project for project in project_list_2
                           if not project in project_list_1]
            if len(diff_list_1) == 0 and len(diff_list_2) == 0:
                option_diff = "  There are no changes between these points."
            else:
                added = []
                modified = []
                deactivated = []
                for project in diff_list_1:
                    (project_name_1,
                     project_version_1) = parse_project_str(project)
                    found = False

                    for project2 in diff_list_2:
                        (project_name_2,
                         project_version_2) = parse_project_str(project2)
                        if project_name_1 == project_name_2:
                            found = True
                            modified.append("%s-%s to %s" % (
                                    project_name_1, project_version_2,
                                project_version_1))
                            break

                    if not found:
                        added.append("%s-%s" % (project_name_1,
                                                project_version_1))
                for project2 in diff_list_2:
                    (project_name_2,
                     project_version_2) = parse_project_str(project2)
                    found = False
                    for project in diff_list_1:
                        (project_name_1,
                         project_version_1) = parse_project_str(project)
                        if project_name_2 == project_name_1:
                            found = True
                            break

                    if not found:
                        deactivated.append("%s-%s" % (project_name_2,
                                                      project_version_2))
                if len(added) > 0:
                    option_diff += "  [A] %s" % added[0]
                    for add_str in added[1:]:
                        option_diff += "\n\t      %s" % add_str
                    if len(modified) > 0 or len(deactivated) > 0:
                        option_diff += "\n\t"
                if len(modified) > 0:
                    option_diff += "  [M] %s" % modified[0]
                    for mod_str in modified[1:]:
                        option_diff += "\n\t      %s" % mod_str
                    if len(deactivated) > 0:
                        option_diff += "\n\t"
                if len(deactivated) > 0:
                    option_diff += "  [D] %s" % deactivated[0]
                    for deac_str in deactivated[1:]:
                        option_diff += "\n\t      %s" % deac_str

        # Set the 'date' metadata according to the date display and
        # the differene between rollback points.
        metadata.append({"date": date_display + "\n\t" + option_diff})

    # If a user selects to view more information about a specific rollback
    # point, keep prompting the user to choose a rollback point after
    # displaying that information.
    while True:
        selection = user_select(["date"],
            metadata, ("Select a restore point to rollback your "
            "environment to.  For more information about a "
            "specific rollback point, type the option number "
            "followed by a question mark.  Use '0' to cancel "
            "rollback:  "), default="0", extra_char="?",
            max_width=term_width)
        if not selection.endswith("?"):
            break
        else:
            option = int(selection.split('?')[0])-1
            state = cached_states[option]
            timestamp = state[0]
            time_tuple = time.strptime(timestamp, "%Y%m%d%H%M%S")
            date_display = time.strftime("%Y/%m/%d %H:%M:%S", time_tuple)
            print "Active Project State on %s:" % date_display
            state_data=[]
            project_list = state[1]
            for project in project_list:
                (project_name, project_version) = parse_project_str(project)
                state_data.append({"project_name": project_name,
                    "version": project_version})
            msg = rst_table(["project_name", "version"],
                state_data, sorted=False, max_width=term_width)
            msg += "\n\n"
            print msg

    # If the user selected option '0', then there's nothing to do.
    if selection == '0':
        return

    # Now that the user has selected a rollback point, perform the action
    # to rollback to that state.  Once the rollback has been completed
    # successfully, let the user know.
    state_index = int(selection)-1
    project_list = cached_states[state_index][1]
    rollback_state(project_list, remote_repos, interactive, dry_run, term_width)
    timestamp = cached_states[state_index][0]
    time_tuple = time.strptime(timestamp, "%Y%m%d%H%M%S")
    date_display = time.strftime("%Y/%m/%d %H:%M:%S", time_tuple)
    print "\nSystem successfully rolled back to state on: %s" % date_display