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)
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