diff --git a/pisi/atomicoperations.py b/pisi/atomicoperations.py index 5d01bb37..3884f62c 100644 --- a/pisi/atomicoperations.py +++ b/pisi/atomicoperations.py @@ -23,6 +23,7 @@ import pisi import pisi.context as ctx import pisi.packagedb as packagedb import pisi.dependency as dependency +import pisi.conflict import pisi.util as util from pisi.specfile import * from pisi.metadata import MetaData @@ -141,9 +142,9 @@ class Install(AtomicOperation): def check_relations(self): # check conflicts - for pkg in self.metadata.package.conflicts: - if ctx.installdb.is_installed(self.pkginfo): - raise Error(_("Package conflicts %s") % pkg) + for conf in self.metadata.package.conflicts: + if pisi.conflict.installed_package_conflicts(conf): + raise Error(_("Package conflicts %s") % conf) # check dependencies if not ctx.config.get_option('ignore_dependency'): diff --git a/pisi/conflict.py b/pisi/conflict.py new file mode 100644 index 00000000..a5a5b4ba --- /dev/null +++ b/pisi/conflict.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005, TUBITAK/UEKAE +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# Please read the COPYING file. +# + +"""conflict analyzer""" + +import gettext +__trans = gettext.translation('pisi', fallback=True) +_ = __trans.ugettext + +import pisi.context as ctx +from pisi.version import Version +import pisi.pxml.autoxml as autoxml +from pisi.util import Checks +import pisi.itembyrepodb + +class Conflict: + + __metaclass__ = autoxml.autoxml + + s_Package = [autoxml.String, autoxml.mandatory] + a_version = [autoxml.String, autoxml.optional] + a_versionFrom = [autoxml.String, autoxml.optional] + a_versionTo = [autoxml.String, autoxml.optional] + a_release = [autoxml.String, autoxml.optional] + a_releaseFrom = [autoxml.String, autoxml.optional] + a_releaseTo = [autoxml.String, autoxml.optional] + + def __str__(self): + s = self.package + if self.versionFrom: + s += _(" version >= ") + self.versionFrom + if self.versionTo: + s += _(" version <= ") + self.versionTo + if self.releaseFrom: + s += _(" release >= ") + self.releaseFrom + if self.releaseTo: + s += _(" release <= ") + self.releaseTo + return s + + def conflicts(self, pkg_name, version, release): + """determine if a package ver. conflicts with given conflicting spec""" + ret = True + v = Version(version) + if self.version: + ret &= v == Version(self.version) + if self.versionFrom: + ret &= v >= Version(self.versionFrom) + if self.versionTo: + ret &= v <= Version(self.versionTo) + r = Version(release) + if self.release: + ret &= r == Version(self.release) + if self.releaseFrom: + ret &= r >= Version(self.releaseFrom) + if self.releaseTo: + ret &= r <= Version(self.releaseTo) + return ret + +def installed_package_conflicts(confinfo): + """determine if an installed package in *repository* conflicts with +given conflicting spec""" + pkg_name = confinfo.package + if not ctx.installdb.is_installed(pkg_name): + return False + else: + pkg = ctx.packagedb.get_package(pkg_name, pisi.itembyrepodb.installed) + (version, release) = (pkg.version, pkg.release) + return confinfo.conflicts(pkg_name, version, release) + +def package_conflicts(pkg, confs): + for c in confs: + if pkg.name == c.package and c.conflicts(pkg.name, pkg.version, pkg.release): + return c + + return None diff --git a/pisi/operations.py b/pisi/operations.py index fda5e646..616923b5 100644 --- a/pisi/operations.py +++ b/pisi/operations.py @@ -22,6 +22,7 @@ import pisi.context as ctx from pisi.uri import URI import pisi.util as util import pisi.dependency as dependency +import pisi.conflict import pisi.pgraph as pgraph import pisi.packagedb as packagedb import pisi.repodb @@ -194,11 +195,11 @@ in the respective order to satisfy extra dependencies: upgrade_pisi() def check_conflict(pkg): - conflicts = [] + conflicts = {} for c in pkg.conflicts: - if ctx.installdb.is_installed(c): - conflicts.append(c) + if pisi.conflict.installed_package_conflicts(c): + conflicts[c.package] = str(c) return conflicts @@ -209,16 +210,23 @@ def calculate_conflicts(order, packagedb): for x in order: pkg = packagedb.get_package(x) - B_p = set(check_conflict(pkg)) + B_p = check_conflict(pkg) if B_p: - pkg_conflicts[x] = B_p - C = C.union(B_p) + pkg_conflicts[x] = B_p.values() + C = C.union(B_p.keys()) - B_i = B_0.intersection(set(pkg.conflicts)) + B_i = B_0.intersection(set(map(lambda p:p.package, pkg.conflicts))) + D_i = set() # check if there are any conflicts within the packages that are # going to be installed - if B_i: - D = D.union(B_i) + for p in map(lambda x:packagedb.get_package(x), B_i): + # check if they are really conflicting + conflicted = pisi.conflict.package_conflicts(p, pkg.conflicts) + if conflicted: + D_i.add(str(conflicted)) + + if D_i: + D = D.union(D_i) D.add(pkg.name) return (C, D, pkg_conflicts) diff --git a/pisi/scenarioapi/pspec.py b/pisi/scenarioapi/pspec.py index 814b65e9..455057a2 100644 --- a/pisi/scenarioapi/pspec.py +++ b/pisi/scenarioapi/pspec.py @@ -12,6 +12,7 @@ from pisi.specfile import SpecFile, Package, Update, Path from pisi.dependency import Dependency +from pisi.conflict import Conflict from pisi.pxml.autoxml import LocalText class Pspec: @@ -71,6 +72,16 @@ class Pspec: self.package.packageDependencies.remove(dep) def add_conflicts(self, conflicts): + # special case of given one conflict package + # with conflict versioning info [**kw, name] + # [{"versionFrom":"0.4.2"}, "udev"] + if type(conflicts[0]) == dict: + conf = Conflict() + (kw, conf.package) = conflicts + conf.__dict__[kw.keys()[0]] = kw.values()[0] + self.package.conflicts.append(conf) + return + for con in conflicts: self.package.conflicts.append(con) diff --git a/pisi/scenarioapi/withops.py b/pisi/scenarioapi/withops.py index 688e3752..ce8f3356 100644 --- a/pisi/scenarioapi/withops.py +++ b/pisi/scenarioapi/withops.py @@ -49,6 +49,23 @@ def with_added_dependencies(*deps): def with_removed_dependencies(*deps): return with_action(DEPENDENCY, REMOVED, deps) +def with_added_conflict(package, **kw): + assert len(kw) <= 1 + + if not len(kw): + return with_action(CONFLICT, ADDED, [package]) + + if (kw.has_key("versionFrom") or + kw.has_key("versionTo") or + kw.has_key("version") or + kw.has_key("releaseFrom") or + kw.has_key("releaseTo") or + kw.has_key("release")): + return with_action(CONFLICT, ADDED, [kw, package]) + + # pass other keywords. + return with_action(CONFLICT, ADDED, [package]) + def with_added_dependency(package, **kw): assert len(kw) <= 1 diff --git a/pisi/specfile.py b/pisi/specfile.py index a1b4b1e7..17f6b6ba 100644 --- a/pisi/specfile.py +++ b/pisi/specfile.py @@ -28,6 +28,7 @@ from pisi.pxml.xmlfile import XmlFile import pisi.pxml.autoxml as autoxml import pisi.context as ctx from pisi.dependency import Dependency +from pisi.conflict import Conflict import pisi.dependency import pisi.component as component from pisi.util import Checks @@ -173,7 +174,7 @@ class Package: t_PackageDependencies = [ [Dependency], autoxml.optional, "RuntimeDependencies/Dependency"] t_ComponentDependencies = [ [autoxml.String], autoxml.optional, "RuntimeDependencies/Component"] t_Files = [ [Path], autoxml.optional] - t_Conflicts = [ [autoxml.String], autoxml.optional, "Conflicts/Package"] + t_Conflicts = [ [Conflict], autoxml.optional, "Conflicts/Package"] t_ProvidesComar = [ [ComarProvide], autoxml.optional, "Provides/COMAR"] #t_RequiresComar = [ [autoxml.String], autoxml.mandatory, "Requires/COMAR"] t_AdditionalFiles = [ [AdditionalFile], autoxml.optional] diff --git a/scenarios/bug3865scen.py b/scenarios/bug3865scen.py new file mode 100644 index 00000000..1762ff61 --- /dev/null +++ b/scenarios/bug3865scen.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# Scenario : bug3865scen.py +# +# Source : http://bugs.pardus.org.tr/show_bug.cgi?id=3865 +# +# Problem : A need arised between the packages libmp4v2 and faad2 that libmp4v2 has to +# define that it only conflicts with the faad2 until release 3. +# +# Problem Description: +# +# PiSi is incapable to define conflict versioning info. libmp4v2 only conflicts with faad2 +# package's release no 3. But because PiSi does not understand that, two packages can not +# be installed together. +# +# Expected: +# +# Versioning info can be defined as in dependency informations. + + +from pisi.scenarioapi.scenario import * + +LIBMP4V2 = "libmp4v2" +FAAD2 = "faad2" + +let_repo_had(LIBMP4V2) +let_repo_had(FAAD2, with_version("0.2.1")) + +let_pisi_had(LIBMP4V2, FAAD2) + +def run(): + repo_version_bumped(LIBMP4V2, with_added_conflict(FAAD2)) + repo_updated_index() + pisi_upgraded() diff --git a/tests/conflicttests.py b/tests/conflicttests.py deleted file mode 100644 index 0976f28b..00000000 --- a/tests/conflicttests.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (C) 2006, TUBITAK/UEKAE -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation; either version 2 of the License, or (at your option) -# any later version. -# -# Please read the COPYING file. -# -# Author: Faik Uygur - -import unittest -import pisi.context as ctx -import pisi.api -import pisi - -import testcase - -class ConflictTestCase(testcase.TestCase): - def setUp(self): - testcase.TestCase.setUp(self) - - d_t = {} - - # instx are the conflicting packages that are installed on the system. - # notinstx are conflicting packages that are not installed on the system. - # a-j are the packages to be installed. - packages = {"a": ["notinst1", "notinst2", "b"], - "b": ["a", "e", "f"], - "c": ["g", "h"], - "d": [], - "e": ["j"], - "f" : ["inst1", "notinst2", "f", "inst3", "notinst3"], - "g" : ["inst4", "notinst1"], - "h" : ["inst2", "inst3", "inst4", "notinst2"], - "i" : ["notinst2", "inst1"], - "j" : []} - - # packages that are installed on the system - ctx.installdb.purge('inst1') - ctx.installdb.purge('inst2') - ctx.installdb.purge('inst3') - ctx.installdb.purge('inst4') - ctx.installdb.install('inst1', '1.2', '2', '3') - ctx.installdb.install('inst2', '2.4', '2', '3') - ctx.installdb.install('inst3', '5.2', '2', '3') - ctx.installdb.install('inst4', '2.1', '2', '3') - - for name in packages.keys(): - pkg = pisi.specfile.Package() - pkg.name = name - pkg.conflicts = packages[name] - d_t[name] = pkg - - class PackageDB: - def get_package(self, key): - return d_t[str(key)] - - self.packagedb = PackageDB() - - def tearDown(self, ): - pisi.api.finalize() - - def testConflictWithEachOther(self): - packages = ["a", "b", "c", "d", "e"] - (C, D, pkg_conflicts) = pisi.operations.calculate_conflicts(packages, self.packagedb) - self.assert_(set(['a', 'b', 'e']) == D) - - def testConflictWithInstalled(self): - packages = ["g", "h", "i"] - (C, D, pkg_conflicts) = pisi.operations.calculate_conflicts(packages, self.packagedb) - self.assert_(not D) - self.assert_(set(['inst1', 'inst2', 'inst3', 'inst4']) == C) - self.assert_("notinst1" not in pkg_conflicts["g"]) - - def testConflictWithEachOtherAndInstalled(self): - packages = ["a", "b", "g", "h", "i"] - (C, D, pkg_conflicts) = pisi.operations.calculate_conflicts(packages, self.packagedb) - self.assert_(set(['a', 'b']) == D) - self.assert_(set(['inst1', 'inst2', 'inst3', 'inst4']) == C) - self.assert_(set(['inst2', 'inst3', 'inst4']) == pkg_conflicts["h"]) - -suite = unittest.makeSuite(ConflictTestCase)