-
Notifications
You must be signed in to change notification settings - Fork 253
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dep_check: use DNF to optimize overlapping virtual || deps (bug 632026)
Deps like these: || ( foo bar ) || ( bar baz ) Translate to disjunctive normal form (DNF): || ( ( foo bar ) ( foo baz ) ( bar bar ) ( bar baz ) ) Using DNF, if none of the packages are currently installed, then the ( bar bar ) choice will be automatically preferred since it is satisfied by the fewest number of packages. If the ( foo baz ) choice is already satisfied, then that choice will be preferred instead. Since DNF results in exponential explosion of the formula, only use DNF for the parts of the dependencies that have overlapping atoms. In order to simplify the implementation of the dnf_convert function, this patch also fixes _expand_new_virtuals to normalize results in the same way as use_reduce (with no redundant nested lists). Bug: https://bugs.gentoo.org/632026
- Loading branch information
Showing
5 changed files
with
435 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# Copyright 2017 Gentoo Foundation | ||
# Distributed under the terms of the GNU General Public License v2 | ||
|
||
from __future__ import unicode_literals | ||
|
||
import itertools | ||
|
||
|
||
def dnf_convert(dep_struct): | ||
""" | ||
Convert dep_struct to disjunctive normal form (DNF), where dep_struct | ||
is either a conjunction or disjunction of the form produced by | ||
use_reduce(opconvert=True). | ||
""" | ||
# Normalize input to have a top-level conjunction. | ||
if isinstance(dep_struct, list): | ||
if dep_struct and dep_struct[0] == '||': | ||
dep_struct = [dep_struct] | ||
else: | ||
dep_struct = [dep_struct] | ||
|
||
conjunction = [] | ||
disjunctions = [] | ||
|
||
for x in dep_struct: | ||
if isinstance (x, list): | ||
assert x | ||
if x[0] == '||': | ||
if any(isinstance(element, list) for element in x): | ||
x_dnf = ['||'] | ||
for element in x[1:]: | ||
if isinstance(element, list): | ||
# Due to normalization, a disjunction must not be | ||
# nested directly in another disjunction, so this | ||
# must be a conjunction. | ||
assert element and element[0] != '||' | ||
element = dnf_convert(element) | ||
if contains_disjunction(element): | ||
assert (len(element) == 1 and | ||
element[0] and element[0][0] == '||') | ||
x_dnf.extend(element[0][1:]) | ||
else: | ||
x_dnf.append(element) | ||
else: | ||
x_dnf.append(element) | ||
x = x_dnf | ||
disjunctions.append(x) | ||
else: | ||
for x in dnf_convert(x): | ||
if isinstance (x, list): | ||
# Due to normalization, a conjunction must not be | ||
# nested directly in another conjunction, so this | ||
# must be a disjunction. | ||
assert x and x[0] == '||' | ||
disjunctions.append(x) | ||
else: | ||
conjunction.append(x) | ||
else: | ||
conjunction.append(x) | ||
|
||
if disjunctions and (conjunction or len(disjunctions) > 1): | ||
dnf_form = ['||'] | ||
for x in itertools.product(*[x[1:] for x in disjunctions]): | ||
normalized = conjunction[:] | ||
for element in x: | ||
if isinstance(element, list): | ||
normalized.extend(element) | ||
else: | ||
normalized.append(element) | ||
dnf_form.append(normalized) | ||
result = [dnf_form] | ||
else: | ||
result = conjunction + disjunctions | ||
|
||
return result | ||
|
||
|
||
def contains_disjunction(dep_struct): | ||
""" | ||
Search for a disjunction contained in dep_struct, where dep_struct | ||
is either a conjunction or disjunction of the form produced by | ||
use_reduce(opconvert=True). If dep_struct is a disjunction, then | ||
this only returns True if there is a nested disjunction. Due to | ||
normalization, recursion is only needed when dep_struct is a | ||
disjunction containing a conjunction. If dep_struct is a conjunction, | ||
then it is assumed that normalization has elevated any nested | ||
disjunctions to the top-level. | ||
""" | ||
is_disjunction = dep_struct and dep_struct[0] == '||' | ||
for x in dep_struct: | ||
if isinstance(x, list): | ||
assert x | ||
if x[0] == '||': | ||
return True | ||
elif is_disjunction and contains_disjunction(x): | ||
return True | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Copyright 2017 Gentoo Foundation | ||
# Distributed under the terms of the GNU General Public License v2 | ||
|
||
from portage.tests import TestCase | ||
from portage.dep import use_reduce | ||
from portage.dep._dnf import dnf_convert | ||
|
||
class DNFConvertTestCase(TestCase): | ||
|
||
def testDNFConvert(self): | ||
|
||
test_cases = ( | ||
( | ||
'|| ( A B ) || ( C D )', | ||
[['||', ['A', 'C'], ['A', 'D'], ['B', 'C'], ['B', 'D']]], | ||
), | ||
( | ||
'|| ( A B ) || ( B C )', | ||
[['||', ['A', 'B'], ['A', 'C'], ['B', 'B'], ['B', 'C']]], | ||
), | ||
( | ||
'|| ( A ( B C D ) )', | ||
[['||', 'A', ['B', 'C', 'D']]], | ||
), | ||
( | ||
'|| ( A ( B C D ) ) E', | ||
[['||', ['E', 'A'], ['E', 'B', 'C', 'D']]], | ||
), | ||
( | ||
'|| ( A ( B C ) ) || ( D E ) F', | ||
[['||', ['F', 'A', 'D'], ['F', 'A', 'E'], ['F', 'B', 'C', 'D'], ['F', 'B', 'C', 'E']]], | ||
), | ||
( | ||
'|| ( A ( B C || ( D E ) ) ( F G ) H )', | ||
[['||', 'A', ['B', 'C', 'D'], ['B', 'C', 'E'], ['F', 'G'], 'H']], | ||
), | ||
( | ||
'|| ( A ( B C || ( D E ) ) F )', | ||
[['||', 'A', ['B', 'C', 'D'], ['B', 'C', 'E'], 'F']], | ||
), | ||
( | ||
'|| ( A ( C || ( D E ) || ( F G ) ) H )', | ||
[['||', 'A', ['C', 'D', 'F'], ['C', 'D', 'G'], ['C', 'E', 'F'], ['C', 'E', 'G'], 'H']], | ||
), | ||
) | ||
|
||
for dep_str, result in test_cases: | ||
self.assertEqual(dnf_convert(use_reduce(dep_str, opconvert=True)), result) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Copyright 2017 Gentoo Foundation | ||
# Distributed under the terms of the GNU General Public License v2 | ||
|
||
from portage.tests import TestCase | ||
from portage.dep import Atom, use_reduce | ||
from portage.dep.dep_check import _overlap_dnf | ||
|
||
class OverlapDNFTestCase(TestCase): | ||
|
||
def testOverlapDNF(self): | ||
|
||
test_cases = ( | ||
( | ||
'|| ( cat/A cat/B ) cat/E || ( cat/C cat/D )', | ||
['cat/E', ['||', 'cat/A', 'cat/B'], ['||', 'cat/C', 'cat/D']], | ||
), | ||
( | ||
'|| ( cat/A cat/B ) cat/D || ( cat/B cat/C )', | ||
['cat/D', ['||', ['cat/A', 'cat/B'], ['cat/A', 'cat/C'], ['cat/B', 'cat/B'], ['cat/B', 'cat/C']]], | ||
), | ||
( | ||
'|| ( cat/A cat/B ) || ( cat/C cat/D ) || ( ( cat/B cat/E ) cat/F )', | ||
[['||', ['cat/A', 'cat/B', 'cat/E'], ['cat/A', 'cat/F'], ['cat/B', 'cat/B', 'cat/E'], ['cat/B', 'cat/F']], ['||', 'cat/C', 'cat/D']], | ||
), | ||
) | ||
|
||
for dep_str, result in test_cases: | ||
self.assertEqual(_overlap_dnf(use_reduce(dep_str, token_class=Atom, opconvert=True)), result) |
Oops, something went wrong.