diff --git a/pisi/cli/commands.py b/pisi/cli/commands.py index a6c0307d..c82118df 100644 --- a/pisi/cli/commands.py +++ b/pisi/cli/commands.py @@ -208,7 +208,7 @@ You may use filenames, URIs or package names for packages. If you have specified a package name, it should exist in a specified repository. """ def __init__(self): - super(Install, self).__init__() + super(Upgrade, self).__init__() def run(self): if not self.args: diff --git a/pisi/dependency.py b/pisi/dependency.py index aa2cccb4..2c0ef75b 100644 --- a/pisi/dependency.py +++ b/pisi/dependency.py @@ -16,9 +16,66 @@ from installdb import installdb import packagedb from ui import ui from version import Version +from xmlext import * +from xmlfile import XmlFile -def satisfiesDep(pkg_name, depinfo): - """determine if package satisfies given dependency spec in installdb""" +class DepInfo: + def __init__(self, node = None): + if node: + self.package = getNodeText(node).strip() + self.versionFrom = getNodeAttribute(node, "versionFrom") + self.versionTo = getNodeAttribute(node, "versionTo") + self.releaseFrom = getNodeAttribute(node, "releaseFrom") + self.releaseTo = getNodeAttribute(node, "releaseTo") + else: + self.versionFrom = self.versionTo = None + self.releaseFrom = self.releaseFrom = None + + def elt(self, xml): + node = xml.newNode("Dependency") + xml.addText(node, self.package) + if self.versionFrom: + node.setAttribute("versionFrom", self.versionFrom) + if self.versionTo: + node.setAttribute("versionTo", self.versionTo) + if self.releaseFrom: + node.setAttribute("releaseFrom", self.versionFrom) + if self.releaseTo: + node.setAttribute("releaseTo", self.versionTo) + return node + + def verify(self): + if not self.package: return False + return True + + def satisfies(self, pkg_name, version, release): + """determine if a package ver. satisfies given dependency spec""" + ret = True + from version import Version + if self.versionFrom: + ret &= Version(version) >= Version(self.versionFrom) + if self.versionTo: + ret &= Version(version) <= Version(self.versionTo) + if self.releaseFrom: + ret &= Version(release) <= Version(self.releaseFrom) + if self.releaseTo: + ret &= Version(release) <= Version(self.releaseTo) + return ret + + def __str__(self): + s = self.package + if self.versionFrom: + s += 'ver >= ' + self.versionFrom + if self.versionTo: + s += 'ver <= ' + self.versionTo + if self.releaseFrom: + s += 'rel >= ' + self.releaseFrom + if self.releaseTo: + s += 'rel <= ' + self.releaseTo + return s + +def upgradableDep(pkg_name, depinfo): + """determine if package in *repository* satisfies given dependency spec in installdb""" if not installdb.is_installed(depinfo.package): return False else: @@ -26,6 +83,15 @@ def satisfiesDep(pkg_name, depinfo): (version, release) = (pkg.version, pkg.release) return depinfo.satisfies(pkg_name, version, release) +def satisfiesDep(pkg_name, depinfo): + """determine if an *already installed* package can be upgraded given dependency spec in installdb""" + if not installdb.is_installed(depinfo.package): + return False + else: + pkg = packagedb.inst_packagedb.get_package(pkg_name) + (version, release) = (pkg.version, pkg.release) + return depinfo.satisfies(pkg_name, version, release) + def satisfiesDeps(pkg, deps): for dep in deps: if not satisfiesDep(pkg, dep): diff --git a/pisi/install.py b/pisi/install.py index 8f5fb145..de13d466 100644 --- a/pisi/install.py +++ b/pisi/install.py @@ -44,8 +44,12 @@ class Installer: self.files = self.package.files self.pkginfo = self.metadata.package - def install(self): + def install(self, ask_reinstall = True): "entry point" + ui.info('Installing %s, version %s, release %s, build %s\n' % + (self.pkginfo.name, self.pkginfo.version, + self.pkginfo.release, self.pkginfo.build)) + self.ask_reinstall = ask_reinstall self.check_requirements() self.check_relations() self.reinstall() @@ -88,8 +92,9 @@ class Installer: (iversion, irelease) = installdb.get_version(pkg.name) if pkg.version == iversion and pkg.release == irelease: - if not ui.confirm('Re-install same version package?'): - raise InstallError('Package re-install declined') + if self.ask_reinstall: + if not ui.confirm('Re-install same version package?'): + raise InstallError('Package re-install declined') else: upgrade = False # is this an upgrade? @@ -102,7 +107,7 @@ class Installer: upgrade = True # is this a downgrade? confirm this action. - if not upgrade: + if self.ask_reinstall and (not upgrade): if pkg.version < iversion: x = 'Downgrade to old upstream version?' elif pkg.release < irelease: @@ -116,17 +121,17 @@ class Installer: def extractInstall(self): "unzip package in place" - ui.info('Extracting files\n') + ui.info('Extracting files,\n') self.package.extract_dir_flat('install', config.destdir) def storePisiFiles(self): """put files.xml, metadata.xml, actions.py and COMAR scripts somewhere in the file system. We'll need these in future...""" - ui.info('Storing %s\n' % const.files_xml) + ui.info('Storing %s, ' % const.files_xml) self.package.extract_file(const.files_xml, self.package.pkg_dir()) - ui.info('Storing %s\n' % const.metadata_xml) + ui.info('%s.\n' % const.metadata_xml) self.package.extract_file(const.metadata_xml, self.package.pkg_dir()) for pcomar in self.metadata.package.providesComar: diff --git a/pisi/operations.py b/pisi/operations.py index 34f24325..e9603b57 100644 --- a/pisi/operations.py +++ b/pisi/operations.py @@ -28,7 +28,7 @@ def remove_single(package_name): #TODO: check dependencies - ui.info('Removing package %s\n' % package_name) + ui.info('Removing package %s' % package_name) if not installdb.is_installed(package_name): raise Exception('Trying to remove nonexistent package ' + package_name) @@ -47,24 +47,25 @@ def remove_single(package_name): if comard: # FIXME: (return value)... comard.remove(package_name) + ui.info('.\n') -def install_single(pkg): +def install_single(pkg, upgrade = False): """install a single package from URI or ID""" url = PUrl(pkg) # Check if we are dealing with a remote file or a real path of # package filename. Otherwise we'll try installing a package from # the package repository. if url.isRemoteFile() or os.path.exists(url.uri): - install_single_file(pkg) + install_single_file(pkg, upgrade) else: - install_single_name(pkg) + install_single_name(pkg, upgrade) -def install_single_file(pkg_location): +def install_single_file(pkg_location, upgrade = False): """install a package file""" from install import Installer - Installer(pkg_location).install() + Installer(pkg_location).install(not upgrade) -def install_single_name(name): +def install_single_name(name, upgrade = False): """install a single package from ID""" # find package in repository repo = packagedb.which_repo(name) @@ -84,7 +85,7 @@ def install_single_name(name): ui.debug("Package URI: %s\n" % pkg_path) # Package will handle remote file for us! - install_single_file(pkg_path) + install_single_file(pkg_path, upgrade) else: ui.error("Package %s not found in the repository file.\n" % pkg) diff --git a/pisi/specfile.py b/pisi/specfile.py index dbce230a..4984aa5b 100644 --- a/pisi/specfile.py +++ b/pisi/specfile.py @@ -24,6 +24,7 @@ from os.path import basename from xmlext import * from xmlfile import XmlFile from ui import ui +from dependency import DepInfo class PackagerInfo: def __init__(self, node = None): @@ -102,60 +103,6 @@ class PatchInfo: s += ' level:' + self.level return s -class DepInfo: - def __init__(self, node = None): - if node: - self.package = getNodeText(node).strip() - self.versionFrom = getNodeAttribute(node, "versionFrom") - self.versionTo = getNodeAttribute(node, "versionTo") - self.releaseFrom = getNodeAttribute(node, "releaseFrom") - self.releaseTo = getNodeAttribute(node, "releaseTo") - else: - self.versionFrom = self.versionTo = None - self.releaseFrom = self.releaseFrom = None - - def elt(self, xml): - node = xml.newNode("Dependency") - xml.addText(node, self.package) - if self.versionFrom: - node.setAttribute("versionFrom", self.versionFrom) - if self.versionTo: - node.setAttribute("versionTo", self.versionTo) - if self.releaseFrom: - node.setAttribute("releaseFrom", self.versionFrom) - if self.releaseTo: - node.setAttribute("releaseTo", self.versionTo) - return node - - def verify(self): - if not self.package: return False - return True - - def satisfies(self, pkg_name, version, release): - """determine if a package ver. satisfies given dependency spec""" - ret = True - from version import Version - if self.versionFrom: - ret &= Version(version) >= Version(self.versionFrom) - if self.versionTo: - ret &= Version(version) <= Version(self.versionTo) - if self.releaseFrom: - ret &= Version(release) <= Version(self.releaseFrom) - if self.releaseTo: - ret &= Version(release) <= Version(self.releaseTo) - return ret - - def __str__(self): - s = self.package - if self.versionFrom: - s += 'ver >= ' + self.versionFrom - if self.versionTo: - s += 'ver <= ' + self.versionTo - if self.releaseFrom: - s += 'rel >= ' + self.releaseFrom - if self.releaseTo: - s += 'rel <= ' + self.releaseTo - return s class UpdateInfo: def __init__(self, node = None): diff --git a/pisi/toplevel.py b/pisi/toplevel.py index b6470912..d44e1f13 100644 --- a/pisi/toplevel.py +++ b/pisi/toplevel.py @@ -45,7 +45,7 @@ def install(packages): except packagedb.PackageDBError, e: ui.error("PackageDBError: (%s)\n" % e) - ui.error("Package is not installable. Its very likely a dependency problem.\n") + ui.error("Package is not installable.\n") except Exception, e: print e @@ -95,6 +95,7 @@ def install_pkg_names(A): installs""" if len(A)==0: + ui.info('No packages to install.\n') return True # try to construct a pisi graph of packages to @@ -131,6 +132,72 @@ def install_pkg_names(A): return True # everything went OK :) + +def upgrade(A): + upgrade_pkg_names(A) + +def upgrade_pkg_names(A): + """Re-installs packages from the repository, trying to perform + a maximum number of upgrades.""" + + # filter packages that are not installed + Ap = [] + for x in A: + if not installdb.is_installed(x): + ui.info('Package %s is not installed.\n' % x) + continue + (version, release) = installdb.get_version(x) + (versionp, releasep) = (packagedb.get_package(x).version, + packagedb.get_package(x).release) + # BUG: we should use build instead of release + if release < releasep: + Ap.append(x) + else: + #ui.info('Package %s cannot be upgraded. ' % x) + # BUG: where is build no? + ui.info('Package %s is already at its latest version %s,\ + release %s, build ?.\n' + % (x, versionp, releasep)) + A = Ap + + if len(A)==0: + ui.info('No packages to upgrade.\n') + return True + + # try to construct a pisi graph of packages to + # install / reinstall + + G_f = pgraph.PGraph() # construct G_f + + # find the "install closure" graph of G_f by package + # set A using packagedb + print A + for x in A: + G_f.add_package(x) + B = A + #state = {} + while len(B) > 0: + Bp = set() + for x in B: + pkg = packagedb.get_package(x) + print pkg + for dep in pkg.runtimeDeps: + print 'checking ', dep + # add packages that can be upgraded + if not dependency.upgradableDep(x, dep): + if not dep.package in G_f.vertices(): + Bp.add(str(dep.package)) + G_f.add_dep(x, dep) + B = Bp + G_f.write_graphviz(sys.stdout) + l = G_f.topological_sort() + l.reverse() + print l + for x in l: + operations.install_single_name(x, True) + + return True # everything went OK :) + def remove(A): """remove set A of packages from system (A is a list of package names)"""