commit:     839a1f4a9a723e642ac4a1b26154bbb8f49e488a
Author:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
AuthorDate: Thu Jun 29 18:27:16 2023 +0000
Commit:     Arthur Zamarin <arthurzam <AT> gentoo <DOT> org>
CommitDate: Fri Jun 30 18:36:33 2023 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/pkgcheck.git/commit/?id=839a1f4a

RubyCompatCheck: new check for new USE_RUBY compatible values

Resolves: https://github.com/pkgcore/pkgcheck/issues/304
Signed-off-by: Arthur Zamarin <arthurzam <AT> gentoo.org>

 src/pkgcheck/checks/ruby.py                        | 109 +++++++++++++++++++++
 .../RubyCompatCheck/RubyCompatUpdate/expected.json |   2 +
 .../RubyCompatUpdate/RubyCompatUpdate-0.ebuild     |  17 ++++
 .../RubyCompatUpdate/RubyCompatUpdate-1.ebuild     |  17 ++++
 .../RubyCompatUpdate/RubyCompatUpdate-2.ebuild     |  17 ++++
 .../repos/python/dev-lang/ruby/ruby-2.7.ebuild     |   6 ++
 .../repos/python/dev-lang/ruby/ruby-3.0.ebuild     |   6 ++
 .../repos/python/dev-lang/ruby/ruby-3.1.ebuild     |   6 ++
 .../repos/python/dev-lang/ruby/ruby-3.2.ebuild     |   6 ++
 testdata/repos/python/eclass/ruby-ng.eclass        |  83 ++++++++++++++++
 .../repos/python/profiles/desc/ruby_targets.desc   |   5 +
 .../python/stub/ruby-dep-old/ruby-dep-old-0.ebuild |  14 +++
 .../repos/python/stub/ruby-dep/ruby-dep-0.ebuild   |  14 +++
 13 files changed, 302 insertions(+)

diff --git a/src/pkgcheck/checks/ruby.py b/src/pkgcheck/checks/ruby.py
new file mode 100644
index 00000000..d7cbb914
--- /dev/null
+++ b/src/pkgcheck/checks/ruby.py
@@ -0,0 +1,109 @@
+import itertools
+
+from pkgcore.ebuild.atom import atom
+from snakeoil.sequences import iflatten_instance
+from snakeoil.strings import pluralism
+
+from .. import results
+from . import Check
+
+
+IUSE_PREFIX = "ruby_targets_"
+
+
+class RubyCompatUpdate(results.VersionResult, results.Info):
+    """``USE_RUBY`` can be updated to support newer ruby version(s)."""
+
+    def __init__(self, updates, **kwargs):
+        super().__init__(**kwargs)
+        self.updates = tuple(updates)
+
+    @property
+    def desc(self):
+        s = pluralism(self.updates)
+        updates = ", ".join(self.updates)
+        return f"USE_RUBY update{s} available: {updates}"
+
+
+class RubyCompatCheck(Check):
+    """Check ruby ebuilds for possible ``USE_RUBY`` updates.
+
+    Supports ebuilds inheriting ``ruby-ng``.
+    """
+
+    known_results = frozenset({RubyCompatUpdate})
+
+    whitelist_categories = frozenset({"virtual"})
+
+    def __init__(self, *args):
+        super().__init__(*args)
+        repo = self.options.target_repo
+        # sorter for ruby targets leveraging USE_EXPAND flag ordering from repo
+        self.sorter = repo.use_expand_sorter("ruby_targets")
+
+        # determine available USE_RUBY use flags
+        targets = []
+        for target, _desc in repo.use_expand_desc.get(IUSE_PREFIX[:-1], ()):
+            if target[len(IUSE_PREFIX) :].startswith("ruby"):
+                targets.append(target[len(IUSE_PREFIX) :])
+        self.multi_targets = tuple(sorted(targets, key=self.sorter))
+
+    def ruby_deps(self, deps, prefix):
+        for dep in (x for x in deps if x.use):
+            for x in dep.use:
+                if x.startswith(("-", "!")):
+                    continue
+                if x.startswith(prefix):
+                    yield dep.no_usedeps
+                    break
+
+    def deps(self, pkg):
+        """Set of dependencies for a given package's attributes."""
+        return {
+            p
+            for attr in (x.lower() for x in pkg.eapi.dep_keys)
+            for p in iflatten_instance(getattr(pkg, attr), atom)
+            if not p.blocks
+        }
+
+    def feed(self, pkg):
+        if pkg.category in self.whitelist_categories or "ruby-ng" not in 
pkg.inherited:
+            return
+
+        deps = self.deps(pkg)
+
+        try:
+            # determine the latest supported ruby version
+            latest_target = sorted(
+                (
+                    f"ruby{x.slot.replace('.', '')}"
+                    for x in deps
+                    if x.key == "dev-lang/ruby" and x.slot is not None
+                ),
+                key=self.sorter,
+            )[-1]
+        except IndexError:
+            return
+
+        # determine ruby impls to target
+        targets = set(
+            itertools.takewhile(lambda x: x != latest_target, 
reversed(self.multi_targets))
+        )
+
+        if targets:
+            try:
+                # determine if deps support missing ruby targets
+                for dep in self.ruby_deps(deps, IUSE_PREFIX):
+                    # TODO: use query caching for repo matching?
+                    latest = sorted(self.options.search_repo.match(dep))[-1]
+                    targets.intersection_update(
+                        f"ruby{x.rsplit('ruby', 1)[-1]}"
+                        for x in latest.iuse_stripped
+                        if x.startswith(IUSE_PREFIX)
+                    )
+                    if not targets:
+                        return
+            except IndexError:
+                return
+
+            yield RubyCompatUpdate(sorted(targets, key=self.sorter), pkg=pkg)

diff --git 
a/testdata/data/repos/python/RubyCompatCheck/RubyCompatUpdate/expected.json 
b/testdata/data/repos/python/RubyCompatCheck/RubyCompatUpdate/expected.json
new file mode 100644
index 00000000..b5253f79
--- /dev/null
+++ b/testdata/data/repos/python/RubyCompatCheck/RubyCompatUpdate/expected.json
@@ -0,0 +1,2 @@
+{"__class__": "RubyCompatUpdate", "category": "RubyCompatCheck", "package": 
"RubyCompatUpdate", "version": "0", "updates": ["ruby31", "ruby32"]}
+{"__class__": "RubyCompatUpdate", "category": "RubyCompatCheck", "package": 
"RubyCompatUpdate", "version": "1", "updates": ["ruby32"]}

diff --git 
a/testdata/repos/python/RubyCompatCheck/RubyCompatUpdate/RubyCompatUpdate-0.ebuild
 
b/testdata/repos/python/RubyCompatCheck/RubyCompatUpdate/RubyCompatUpdate-0.ebuild
new file mode 100644
index 00000000..8f6f1da4
--- /dev/null
+++ 
b/testdata/repos/python/RubyCompatCheck/RubyCompatUpdate/RubyCompatUpdate-0.ebuild
@@ -0,0 +1,17 @@
+EAPI=7
+
+USE_RUBY="ruby27 ruby30"
+inherit ruby-ng
+
+DESCRIPTION="Ebuild with potential USE_RUBY updates"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+LICENSE="BSD"
+SLOT="0"
+
+RDEPEND="
+       stub/stub2
+"
+
+ruby_add_rdepend "
+       stub/ruby-dep
+"

diff --git 
a/testdata/repos/python/RubyCompatCheck/RubyCompatUpdate/RubyCompatUpdate-1.ebuild
 
b/testdata/repos/python/RubyCompatCheck/RubyCompatUpdate/RubyCompatUpdate-1.ebuild
new file mode 100644
index 00000000..8af80e69
--- /dev/null
+++ 
b/testdata/repos/python/RubyCompatCheck/RubyCompatUpdate/RubyCompatUpdate-1.ebuild
@@ -0,0 +1,17 @@
+EAPI=7
+
+USE_RUBY="ruby27 ruby30 ruby31"
+inherit ruby-ng
+
+DESCRIPTION="Ebuild with potential USE_RUBY updates"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+LICENSE="BSD"
+SLOT="0"
+
+RDEPEND="
+       stub/stub2
+"
+
+ruby_add_depend "
+       stub/ruby-dep
+"

diff --git 
a/testdata/repos/python/RubyCompatCheck/RubyCompatUpdate/RubyCompatUpdate-2.ebuild
 
b/testdata/repos/python/RubyCompatCheck/RubyCompatUpdate/RubyCompatUpdate-2.ebuild
new file mode 100644
index 00000000..bb2e95d3
--- /dev/null
+++ 
b/testdata/repos/python/RubyCompatCheck/RubyCompatUpdate/RubyCompatUpdate-2.ebuild
@@ -0,0 +1,17 @@
+EAPI=7
+
+USE_RUBY="ruby27 ruby30"
+inherit ruby-ng
+
+DESCRIPTION="Ebuild without potential USE_RUBY updates"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+LICENSE="BSD"
+SLOT="0"
+
+RDEPEND="
+       stub/stub2
+"
+
+ruby_add_rdepend "
+       stub/ruby-dep-old
+"

diff --git a/testdata/repos/python/dev-lang/ruby/ruby-2.7.ebuild 
b/testdata/repos/python/dev-lang/ruby/ruby-2.7.ebuild
new file mode 100644
index 00000000..6192e625
--- /dev/null
+++ b/testdata/repos/python/dev-lang/ruby/ruby-2.7.ebuild
@@ -0,0 +1,6 @@
+EAPI=7
+
+DESCRIPTION="Stub ebuild for ruby interpreter"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+LICENSE="BSD"
+SLOT="2.7"

diff --git a/testdata/repos/python/dev-lang/ruby/ruby-3.0.ebuild 
b/testdata/repos/python/dev-lang/ruby/ruby-3.0.ebuild
new file mode 100644
index 00000000..452b04e7
--- /dev/null
+++ b/testdata/repos/python/dev-lang/ruby/ruby-3.0.ebuild
@@ -0,0 +1,6 @@
+EAPI=7
+
+DESCRIPTION="Stub ebuild for ruby interpreter"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+LICENSE="BSD"
+SLOT="3.0"

diff --git a/testdata/repos/python/dev-lang/ruby/ruby-3.1.ebuild 
b/testdata/repos/python/dev-lang/ruby/ruby-3.1.ebuild
new file mode 100644
index 00000000..570b1709
--- /dev/null
+++ b/testdata/repos/python/dev-lang/ruby/ruby-3.1.ebuild
@@ -0,0 +1,6 @@
+EAPI=7
+
+DESCRIPTION="Stub ebuild for ruby interpreter"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+LICENSE="BSD"
+SLOT="3.1"

diff --git a/testdata/repos/python/dev-lang/ruby/ruby-3.2.ebuild 
b/testdata/repos/python/dev-lang/ruby/ruby-3.2.ebuild
new file mode 100644
index 00000000..9bbb0f65
--- /dev/null
+++ b/testdata/repos/python/dev-lang/ruby/ruby-3.2.ebuild
@@ -0,0 +1,6 @@
+EAPI=7
+
+DESCRIPTION="Stub ebuild for ruby interpreter"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+LICENSE="BSD"
+SLOT="3.2"

diff --git a/testdata/repos/python/eclass/ruby-ng.eclass 
b/testdata/repos/python/eclass/ruby-ng.eclass
new file mode 100644
index 00000000..082c98f0
--- /dev/null
+++ b/testdata/repos/python/eclass/ruby-ng.eclass
@@ -0,0 +1,83 @@
+_ruby_implementation_depend() {
+       local rubypn= rubyslot=
+
+       case $1 in
+               ruby27) rubypn="dev-lang/ruby" rubyslot=":2.7" ;;
+               ruby30) rubypn="dev-lang/ruby" rubyslot=":3.0" ;;
+               ruby31) rubypn="dev-lang/ruby" rubyslot=":3.1" ;;
+               ruby32) rubypn="dev-lang/ruby" rubyslot=":3.2" ;;
+               *) die "$1: unknown Ruby implementation"
+       esac
+
+       echo "$2${rubypn}$3${rubyslot}"
+}
+
+_ruby_implementations_depend() {
+       local depend _ruby_implementation
+       for _ruby_implementation in "${_RUBY_GET_ALL_IMPLS[@]}"; do
+               depend="${depend}${depend+ 
}ruby_targets_${_ruby_implementation}? ( $(_ruby_implementation_depend 
$_ruby_implementation) )"
+       done
+       DEPEND="${depend}"
+       IUSE="${_RUBY_GET_ALL_IMPLS[*]/#/ruby_targets_}"
+       REQUIRED_USE="|| ( ${IUSE} )"
+}
+
+_ruby_atoms_samelib_generic() {
+       local shopt_save=$(shopt -p -o noglob)
+       echo "RUBYTARGET? ("
+       for token in $*; do
+               case "$token" in
+                       "||" | "(" | ")" | *"?")
+                               echo "${token}" ;;
+                       *])
+                               echo "${token%[*}[RUBYTARGET(-),${token/*[}" ;;
+                       *)
+                               echo "${token}[RUBYTARGET(-)]" ;;
+               esac
+       done
+       echo ")"
+       ${shopt_save}
+}
+
+_ruby_atoms_samelib() {
+       local atoms=$(_ruby_atoms_samelib_generic "$*")
+    for _ruby_implementation in "${_RUBY_GET_ALL_IMPLS[@]}"; do
+        echo "${atoms//RUBYTARGET/ruby_targets_${_ruby_implementation}}"
+    done
+}
+
+_ruby_get_all_impls() {
+       _RUBY_GET_ALL_IMPLS=()
+
+       local i found_valid_impl
+       for i in ${USE_RUBY}; do
+               case ${i} in
+                       # removed implementations
+                       ruby19|ruby2[0-7]|jruby)
+                               ;;
+                       *)
+                               found_valid_impl=1
+                               _RUBY_GET_ALL_IMPLS+=( ${i} )
+                               ;;
+               esac
+       done
+
+       if [[ -z ${found_valid_impl} ]] ; then
+               die "No supported implementation in USE_RUBY."
+       fi
+}
+
+ruby_add_depend() {
+       DEPEND+=" $(_ruby_atoms_samelib "$1")"
+}
+
+ruby_add_bdepend() {
+       BDEPEND+=" $(_ruby_atoms_samelib "$1")"
+}
+
+ruby_add_rdepend() {
+       RDEPEND+=" $(_ruby_atoms_samelib "$1")"
+}
+
+_ruby_get_all_impls
+_ruby_implementations_depend

diff --git a/testdata/repos/python/profiles/desc/ruby_targets.desc 
b/testdata/repos/python/profiles/desc/ruby_targets.desc
new file mode 100644
index 00000000..59c3dec7
--- /dev/null
+++ b/testdata/repos/python/profiles/desc/ruby_targets.desc
@@ -0,0 +1,5 @@
+# available RUBY_TARGETS USE_EXPAND flags
+ruby27 - Build with MRI Ruby 2.7.x
+ruby30 - Build with MRI Ruby 3.0.x
+ruby31 - Build with MRI Ruby 3.1.x
+ruby32 - Build with MRI Ruby 3.2.x

diff --git a/testdata/repos/python/stub/ruby-dep-old/ruby-dep-old-0.ebuild 
b/testdata/repos/python/stub/ruby-dep-old/ruby-dep-old-0.ebuild
new file mode 100644
index 00000000..ec9071c3
--- /dev/null
+++ b/testdata/repos/python/stub/ruby-dep-old/ruby-dep-old-0.ebuild
@@ -0,0 +1,14 @@
+EAPI=7
+
+USE_RUBY="ruby27 ruby30"
+
+inherit ruby-ng
+
+DESCRIPTION="Stub ebuild with old USE_RUBY support"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+LICENSE="BSD"
+SLOT="0"
+
+RDEPEND="
+       stub/stub2
+"

diff --git a/testdata/repos/python/stub/ruby-dep/ruby-dep-0.ebuild 
b/testdata/repos/python/stub/ruby-dep/ruby-dep-0.ebuild
new file mode 100644
index 00000000..abc2efee
--- /dev/null
+++ b/testdata/repos/python/stub/ruby-dep/ruby-dep-0.ebuild
@@ -0,0 +1,14 @@
+EAPI=7
+
+USE_RUBY="ruby27 ruby30 ruby31 ruby32"
+
+inherit ruby-ng
+
+DESCRIPTION="Stub ebuild with complete USE_RUBY support"
+HOMEPAGE="https://github.com/pkgcore/pkgcheck";
+LICENSE="BSD"
+SLOT="0"
+
+RDEPEND="
+       stub/stub1
+"

Reply via email to