From 5cf564d3b5109c2a9e4b5917eb7d834f8b911d3b Mon Sep 17 00:00:00 2001 From: V3n3RiX Date: Sat, 20 Jan 2024 22:52:36 +0000 Subject: don't silently fail while attempting to uninstall a package with reverse dependencies --- src/backend/__init__.py | 3 +- src/backend/search.py | 185 --------------------------------------- src/backend/searchdb.py | 185 +++++++++++++++++++++++++++++++++++++++ src/backend/solverdeps.py | 37 ++++++++ src/backend/uninstall.py | 71 +++++++++++---- src/frontend/cli/sisyphus-cli.py | 4 +- 6 files changed, 279 insertions(+), 206 deletions(-) delete mode 100644 src/backend/search.py create mode 100644 src/backend/searchdb.py create mode 100644 src/backend/solverdeps.py diff --git a/src/backend/__init__.py b/src/backend/__init__.py index 9c38585..d6dc4c7 100644 --- a/src/backend/__init__.py +++ b/src/backend/__init__.py @@ -10,7 +10,8 @@ from .mirrors import * from .purgeenv import * from .recoverdb import * from .solvedeps import * -from .search import * +from .solverdeps import * +from .searchdb import * from .setbranch import * from .setjobs import * from .setprofile import * diff --git a/src/backend/search.py b/src/backend/search.py deleted file mode 100644 index 22bd4ba..0000000 --- a/src/backend/search.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/python3 - -import signal -import sqlite3 -import subprocess -import sisyphus.checkenv -import sisyphus.getcolor -import sisyphus.getfs -import sisyphus.update - - -def sigint_handler(signal, frame): - sys.exit(0) - - -signal.signal(signal.SIGINT, sigint_handler) - - -def srch_db(filter, cat='', pn='', desc=''): - NOVIRT = "AND cat NOT LIKE 'virtual'" - SELECTS = { - 'all': f'''SELECT - i.category AS cat, - i.name as pn, - i.version as iv, - IFNULL(a.version, 'alien') AS av, - d.description AS desc - FROM local_packages AS i LEFT OUTER JOIN remote_packages as a - ON i.category = a.category - AND i.name = a.name - AND i.slot = a.slot - LEFT JOIN remote_descriptions AS d ON i.name = d.name AND i.category = d.category - WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT} - UNION - SELECT - a.category AS cat, - a.name as pn, - IFNULL(i.version, 'None') AS iv, - a.version as av, - d.description AS desc - FROM remote_packages AS a LEFT OUTER JOIN local_packages AS i - ON a.category = i.category - AND a.name = i.name - AND a.slot = i.slot - LEFT JOIN remote_descriptions AS d ON a.name = d.name AND a.category = d.category - WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT}''', - 'installed': f'''SELECT - i.category AS cat, - i.name AS pn, - i.version AS iv, - a.version as av, - d.description AS desc - FROM local_packages AS i - LEFT JOIN remote_packages AS a - ON i.category = a.category - AND i.name = a.name - AND i.slot = a.slot - LEFT JOIN remote_descriptions AS d ON i.name = d.name AND i.category = d.category - WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT}''', - 'alien': f'''SELECT - i.category AS cat, - i.name AS pn, - i.version as iv, - IFNULL(a.version, 'alien') AS av, - d.description AS desc - FROM local_packages AS i - LEFT JOIN remote_packages AS a - ON a.category = i.category - AND a.name = i.name - AND a.slot = i.slot - LEFT JOIN remote_descriptions AS d ON i.name = d.name AND i.category = d.category - WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT} - AND av IS 'alien' ''', - 'available': f'''SELECT - a.category AS cat, - a.name AS pn, - i.version as iv, - a.version AS av, - d.description AS desc - FROM remote_packages AS a - LEFT JOIN local_packages AS i - ON a.category = i.category - AND a.name = i.name - AND a.slot = i.slot - LEFT JOIN remote_descriptions AS d ON a.name = d.name AND a.category = d.category - WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT} - AND iv IS NULL''', - 'upgradable': f'''SELECT - i.category AS cat, - i.name AS pn, - i.version as iv, - a.version AS av, - d.description AS desc - FROM local_packages AS i - INNER JOIN remote_packages AS a - ON i.category = a.category - AND i.name = a.name - AND i.slot = a.slot - LEFT JOIN remote_descriptions AS d ON i.name = d.name AND i.category = d.category - WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT} - AND iv <> av''' - } - - with sqlite3.connect(sisyphus.getfs.lcl_db) as db: - db.row_factory = sqlite3.Row - cursor = db.cursor() - cursor.execute(SELECTS[filter]) - rows = cursor.fetchall() - - return rows - - -def tosql(string): - return '%%' if string == '' else string.replace('*', '%').replace('?', '_') - - -def srch_rslt(filter, cat, pn, desc, single): - print("\nSearching" + sisyphus.getcolor.bright_yellow + " " + - f"{filter}" + " " + sisyphus.getcolor.reset + "packages ..." + "\n") - pkglist = srch_db(filter, tosql(cat), tosql(pn), tosql(desc)) - - if len(pkglist) == 0: - print(sisyphus.getcolor.bright_red + - "No binary package found!\n" + sisyphus.getcolor.reset) - print(sisyphus.getcolor.bright_yellow + "Use the" + sisyphus.getcolor.reset + " " + "'" + "--ebuild" + - "'" + " " + sisyphus.getcolor.bright_yellow + "option to search source packages" + sisyphus.getcolor.reset) - print(sisyphus.getcolor.bright_yellow + "Use" + sisyphus.getcolor.reset + " " + "'" + - "sisyphus search --help" + "'" + " " + sisyphus.getcolor.bright_yellow + "for help" + sisyphus.getcolor.reset) - else: - if single: - print(sisyphus.getcolor.green + - f"{'Package category/name':45} {'Installed version':20} {'Latest available version':30} {'Description'}" + sisyphus.getcolor.reset) - for pkg in pkglist: - if not single: - print(sisyphus.getcolor.bright_green + "*" + " " + sisyphus.getcolor.reset + - sisyphus.getcolor.bright_white + f"{pkg['cat']}/{pkg['pn']}" + sisyphus.getcolor.reset) - print(sisyphus.getcolor.green + "\tInstalled version:" + - " " + sisyphus.getcolor.reset + f"{pkg['iv']}") - if pkg['av'] != 'alien': - print(sisyphus.getcolor.green + "\tLatest available version:" + - " " + sisyphus.getcolor.reset + f"{pkg['av']}") - else: - print(sisyphus.getcolor.green + "\tAlien package:" + " " + sisyphus.getcolor.reset + - "Use `sisyphus search --ebuild" + " " + f"{pkg['pn']}`" + " " + "for available version!") - print(sisyphus.getcolor.green + "\tDescription:" + " " + - sisyphus.getcolor.reset + f"{pkg['desc']}" + "\n") - else: - cpn = f"{pkg['cat']}/{pkg['pn']}" - print(sisyphus.getcolor.bright_white + f"{cpn:45}" + " " + sisyphus.getcolor.reset + - f"{str(pkg['iv']):20}" + " " + f"{str(pkg['av']):30}" + " " + f"{str(pkg['desc'])}") - print("\nFound" + " " + sisyphus.getcolor.bright_yellow + - f"{len(pkglist)}" + " " + sisyphus.getcolor.reset + "matching package(s) ...") - - -def start(filter, cat, pn, desc, single): - if sisyphus.checkenv.root(): - print(sisyphus.getcolor.bright_red + - "\nSearching as root, database can be updated!\n" + sisyphus.getcolor.reset) - print(sisyphus.getcolor.bright_yellow + - "Search results will be accurate\n" + sisyphus.getcolor.reset) - while True: - user_input = input(sisyphus.getcolor.bright_white + "Would you like to proceed?" + sisyphus.getcolor.reset + " " + - "[" + sisyphus.getcolor.bright_green + "Yes" + sisyphus.getcolor.reset + "/" + sisyphus.getcolor.bright_red + "No" + sisyphus.getcolor.reset + "]" + " ") - if user_input.lower() in ['yes', 'y', '']: - sisyphus.update.start(gfx_ui=False) - break - elif user_input.lower() in ['no', 'n']: - print(sisyphus.getcolor.bright_red + - "\nSkipping database update, displaying search results!\n" + sisyphus.getcolor.reset) - print(sisyphus.getcolor.bright_yellow + - "Search results may be inaccurate" + sisyphus.getcolor.reset) - break - else: - continue - else: - print(sisyphus.getcolor.bright_red + - "\nSearching as user, database cannot be updated!\n" + sisyphus.getcolor.reset) - print(sisyphus.getcolor.bright_yellow + - "Search results may be inaccurate" + sisyphus.getcolor.reset) - - srch_rslt(filter, cat, pn, desc, single) - - -def estart(pkgname): - subprocess.call(['emerge', '--search', '--getbinpkg'] + list(pkgname)) diff --git a/src/backend/searchdb.py b/src/backend/searchdb.py new file mode 100644 index 0000000..22bd4ba --- /dev/null +++ b/src/backend/searchdb.py @@ -0,0 +1,185 @@ +#!/usr/bin/python3 + +import signal +import sqlite3 +import subprocess +import sisyphus.checkenv +import sisyphus.getcolor +import sisyphus.getfs +import sisyphus.update + + +def sigint_handler(signal, frame): + sys.exit(0) + + +signal.signal(signal.SIGINT, sigint_handler) + + +def srch_db(filter, cat='', pn='', desc=''): + NOVIRT = "AND cat NOT LIKE 'virtual'" + SELECTS = { + 'all': f'''SELECT + i.category AS cat, + i.name as pn, + i.version as iv, + IFNULL(a.version, 'alien') AS av, + d.description AS desc + FROM local_packages AS i LEFT OUTER JOIN remote_packages as a + ON i.category = a.category + AND i.name = a.name + AND i.slot = a.slot + LEFT JOIN remote_descriptions AS d ON i.name = d.name AND i.category = d.category + WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT} + UNION + SELECT + a.category AS cat, + a.name as pn, + IFNULL(i.version, 'None') AS iv, + a.version as av, + d.description AS desc + FROM remote_packages AS a LEFT OUTER JOIN local_packages AS i + ON a.category = i.category + AND a.name = i.name + AND a.slot = i.slot + LEFT JOIN remote_descriptions AS d ON a.name = d.name AND a.category = d.category + WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT}''', + 'installed': f'''SELECT + i.category AS cat, + i.name AS pn, + i.version AS iv, + a.version as av, + d.description AS desc + FROM local_packages AS i + LEFT JOIN remote_packages AS a + ON i.category = a.category + AND i.name = a.name + AND i.slot = a.slot + LEFT JOIN remote_descriptions AS d ON i.name = d.name AND i.category = d.category + WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT}''', + 'alien': f'''SELECT + i.category AS cat, + i.name AS pn, + i.version as iv, + IFNULL(a.version, 'alien') AS av, + d.description AS desc + FROM local_packages AS i + LEFT JOIN remote_packages AS a + ON a.category = i.category + AND a.name = i.name + AND a.slot = i.slot + LEFT JOIN remote_descriptions AS d ON i.name = d.name AND i.category = d.category + WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT} + AND av IS 'alien' ''', + 'available': f'''SELECT + a.category AS cat, + a.name AS pn, + i.version as iv, + a.version AS av, + d.description AS desc + FROM remote_packages AS a + LEFT JOIN local_packages AS i + ON a.category = i.category + AND a.name = i.name + AND a.slot = i.slot + LEFT JOIN remote_descriptions AS d ON a.name = d.name AND a.category = d.category + WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT} + AND iv IS NULL''', + 'upgradable': f'''SELECT + i.category AS cat, + i.name AS pn, + i.version as iv, + a.version AS av, + d.description AS desc + FROM local_packages AS i + INNER JOIN remote_packages AS a + ON i.category = a.category + AND i.name = a.name + AND i.slot = a.slot + LEFT JOIN remote_descriptions AS d ON i.name = d.name AND i.category = d.category + WHERE cat LIKE '%{cat}%' AND pn LIKE '%{pn}%' AND desc LIKE '%{desc}%' {NOVIRT} + AND iv <> av''' + } + + with sqlite3.connect(sisyphus.getfs.lcl_db) as db: + db.row_factory = sqlite3.Row + cursor = db.cursor() + cursor.execute(SELECTS[filter]) + rows = cursor.fetchall() + + return rows + + +def tosql(string): + return '%%' if string == '' else string.replace('*', '%').replace('?', '_') + + +def srch_rslt(filter, cat, pn, desc, single): + print("\nSearching" + sisyphus.getcolor.bright_yellow + " " + + f"{filter}" + " " + sisyphus.getcolor.reset + "packages ..." + "\n") + pkglist = srch_db(filter, tosql(cat), tosql(pn), tosql(desc)) + + if len(pkglist) == 0: + print(sisyphus.getcolor.bright_red + + "No binary package found!\n" + sisyphus.getcolor.reset) + print(sisyphus.getcolor.bright_yellow + "Use the" + sisyphus.getcolor.reset + " " + "'" + "--ebuild" + + "'" + " " + sisyphus.getcolor.bright_yellow + "option to search source packages" + sisyphus.getcolor.reset) + print(sisyphus.getcolor.bright_yellow + "Use" + sisyphus.getcolor.reset + " " + "'" + + "sisyphus search --help" + "'" + " " + sisyphus.getcolor.bright_yellow + "for help" + sisyphus.getcolor.reset) + else: + if single: + print(sisyphus.getcolor.green + + f"{'Package category/name':45} {'Installed version':20} {'Latest available version':30} {'Description'}" + sisyphus.getcolor.reset) + for pkg in pkglist: + if not single: + print(sisyphus.getcolor.bright_green + "*" + " " + sisyphus.getcolor.reset + + sisyphus.getcolor.bright_white + f"{pkg['cat']}/{pkg['pn']}" + sisyphus.getcolor.reset) + print(sisyphus.getcolor.green + "\tInstalled version:" + + " " + sisyphus.getcolor.reset + f"{pkg['iv']}") + if pkg['av'] != 'alien': + print(sisyphus.getcolor.green + "\tLatest available version:" + + " " + sisyphus.getcolor.reset + f"{pkg['av']}") + else: + print(sisyphus.getcolor.green + "\tAlien package:" + " " + sisyphus.getcolor.reset + + "Use `sisyphus search --ebuild" + " " + f"{pkg['pn']}`" + " " + "for available version!") + print(sisyphus.getcolor.green + "\tDescription:" + " " + + sisyphus.getcolor.reset + f"{pkg['desc']}" + "\n") + else: + cpn = f"{pkg['cat']}/{pkg['pn']}" + print(sisyphus.getcolor.bright_white + f"{cpn:45}" + " " + sisyphus.getcolor.reset + + f"{str(pkg['iv']):20}" + " " + f"{str(pkg['av']):30}" + " " + f"{str(pkg['desc'])}") + print("\nFound" + " " + sisyphus.getcolor.bright_yellow + + f"{len(pkglist)}" + " " + sisyphus.getcolor.reset + "matching package(s) ...") + + +def start(filter, cat, pn, desc, single): + if sisyphus.checkenv.root(): + print(sisyphus.getcolor.bright_red + + "\nSearching as root, database can be updated!\n" + sisyphus.getcolor.reset) + print(sisyphus.getcolor.bright_yellow + + "Search results will be accurate\n" + sisyphus.getcolor.reset) + while True: + user_input = input(sisyphus.getcolor.bright_white + "Would you like to proceed?" + sisyphus.getcolor.reset + " " + + "[" + sisyphus.getcolor.bright_green + "Yes" + sisyphus.getcolor.reset + "/" + sisyphus.getcolor.bright_red + "No" + sisyphus.getcolor.reset + "]" + " ") + if user_input.lower() in ['yes', 'y', '']: + sisyphus.update.start(gfx_ui=False) + break + elif user_input.lower() in ['no', 'n']: + print(sisyphus.getcolor.bright_red + + "\nSkipping database update, displaying search results!\n" + sisyphus.getcolor.reset) + print(sisyphus.getcolor.bright_yellow + + "Search results may be inaccurate" + sisyphus.getcolor.reset) + break + else: + continue + else: + print(sisyphus.getcolor.bright_red + + "\nSearching as user, database cannot be updated!\n" + sisyphus.getcolor.reset) + print(sisyphus.getcolor.bright_yellow + + "Search results may be inaccurate" + sisyphus.getcolor.reset) + + srch_rslt(filter, cat, pn, desc, single) + + +def estart(pkgname): + subprocess.call(['emerge', '--search', '--getbinpkg'] + list(pkgname)) diff --git a/src/backend/solverdeps.py b/src/backend/solverdeps.py new file mode 100644 index 0000000..b866cc8 --- /dev/null +++ b/src/backend/solverdeps.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 + +import animation +import signal +import subprocess +import sys + + +def sigint_handler(signal, frame): + sys.exit(0) + + +signal.signal(signal.SIGINT, sigint_handler) + + +@animation.wait('resolving reverse dependencies') +def start(pkgname=None): + is_needed = int(0) + + p_exe = subprocess.Popen( + ['emerge', '--depclean', '--quiet', '--pretend', '--verbose'] + list(pkgname), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + stdout, stderr = p_exe.communicate() + + for p_out in stdout.decode('utf-8').splitlines(): + if any(key in p_out for key in ["pulled in by:", "required"]): + is_needed = int(1) + + except KeyboardInterrupt: + p_exe.terminate() + try: + p_exe.wait(1) + except subprocess.TimeoutExpired: + p_exe.kill() + sys.exit() + return is_needed diff --git a/src/backend/uninstall.py b/src/backend/uninstall.py index 70150f9..ede3947 100644 --- a/src/backend/uninstall.py +++ b/src/backend/uninstall.py @@ -8,6 +8,7 @@ import sys import sisyphus.checkenv import sisyphus.getcolor import sisyphus.killemerge +import sisyphus.solverdeps import sisyphus.syncdb @@ -25,24 +26,16 @@ def start(pkgname, depclean=False, gfx_ui=False, unmerge=False): print(sisyphus.getcolor.bright_red + "\nYou need root permissions to do this!\n" + sisyphus.getcolor.reset) sys.exit() + else: + if gfx_ui: + is_needed = sisyphus.solverdeps.start.__wrapped__(pkgname) + else: + is_needed = sisyphus.solverdeps.start(pkgname) - if unmerge: - p_exe = subprocess.Popen( - ['emerge', '--quiet', '--unmerge', '--ask'] + list(pkgname)) - try: - p_exe.wait() - sisyphus.syncdb.lcl_tbl() - except KeyboardInterrupt: - p_exe.terminate() - try: - p_exe.wait(1) - except subprocess.TimeoutExpired: - p_exe.kill() - sys.exit() - elif depclean: + if is_needed != 0: if gfx_ui: - p_exe = subprocess.Popen( - ['emerge'] + args + pkgname, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p_exe = subprocess.Popen(['emerge'] + args + ['--pretend', '--verbose'] + list( + pkgname), stdout=subprocess.PIPE, stderr=subprocess.PIPE) # kill portage if the program dies or it's terminated by the user atexit.register(sisyphus.killemerge.start, p_exe) @@ -50,10 +43,27 @@ def start(pkgname, depclean=False, gfx_ui=False, unmerge=False): print(p_out.rstrip()) p_exe.wait() - sisyphus.syncdb.lcl_tbl() + print("\nWon't uninstall! Other packages depend on " + str(pkgname)) else: p_exe = subprocess.Popen( - ['emerge'] + args + ['--ask'] + list(pkgname)) + ['emerge'] + args + ['--pretend', '--verbose'] + list(pkgname)) + try: + p_exe.wait() + except KeyboardInterrupt: + p_exe.terminate() + try: + p_exe.wait(1) + except subprocess.TimeoutExpired: + p_exe.kill() + sys.exit() + print(sisyphus.getcolor.bright_red + + "\nWon't uninstall! Other packages depend on " + sisyphus.getcolor.reset + str(pkgname)) + print(sisyphus.getcolor.bright_red + "Use the " + sisyphus.getcolor.reset + sisyphus.getcolor.green + "'--force'" + + sisyphus.getcolor.reset + sisyphus.getcolor.bright_red + " option to override at your own risk!\n" + sisyphus.getcolor.reset) + else: + if unmerge: + p_exe = subprocess.Popen( + ['emerge', '--quiet', '--unmerge', '--ask'] + list(pkgname)) try: p_exe.wait() sisyphus.syncdb.lcl_tbl() @@ -64,3 +74,28 @@ def start(pkgname, depclean=False, gfx_ui=False, unmerge=False): except subprocess.TimeoutExpired: p_exe.kill() sys.exit() + elif depclean: + if gfx_ui: + p_exe = subprocess.Popen( + ['emerge'] + args + pkgname, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # kill portage if the program dies or it's terminated by the user + atexit.register(sisyphus.killemerge.start, p_exe) + + for p_out in io.TextIOWrapper(p_exe.stdout, encoding="utf-8"): + print(p_out.rstrip()) + + p_exe.wait() + sisyphus.syncdb.lcl_tbl() + else: + p_exe = subprocess.Popen( + ['emerge'] + args + ['--ask'] + list(pkgname)) + try: + p_exe.wait() + sisyphus.syncdb.lcl_tbl() + except KeyboardInterrupt: + p_exe.terminate() + try: + p_exe.wait(1) + except subprocess.TimeoutExpired: + p_exe.kill() + sys.exit() diff --git a/src/frontend/cli/sisyphus-cli.py b/src/frontend/cli/sisyphus-cli.py index 93928c4..82773d3 100755 --- a/src/frontend/cli/sisyphus-cli.py +++ b/src/frontend/cli/sisyphus-cli.py @@ -89,13 +89,13 @@ def search(package: List[str] = typer.Argument(...), cat, pn = package[0].split('/') else: cat, pn = '', package[0] - sisyphus.search.start(filter.value, cat, pn, desc, quiet) + sisyphus.searchdb.start(filter.value, cat, pn, desc, quiet) else: if not package: raise typer.Exit( 'No search term provided, try: sisyphus search --help') else: - sisyphus.search.estart(package) + sisyphus.searchdb.estart(package) @app.command("install") -- cgit v1.2.3