From 7635fb6d3b19093842524f86119236da7ec84d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awomir=20Nizio?= Date: Sat, 24 Feb 2018 18:47:16 +0100 Subject: [PATCH 1/4] [entropy.server] create a simple abstration/interface for dep. rewrite --- lib/entropy/server/interfaces/main.py | 75 +++++++++++++++++++-------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/lib/entropy/server/interfaces/main.py b/lib/entropy/server/interfaces/main.py index 28fb49807..6574f4cb3 100644 --- a/lib/entropy/server/interfaces/main.py +++ b/lib/entropy/server/interfaces/main.py @@ -2,8 +2,9 @@ """ @author: Fabio Erculiani + @author: Slawomir Nizio @contact: lxnay@sabayon.org - @copyright: Fabio Erculiani + @copyright: Fabio Erculiani, Slawomir Nizio @license: GPL-2 B{Entropy Package Manager Server Main Interfaces}. @@ -990,6 +991,31 @@ class ServerSystemSettingsPlugin(SystemSettingsPlugin): return data rewrite_content = self.__generic_parser(rewrite_file) + def add_dep_handler(): + metadata = {'replaces_str': _("added"), + 'action': "add"} + return metadata + + def remove_dep_handler(compiled_pattern): + metadata = {'replaces_str': _("removed"), + 'action': "remove", + 'does_dep_match_func': lambda dep_string: compiled_pattern.match(dep_string) is not None, + 'replaces': []} + return metadata + + def replace_dep_handler(compiled_pattern, replaces): + def _do(replace, dep_string): + new_dep_string, number_of_subs_made = \ + compiled_pattern.subn(replace, dep_string) + return new_dep_string, bool(number_of_subs_made) + + metadata = {'replaces_str': "=> " + ', '.join(replaces), + 'action': "replace", + 'does_dep_match_func': lambda dep_string: compiled_pattern.match(dep_string) is not None, + 'replaces': replaces, + 'do': _do} + return metadata + for line in rewrite_content: params = line.split() if len(params) < 2: @@ -1007,8 +1033,18 @@ class ServerSystemSettingsPlugin(SystemSettingsPlugin): except re.error: # invalid pattern continue + + if compiled_pattern is None: + # this means that user is asking to add dep_pattern + # as a dependency to package + metadata = add_dep_handler() + elif not replaces: + # this means that user is asking to remove dep_pattern + metadata = remove_dep_handler(compiled_pattern) + else: + metadata = replace_dep_handler(compiled_pattern, replaces) # use this key to make sure to not overwrite similar entries - data[(pkg_match, pattern)] = (compiled_pattern, replaces) + data[(pkg_match, pattern)] = metadata self._mod_rewrite_data = data return data @@ -7385,21 +7421,11 @@ class Server(Client): header = brown(" @@ ") ) for dep_string_rewrite, dep_pattern in rewrites_enabled: - compiled_pattern, replaces = \ - dep_rewrite[(dep_string_rewrite, dep_pattern)] - if compiled_pattern is None: - # this means that user is asking to add dep_pattern - # as a dependency to package - replaces_str = _("added") - elif not replaces: - # this means that user is asking to remove dep_pattern - replaces_str = _("removed") - else: - replaces_str = "=> " + ', '.join(replaces) + handler = dep_rewrite[(dep_string_rewrite, dep_pattern)] self.output( "%s %s" % ( purple(dep_pattern), - replaces_str, + handler['replaces_str'], ), importance = 1, level = "info", @@ -7463,8 +7489,12 @@ class Server(Client): for key in rewrites_enabled: dep_string_rewrite, dep_pattern = key - compiled_pattern, replaces = dep_rewrite[key] - if compiled_pattern is None: + handler = dep_rewrite[key] + action = handler['action'] + + assert action in ("add", "remove", "replace") + + if action == "add": # user is asking to add dep_pattern to dependency list dep_pattern_string, dep_pattern_type, conflict = \ _extract_dep_add_from_dep_pattern(dep_pattern) @@ -7475,19 +7505,18 @@ class Server(Client): (dep_pattern_string, dep_pattern_type)) continue - if not compiled_pattern.match(dep_string): + if not handler['does_dep_match_func'](dep_string): # dep_string not matched, skipping continue matched_pattern = True - if not replaces: - # then it's a removal + if action == "remove": dep_string_matched = True - for replace in replaces: - new_dep_string, number_of_subs_made = \ - compiled_pattern.subn(replace, dep_string) - if number_of_subs_made: + for replace in handler['replaces']: + new_dep_string, subst_made = \ + handler['do'](replace, dep_string) + if subst_made: dep_string_matched = True if new_dep_string and (new_dep_string != "-"): From 9e3f45f4c3f8a8fa3137c27ee3661197fe4d3c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awomir=20Nizio?= Date: Sat, 24 Feb 2018 18:49:39 +0100 Subject: [PATCH 2/4] [entropy.server] simplify data container for dep_rewrite Previously it has a dict with complex key (with the need to pack and unpack it in places where it was used), and data was both part of the keys and values. It had the small advantage of removing duplicates, but changes for them are very low and they are harmless anyway. --- lib/entropy/server/interfaces/main.py | 45 ++++++++++++++------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/lib/entropy/server/interfaces/main.py b/lib/entropy/server/interfaces/main.py index 6574f4cb3..45021f3c5 100644 --- a/lib/entropy/server/interfaces/main.py +++ b/lib/entropy/server/interfaces/main.py @@ -985,31 +985,35 @@ class ServerSystemSettingsPlugin(SystemSettingsPlugin): if cached is not None: return cached - data = {} + data = [] rewrite_file = Server._get_conf_dep_rewrite_file() if not os.path.isfile(rewrite_file): return data rewrite_content = self.__generic_parser(rewrite_file) - def add_dep_handler(): - metadata = {'replaces_str': _("added"), - 'action': "add"} + def add_dep_handler(dep_pattern): + metadata = {'dep_pattern_str': dep_pattern, + 'replaces_str': _("added"), + 'action': "add", + 'add_what': dep_pattern} return metadata - def remove_dep_handler(compiled_pattern): - metadata = {'replaces_str': _("removed"), + def remove_dep_handler(dep_pattern, compiled_pattern): + metadata = {'dep_pattern_str': dep_pattern, + 'replaces_str': _("removed"), 'action': "remove", 'does_dep_match_func': lambda dep_string: compiled_pattern.match(dep_string) is not None, 'replaces': []} return metadata - def replace_dep_handler(compiled_pattern, replaces): + def replace_dep_handler(dep_pattern, compiled_pattern, replaces): def _do(replace, dep_string): new_dep_string, number_of_subs_made = \ compiled_pattern.subn(replace, dep_string) return new_dep_string, bool(number_of_subs_made) - metadata = {'replaces_str': "=> " + ', '.join(replaces), + metadata = {'dep_pattern_str': dep_pattern, + 'replaces_str': "=> " + ', '.join(replaces), 'action': "replace", 'does_dep_match_func': lambda dep_string: compiled_pattern.match(dep_string) is not None, 'replaces': replaces, @@ -1037,14 +1041,14 @@ class ServerSystemSettingsPlugin(SystemSettingsPlugin): if compiled_pattern is None: # this means that user is asking to add dep_pattern # as a dependency to package - metadata = add_dep_handler() + metadata = add_dep_handler(pattern) elif not replaces: # this means that user is asking to remove dep_pattern - metadata = remove_dep_handler(compiled_pattern) + metadata = remove_dep_handler(pattern, compiled_pattern) else: - metadata = replace_dep_handler(compiled_pattern, replaces) + metadata = replace_dep_handler(pattern, compiled_pattern, replaces) # use this key to make sure to not overwrite similar entries - data[(pkg_match, pattern)] = metadata + data.append((pkg_match, metadata)) self._mod_rewrite_data = data return data @@ -7393,7 +7397,7 @@ class Server(Client): rewrites_enabled = [] wildcard_rewrite = False - for dep_string_rewrite, dep_pattern in dep_rewrite: + for dep_string_rewrite, handler in dep_rewrite: # magic catch-all support if dep_string_rewrite == "*": pkg_id, rc = None, 0 @@ -7402,7 +7406,7 @@ class Server(Client): wildcard_rewrite = False pkg_id, rc = tmp_repo.atomMatch(dep_string_rewrite) if rc == 0: - rewrites_enabled.append((dep_string_rewrite, dep_pattern)) + rewrites_enabled.append((dep_string_rewrite, handler)) tmp_repo.close() if not rewrites_enabled: @@ -7420,11 +7424,10 @@ class Server(Client): level = "info", header = brown(" @@ ") ) - for dep_string_rewrite, dep_pattern in rewrites_enabled: - handler = dep_rewrite[(dep_string_rewrite, dep_pattern)] + for dep_string_rewrite, handler in rewrites_enabled: self.output( "%s %s" % ( - purple(dep_pattern), + purple(handler['dep_pattern_str']), handler['replaces_str'], ), importance = 1, @@ -7486,18 +7489,16 @@ class Server(Client): dep_string_matched = False matched_pattern = False - for key in rewrites_enabled: + for dep_string_rewrite, handler in rewrites_enabled: - dep_string_rewrite, dep_pattern = key - handler = dep_rewrite[key] action = handler['action'] assert action in ("add", "remove", "replace") if action == "add": - # user is asking to add dep_pattern to dependency list + # user is asking to add a dependency to dependency list dep_pattern_string, dep_pattern_type, conflict = \ - _extract_dep_add_from_dep_pattern(dep_pattern) + _extract_dep_add_from_dep_pattern(handler['add_what']) if conflict: conflicts.add(dep_pattern_string) else: From 0a56ec74b8b94169282d3ab3667b7551b74fe87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awomir=20Nizio?= Date: Sat, 24 Feb 2018 18:56:50 +0100 Subject: [PATCH 3/4] [entropy.core,entropy.server] implement new dep. rewrite parser It uses a simple rules without the need to use complex regexes. It is not as powerful as the existing approaches so both are complementary. For example, this: foo/bar (.*)app-crypt/pinentry(.*)\[gtk\] \1app-crypt/pinentry-gtk2\2 expresses the intention that can be expressed simpler: rewrite foo/bar from-dep=app-crypt/pinentry to-dep=app-crypt/pinentry-gtk2 if-dep-has-use=gtk drop-use=gtk --- .../packages.server.dep_rewrite.example | 23 ++ lib/entropy/dep.py | 133 ++++++++++- lib/entropy/server/interfaces/main.py | 52 +++- lib/tests/dep.py | 223 ++++++++++++++++++ 4 files changed, 423 insertions(+), 8 deletions(-) diff --git a/conf/packages/packages.server.dep_rewrite.example b/conf/packages/packages.server.dep_rewrite.example index 52b43b6c6..cb5a239a2 100644 --- a/conf/packages/packages.server.dep_rewrite.example +++ b/conf/packages/packages.server.dep_rewrite.example @@ -10,6 +10,8 @@ # AND or OR operators: ( app-foo/bar & foo-app/rab ) | app-nice/woot # Please take this into consideration when writing dep rewrites. +# 1. Regex-based dependency rewrite syntax. + # LINE CONSTRUCTION: # <::pkg::> <::dep_pattern::> <::dep_replace_1::> [<::dep_replace_2::> ...] # ::pkg:: = package containing dependency to match, or * for catch-all @@ -59,3 +61,24 @@ # Apply a dep_rewrite rule to any package being added: # * <::dep_pattern::> <::dep_replace_1::> [<::dep_replace_2::> ...] # In other words, set <::pkg::> to * + + +# 2. Token-based dependency rewrite syntax. + +# This syntax supports only modification to dependencies, not addition nor removal. +# rewrite <::pkg::> <::rule::> +# where <::pkg::> is matched the same was as explained above, +# and <::rule::> is: +# +# [] [] +# (order does not matter). +# +# To a dependency to be rewritten, ::dep_atom:: must match and ::use_to_match:: if present. +# Dependency will be rewritten as ::new_dep_atom::. +# If present in rule, ::use_to_remove:: will be removed if dependency contains it. +# +# ::dep_atom:: and ::new_dep_atom:: are category/package names, without version, slot etc. +# +# Examples: +# rewrite foo/bar from-dep=app-crypt/pinentry to-dep=app-crypt/pinentry-gtk2 if-dep-has-use=gtk drop-use=gtk +# will change foo/bar's dependency "app-crypt/pinentry[gtk,qt5]" to "app-crypt/pinentry-gtk2[qt5]". diff --git a/lib/entropy/dep.py b/lib/entropy/dep.py index fbbe1108e..c88533c20 100644 --- a/lib/entropy/dep.py +++ b/lib/entropy/dep.py @@ -3,8 +3,9 @@ """ @author: Fabio Erculiani + @author: Slawomir Nizio @contact: lxnay@sabayon.org - @copyright: Fabio Erculiani + @copyright: Fabio Erculiani, Slawomir Nizio @license: GPL-2 B{Entropy dependency functions module}. @@ -1241,3 +1242,133 @@ def expand_dependencies(dependencies, entropy_repository_list, pkg_deps.append((dep, dep_type)) return pkg_deps + +class WrongRewriteRuleError(Exception): + """ + Exception thrown when the new dep_rewrite mechanism encounters a badly + formatted rule. + """ + pass + + +class DependencyRewriter(object): + """ + Class that implements the new dependency rewrite schema. + It is simpler to use but less powerful than the regex based one, so the two + are complementary. + """ + _use_in_depstring_re = re.compile(r"\[([^\]]+)\]") + + def __init__(self, deps, rule): + self.deps = deps + self._rule = rule + self._rule_dict = self._get_rewrite_rules_dict(rule) + self.matched = None + self.changed = None + + self._dep_has_use, self._modify_use = self._setup_default_funcs() + + + @staticmethod + def _get_rewrite_rules_dict(rule): + known_rule_keys = set(["if-dep-has-use", "from-dep", "to-dep", "drop-use"]) + mandatory_keys = set(["from-dep", "to-dep"]) + + rule_dict = {} + for rule_word in rule.split(): + try: + op, arg = rule_word.split("=", 1) + except ValueError: + raise WrongRewriteRuleError("Badly formatted line with '%s'" % (rule_word,)) + rule_dict[op] = arg + + if op not in known_rule_keys: + raise WrongRewriteRuleError("Bad key '%s'" % (op,)) + + missing_keys = mandatory_keys - set(rule_dict.keys()) + if missing_keys: + raise WrongRewriteRuleError("Incomplete rewrite rule") + + return rule_dict + + + def _setup_default_funcs(self): + if 'if-dep-has-use' in self._rule_dict: + dep_has_use_func = lambda deps_: self._rule_dict['if-dep-has-use'] in deps_ + else: + dep_has_use_func = lambda _: True + + if 'drop-use' in self._rule_dict: + modify_use_func = lambda dep_postfix, use_flags_list, clean_use_flags_list: \ + DependencyRewriter._use_in_depstring_re.sub( + self._filter_use(use_flags_list, clean_use_flags_list, self._rule_dict['drop-use']), + dep_postfix, + count=1) + else: + modify_use_func = lambda dep_postfix, _1, _2: dep_postfix + + return dep_has_use_func, modify_use_func + + + @staticmethod + def _filter_use(use_list, clean_use_list, use_to_remove): + filtered_use = [] + for orig_use, clean_use in zip(use_list, clean_use_list): + if clean_use != use_to_remove: + filtered_use.append(orig_use) + + if filtered_use: + result = "[%s]" % (",".join(filtered_use),) + else: + result = "" + + return result + + + def rewrite(self): + """ + Do a rewrite of dependencies and set attributes: self.deps (rewritten + dependencies), self.matched (any dependency matched its prerequisites) + and self.changed (at least one dependency was modifed). + + Value of these attributes is not defined before executing this method. + """ + matched = False + changed = False + + new_deps = [] + for dep in self.deps: + clean_dep = dep_getkey(dep) + dep_prefix, dep_postfix = dep.split(clean_dep, 1) + + use_flags_list, clean_use_flags_list = self._use_flags_from_dep(dep_postfix) + + if self._rule_dict['from-dep'] == clean_dep and self._dep_has_use(clean_use_flags_list): + new_dep = dep_prefix + self._rule_dict['to-dep'] + self._modify_use(dep_postfix, use_flags_list, clean_use_flags_list) + new_deps.append(new_dep) + matched = True + if dep != new_dep: + changed = True + else: + new_deps.append(dep) + + self.matched = matched + self.changed = changed + self.deps = new_deps + + + @staticmethod + def _use_flags_from_dep(depstring): + dep_use_match = DependencyRewriter._use_in_depstring_re.search(depstring) + usestring = dep_use_match.group(1) if dep_use_match else "" + dep_use = usestring.split(",") + clean_dep_use = [] + use_postfixes_to_strip = ("?", "(-)", "(+)") + for raw_dep in dep_use: + for use_postfix_to_strip in use_postfixes_to_strip: + if raw_dep.endswith(use_postfix_to_strip): + raw_dep = raw_dep[:-len(use_postfix_to_strip)] + clean_dep_use += [raw_dep] + + assert len(dep_use) == len(clean_dep_use) + return dep_use, clean_dep_use diff --git a/lib/entropy/server/interfaces/main.py b/lib/entropy/server/interfaces/main.py index 45021f3c5..9633d9d7d 100644 --- a/lib/entropy/server/interfaces/main.py +++ b/lib/entropy/server/interfaces/main.py @@ -1017,13 +1017,51 @@ class ServerSystemSettingsPlugin(SystemSettingsPlugin): 'action': "replace", 'does_dep_match_func': lambda dep_string: compiled_pattern.match(dep_string) is not None, 'replaces': replaces, - 'do': _do} + 'do_func': _do} + return metadata + + def new_replace_dep_handler(rule): + def _do(_unused, dep_string): + try: + # Should not actually happen as it's tested with does_dep_match_func. + rewriter = entropy.dep.DependencyRewriter([dep_string], rule) + except entropy.dep.WrongRewriteRuleError: + return dep_string, False + + rewriter.rewrite() + return rewriter.deps[0], rewriter.changed + + def _does_dep_match(dep_string): + try: + rewriter = entropy.dep.DependencyRewriter([dep_string], rule) + except entropy.dep.WrongRewriteRuleError: + return False + + rewriter.rewrite() + return rewriter.matched + + metadata = {'dep_pattern_str': "rewrite", + # not translated to match the keyword in configuration + 'replaces_str': "~> %s" % (rule,), + 'action': "replace", + 'does_dep_match_func': _does_dep_match, + 'replaces': [rule], + 'do_func': _do} return metadata for line in rewrite_content: params = line.split() if len(params) < 2: continue + + if params[0] == "rewrite": + if len(params) < 3: + continue + _unused, pkg_match, rule = line.split(None, 2) + metadata = new_replace_dep_handler(rule) + data.append((pkg_match, metadata)) + continue + pkg_match, pattern, replaces = params[0], params[1], params[2:] if pattern.startswith("++"): compiled_pattern = None @@ -7486,7 +7524,7 @@ class Server(Client): for dep_string, dep_value, is_conflict in pkg_deps: - dep_string_matched = False + dep_changed = False matched_pattern = False for dep_string_rewrite, handler in rewrites_enabled: @@ -7512,13 +7550,13 @@ class Server(Client): matched_pattern = True if action == "remove": - dep_string_matched = True + dep_changed = True for replace in handler['replaces']: new_dep_string, subst_made = \ - handler['do'](replace, dep_string) + handler['do_func'](replace, dep_string) if subst_made: - dep_string_matched = True + dep_changed = True if new_dep_string and (new_dep_string != "-"): if is_conflict: @@ -7559,13 +7597,13 @@ class Server(Client): header = darkred(" !!! ") ) - if dep_string_matched: + if dep_changed: if is_conflict: remove_conflicts.add(dep_string) else: remove_dependencies.add(dep_string) - elif (not dep_string_matched) and matched_pattern: + elif (not dep_changed) and matched_pattern: self.output( "%s: %s :: %s" % ( darkred(_("No dependency rewrite made for")), diff --git a/lib/tests/dep.py b/lib/tests/dep.py index bae8e12e1..ad7027906 100644 --- a/lib/tests/dep.py +++ b/lib/tests/dep.py @@ -7,6 +7,7 @@ import unittest from entropy.const import const_convert_to_rawstring, const_convert_to_unicode from entropy.output import print_generic import tests._misc as _misc +import itertools import tempfile import subprocess import shutil @@ -285,6 +286,228 @@ class DepTest(unittest.TestCase): outcome = et.get_entropy_package_sha1(name) self.assertEqual(outcome, expected_outcome) + +class DepsRewriteTestsMixin: + def _validate(self): + rewriter = et.DependencyRewriter(self._deps, self._rule) + rewriter.rewrite() + self.assertEqual(rewriter.deps, self._expected_deps) + + +class MatchedChangedTestsMixin: + def _validate(self): + rewriter = et.DependencyRewriter(self._deps, self._rule) + rewriter.rewrite() + self.assertEqual(rewriter.matched, self._expected_matched) + self.assertEqual(rewriter.changed, self._expected_changed) + + +class DependencyRewriterTests(unittest.TestCase, DepsRewriteTestsMixin): + # Test for the new rewrite rules without regexes. + def test_dep_changed(self): + # inspired by: + # foo/bar (.*)app-text/poppler(.*)(\[.*\]) \1app-text/poppler-glib\2 + self._rule = "from-dep=app-text/poppler to-dep=app-text/poppler-glib" + self._deps = ["app-text/poppler"] + self._expected_deps = ["app-text/poppler-glib"] + self._validate() + + def _test_dep_changed_pkg_data_stays(self, dep_matches): + prefixes = ("", "=", ">=", "<", "~") + postfixes = ("", ":3", "[some]", "[some,use]", ":4[use]") + rule = "from-dep=app-text/poppler to-dep=app-text/poppler-glib" + + if dep_matches: + dep_base = "app-text/poppler" + expected_base = "app-text/poppler-glib" + else: + dep_base = "foobar" + expected_base = dep_base + + for prefix, postfix in itertools.product(prefixes, postfixes): + self._rule = rule + self._deps = [prefix + dep_base + postfix] + self._expected_deps = [prefix + expected_base + postfix] + self._validate() + + def test_dep_changed_pkg_data_stays(self): + self._test_dep_changed_pkg_data_stays(dep_matches=True) + + def test_dep_with_pkg_data_dep_not_matched(self): + self._test_dep_changed_pkg_data_stays(dep_matches=False) + + def test_dep_changed_version_stays(self): + self._rule = "from-dep=app-text/poppler to-dep=app-text/poppler-glib" + self._deps = ["=app-text/poppler-10"] + self._expected_deps = ["=app-text/poppler-glib-10"] + self._validate() + + def test_version_dep_not_matched(self): + self._rule = "from-dep=app-text/poppler to-dep=app-text/poppler-glib" + self._deps = ["=app-text/not-poppler-10"] + self._expected_deps = ["=app-text/not-poppler-10"] + self._validate() + + def test_version_and_data_matches(self): + self._rule = "from-dep=app-text/poppler to-dep=app-text/poppler-glib" + self._deps = ["=app-text/poppler-10:3[use]"] + self._expected_deps = ["=app-text/poppler-glib-10:3[use]"] + self._validate() + + def test_version_and_data_not_matches(self): + self._rule = "from-dep=app-text/poppler to-dep=app-text/poppler-glib" + self._deps = ["=app-text/not-poppler-10:3[use]"] + self._expected_deps = ["=app-text/not-poppler-10:3[use]"] + self._validate() + + def test_more_deps_one_changed(self): + self._rule = "from-dep=app-text/poppler to-dep=app-text/poppler-glib" + self._deps = ["d1", "app-text/poppler", "d2"] + self._expected_deps = ["d1", "app-text/poppler-glib", "d2"] + self._validate() + + def test_dep_not_changed_dep_not_matched(self): + self._rule = "from-dep=app-text/poppler-not to-dep=app-text/poppler-glib" + self._deps = ["app-text/poppler"] + self._expected_deps = ["app-text/poppler"] + self._validate() + + def test_more_deps_none_changed_dep_not_matched(self): + self._rule = "from-dep=app-text/poppler-not to-dep=app-text/poppler-glib" + self._deps = ["d1", "app-text/poppler", "d2"] + self._expected_deps = ["d1", "app-text/poppler", "d2"] + self._validate() + + def _test_with_if_has_use(self, dep_use, rule_use, dep_use_matches): + assert dep_use.startswith("[") or not dep_use + if dep_use_matches: + expected_dep_base = "x11-misc/lightdm-qt4" + else: + expected_dep_base = "x11-misc/lightdm" + + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt4 if-dep-has-use=%s" % (rule_use,) + self._deps = ["x11-misc/lightdm"+dep_use] + self._expected_deps = [expected_dep_base+dep_use] + self._validate() + + def test_with_if_has_use_matched(self): + self._test_with_if_has_use(dep_use="[qt4]", rule_use="qt4", dep_use_matches=True) + + def test_with_if_has_use_matched_complex(self): + use_list = ("[qt4,qt5]", "[qt4?,qt5]", "[qt4(-)]", "[qt4(+)]") + for use in use_list: + self._test_with_if_has_use(dep_use=use, rule_use="qt4", dep_use_matches=True) + + def test_with_if_has_use_not_matched_no_use(self): + self._test_with_if_has_use(dep_use="", rule_use="qt4", dep_use_matches=False) + + def test_with_if_has_use_not_matched_different_use(self): + self._test_with_if_has_use(dep_use="[not-qt4]", rule_use="qt4", dep_use_matches=False) + + def test_with_if_has_use_not_matched_different_use_complex(self): + self._test_with_if_has_use(dep_use="[qt4]", rule_use="not-qt4", dep_use_matches=False) + # There is no necessity to handle [-use(-)]. + self._test_with_if_has_use(dep_use="[-qt4]", rule_use="qt4", dep_use_matches=False) + use_list = ("[qt4,qt5]", "[qt4?,qt5]", "[qt4(-)]", "[qt4(+)]") + for use in use_list: + self._test_with_if_has_use(dep_use=use, rule_use="not-qt4", dep_use_matches=False) + + def test_with_if_has_use_and_drop_use(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt4 if-dep-has-use=qt4 drop-use=qt4" + self._deps = ["x11-misc/lightdm[qt4]"] + self._expected_deps = ["x11-misc/lightdm-qt4"] + self._validate() + + def test_with_if_has_use_and_drop_use_more_deps(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt4 if-dep-has-use=qt4 drop-use=qt4" + self._deps = ["x11-misc/lightdm[qt4,something-else]"] + self._expected_deps = ["x11-misc/lightdm-qt4[something-else]"] + self._validate() + + def test_with_if_has_use_and_drop_use_complex(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt4 if-dep-has-use=qt4 drop-use=qt4" + self._deps = ["x11-misc/lightdm[qt4(-),something-else(+)]"] + self._expected_deps = ["x11-misc/lightdm-qt4[something-else(+)]"] + self._validate() + + def test_with_if_has_use_and_drop_use_no_dropped_use_match(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt4 if-dep-has-use=qt4 drop-use=something" + self._deps = ["x11-misc/lightdm[qt4]"] + self._expected_deps = ["x11-misc/lightdm-qt4[qt4]"] + self._validate() + + def test_without_if_has_use_but_with_drop_use(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt5 drop-use=qt4" + self._deps = ["x11-misc/lightdm[qt4]"] + self._expected_deps = ["x11-misc/lightdm-qt5"] + self._validate() + + def test_raises_on_unknown_key(self): + rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt4 haha=true" + deps = ["x11-misc/lightdm"] + + with self.assertRaisesRegexp(et.WrongRewriteRuleError, r"^Bad key 'haha'"): + et.DependencyRewriter(deps, rule) + + def test_raises_on_missing_key(self): + rule_parts = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt4".split() + deps = [] + for rule in rule_parts: + with self.assertRaisesRegexp(et.WrongRewriteRuleError, r"Incomplete rewrite rule"): + et.DependencyRewriter(deps, rule) + + def test_raises_on_key_with_no_assignment(self): + wrong_values = ("if-has-use", "drop-use", "haha") + for value in wrong_values: + rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt4 %s" % (value,) + deps = ["x11-misc/lightdm"] + with self.assertRaisesRegexp(et.WrongRewriteRuleError, + r"^Badly formatted line with '%s'" % (value,)): + et.DependencyRewriter(deps, rule) + + def test_with_empty_deps(self): + self._rule = "from-dep=app-text/poppler to-dep=app-text/poppler-glib" + self._deps = [] + self._expected_deps = [] + self._validate() + + +class DependencyRewriterBasicAttrsTests(unittest.TestCase, MatchedChangedTestsMixin): + def test_matched_and_changed(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt5 drop-use=qt4" + self._deps = ["x11-misc/lightdm[qt4]"] + self._expected_matched = True + self._expected_changed = True + self._validate() + + def test_not_matched_and_not_changed_dep_mismatch(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt5 drop-use=qt4" + self._deps = ["something-else/lightdm[qt4]"] + self._expected_matched = False + self._expected_changed = False + self._validate() + + def test_not_matched_and_not_changed_use_mismatch(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt5 if-dep-has-use=qt4" + self._deps = ["x11-misc/lightdm[not-qt4]"] + self._expected_matched = False + self._expected_changed = False + self._validate() + + def test_matched_and_not_changed(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm if-dep-has-use=qt4" + self._deps = ["x11-misc/lightdm[qt4]"] + self._expected_matched = True + self._expected_changed = False + self._validate() + + def test_with_empty_deps(self): + self._rule = "from-dep=app-text/poppler to-dep=app-text/poppler-glib" + self._deps = [] + self._expected_matched = False + self._expected_changed = False + self._validate() + if __name__ == '__main__': unittest.main() raise SystemExit(0) From cbcbf7495fe86e2e73745495be6de93e6fae8c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awomir=20Nizio?= Date: Sat, 24 Feb 2018 19:52:12 +0100 Subject: [PATCH 4/4] [entropy.core] implement "drop all USE" in the new dep. rewrite --- .../packages.server.dep_rewrite.example | 3 ++- lib/entropy/dep.py | 17 ++++++++---- lib/tests/dep.py | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/conf/packages/packages.server.dep_rewrite.example b/conf/packages/packages.server.dep_rewrite.example index cb5a239a2..d2a6ed435 100644 --- a/conf/packages/packages.server.dep_rewrite.example +++ b/conf/packages/packages.server.dep_rewrite.example @@ -78,7 +78,8 @@ # If present in rule, ::use_to_remove:: will be removed if dependency contains it. # # ::dep_atom:: and ::new_dep_atom:: are category/package names, without version, slot etc. +# ::use_to_remove:: can be an asterisk (*) which means to remove all USE flags. # -# Examples: +# EXAMPLE # rewrite foo/bar from-dep=app-crypt/pinentry to-dep=app-crypt/pinentry-gtk2 if-dep-has-use=gtk drop-use=gtk # will change foo/bar's dependency "app-crypt/pinentry[gtk,qt5]" to "app-crypt/pinentry-gtk2[qt5]". diff --git a/lib/entropy/dep.py b/lib/entropy/dep.py index c88533c20..1f6b5684f 100644 --- a/lib/entropy/dep.py +++ b/lib/entropy/dep.py @@ -1299,11 +1299,18 @@ class DependencyRewriter(object): dep_has_use_func = lambda _: True if 'drop-use' in self._rule_dict: - modify_use_func = lambda dep_postfix, use_flags_list, clean_use_flags_list: \ - DependencyRewriter._use_in_depstring_re.sub( - self._filter_use(use_flags_list, clean_use_flags_list, self._rule_dict['drop-use']), - dep_postfix, - count=1) + if self._rule_dict['drop-use'] == "*": + modify_use_func = lambda dep_postfix, use_flags_list, clean_use_flags_list: \ + DependencyRewriter._use_in_depstring_re.sub( + "", + dep_postfix, + count=1) + else: + modify_use_func = lambda dep_postfix, use_flags_list, clean_use_flags_list: \ + DependencyRewriter._use_in_depstring_re.sub( + self._filter_use(use_flags_list, clean_use_flags_list, self._rule_dict['drop-use']), + dep_postfix, + count=1) else: modify_use_func = lambda dep_postfix, _1, _2: dep_postfix diff --git a/lib/tests/dep.py b/lib/tests/dep.py index ad7027906..f17894bd8 100644 --- a/lib/tests/dep.py +++ b/lib/tests/dep.py @@ -442,6 +442,18 @@ class DependencyRewriterTests(unittest.TestCase, DepsRewriteTestsMixin): self._expected_deps = ["x11-misc/lightdm-qt5"] self._validate() + def test_with_drop_use_asterisk(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt5 drop-use=*" + self._deps = ["x11-misc/lightdm[qt4?,qt5]"] + self._expected_deps = ["x11-misc/lightdm-qt5"] + self._validate() + + def test_with_drop_use_asterisk_and_no_use_in_dep(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt5 drop-use=*" + self._deps = ["x11-misc/lightdm"] + self._expected_deps = ["x11-misc/lightdm-qt5"] + self._validate() + def test_raises_on_unknown_key(self): rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm-qt4 haha=true" deps = ["x11-misc/lightdm"] @@ -508,6 +520,21 @@ class DependencyRewriterBasicAttrsTests(unittest.TestCase, MatchedChangedTestsMi self._expected_changed = False self._validate() + def test_matched_and_changed_with_use_asterisk(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm drop-use=*" + self._deps = ["x11-misc/lightdm[gtk]"] + self._expected_matched = True + self._expected_changed = True + self._validate() + + def test_matched_and_not_changed_with_use_asterisk(self): + self._rule = "from-dep=x11-misc/lightdm to-dep=x11-misc/lightdm drop-use=*" + self._deps = ["x11-misc/lightdm"] + self._expected_matched = True + self._expected_changed = False + self._validate() + + if __name__ == '__main__': unittest.main() raise SystemExit(0)