[PATCH gentoolkit] eclean: Add --changed-iuse flag

Matt Turner <[email protected]> Sat, 2 Jan 2021 19:08:40 -0500
Newsgroups gmane.linux.gentoo.portage.devel
Message-ID <[email protected]>
Allows binpkgs to be deleted if they are not usable due to IUSE changes.
---
Just kind of spitballing. I'm not sure about what USE flags we should
ignore or whether it should be configurable, etc. On one hand, deleting
binpkgs that don't have a newly added PYTHON_TARGET option might make
sense if your binhost is configured to rebuild the package. On the
other, you probably don't want to throw out amd64 binpkgs because
abi_riscv_* was added.

 pym/gentoolkit/eclean/cli.py    |  8 ++++-
 pym/gentoolkit/eclean/search.py | 62 +++++++++++++++++++++++++--------
 2 files changed, 54 insertions(+), 16 deletions(-)

diff --git a/pym/gentoolkit/eclean/cli.py b/pym/gentoolkit/eclean/cli.py
index e31fde9..910296b 100644
--- a/pym/gentoolkit/eclean/cli.py
+++ b/pym/gentoolkit/eclean/cli.py
@@ -146,6 +146,8 @@ def printUsage(_error=None, help=None):
 				green("packages"),"action:", file=out)
 		print( yellow("     --changed-deps")+
 			"               - delete packages for which ebuild dependencies have changed", file=out)
+		print( yellow("     --changed-iuse")+
+			"               - delete packages for which IUSE have changed", file=out)
 		print( yellow(" -i, --ignore-failure")+
 			"             - ignore failure to locate PKGDIR", file=out)
 		print( file=out)
@@ -264,6 +266,8 @@ def parseArgs(options={}):
 					options['verbose'] = True
 			elif o in ("--changed-deps"):
 				options['changed-deps'] = True
+			elif o in ("--changed-iuse"):
+				options['changed-iuse'] = True
 			elif o in ("-i", "--ignore-failure"):
 				options['ignore-failure'] = True
 			else:
@@ -291,7 +295,8 @@ def parseArgs(options={}):
 	getopt_options['short']['distfiles'] = "fs:"
 	getopt_options['long']['distfiles'] = ["fetch-restricted", "size-limit="]
 	getopt_options['short']['packages'] = "i"
-	getopt_options['long']['packages'] = ["ignore-failure", "changed-deps"]
+	getopt_options['long']['packages'] = ["ignore-failure", "changed-deps",
+					      "changed-iuse"]
 	# set default options, except 'nocolor', which is set in main()
 	options['interactive'] = False
 	options['pretend'] = False
@@ -305,6 +310,7 @@ def parseArgs(options={}):
 	options['size-limit'] = 0
 	options['verbose'] = False
 	options['changed-deps'] = False
+	options['changed-iuse'] = False
 	options['ignore-failure'] = False
 	# if called by a well-named symlink, set the action accordingly:
 	action = None
diff --git a/pym/gentoolkit/eclean/search.py b/pym/gentoolkit/eclean/search.py
index 8f6e52f..8344e15 100644
--- a/pym/gentoolkit/eclean/search.py
+++ b/pym/gentoolkit/eclean/search.py
@@ -498,6 +498,47 @@ def _deps_equal(deps_a, eapi_a, deps_b, eapi_b, uselist=None):
 	return deps_a == deps_b
 
 
+def _changed_deps(cpv, options, port_dbapi, bin_dbapi):
+	if not options['changed-deps']:
+		return False
+
+	dep_keys = ('RDEPEND', 'PDEPEND')
+	keys = ('EAPI', 'USE') + dep_keys
+	binpkg_metadata = dict(zip(keys, bin_dbapi.aux_get(cpv, keys)))
+	ebuild_metadata = dict(zip(keys, port_dbapi.aux_get(cpv, keys)))
+
+	return _deps_equal(' '.join(binpkg_metadata[key] for key in dep_keys),
+					   binpkg_metadata['EAPI'], ' '.join(ebuild_metadata[key] for key in dep_keys),
+					   ebuild_metadata['EAPI'],
+					   frozenset(binpkg_metadata['USE'].split()))
+
+
+# IUSE flags to be ignored for purposes of invalidating binpkgs
+def ignore(iuse):
+	return iuse.startswith('abi_')
+
+
+# Prune the + characters from USE flags (e.g. '+cxx' -> 'cxx')
+def prune_plus(iuse):
+	if iuse.startswith('+'):
+		return iuse[1:]
+	return iuse
+
+
+def _changed_iuse(cpv, options, port_dbapi, bin_dbapi):
+	if not options['changed-iuse']:
+		return False
+
+	keys = ('IUSE',)
+	binpkg_metadata = [prune_plus(x) for x in  bin_dbapi.aux_get(cpv, keys)[0].split(' ') if not ignore(x)]
+	ebuild_metadata = [prune_plus(x) for x in port_dbapi.aux_get(cpv, keys)[0].split(' ') if not ignore(x)]
+
+	if binpkg_metadata == ['']: binpkg_metadata = []
+	if ebuild_metadata == ['']: ebuild_metadata = []
+
+	return binpkg_metadata != ebuild_metadata
+
+
 def findPackages(
 		options,
 		exclude=None,
@@ -570,21 +611,6 @@ def findPackages(
 			if mtime >= time_limit:
 				continue
 
-		# Exclude if binpkg exists in the porttree and not --deep
-		if not destructive and port_dbapi.cpv_exists(cpv):
-			if not options['changed-deps']:
-				continue
-
-			dep_keys = ('RDEPEND', 'PDEPEND')
-			keys = ('EAPI', 'USE') + dep_keys
-			binpkg_metadata = dict(zip(keys, bin_dbapi.aux_get(cpv, keys)))
-			ebuild_metadata = dict(zip(keys, port_dbapi.aux_get(cpv, keys)))
-
-			if _deps_equal(' '.join(binpkg_metadata[key] for key in dep_keys), binpkg_metadata['EAPI'],
-				' '.join(ebuild_metadata[key] for key in dep_keys), ebuild_metadata['EAPI'],
-				frozenset(binpkg_metadata['USE'].split())):
-				continue
-
 		if destructive and var_dbapi.cpv_exists(cpv):
 			# Exclude if an instance of the package is installed due to
 			# the --package-names option.
@@ -596,6 +622,12 @@ def findPackages(
 			if buildtime == bin_dbapi.aux_get(cpv, ['BUILD_TIME'])[0]:
 				continue
 
+		# Exclude if binpkg exists in the porttree and not --deep
+		if not destructive and port_dbapi.cpv_exists(cpv):
+			if not _changed_deps(cpv, options, port_dbapi, bin_dbapi) and \
+			   not _changed_iuse(cpv, options, port_dbapi, bin_dbapi):
+				continue
+
 		binpkg_path = bin_dbapi.bintree.getname(cpv)
 		dead_binpkgs.setdefault(cpv, []).append(binpkg_path)
 
-- 
2.26.2