Автор Тема: Удаление хвостов удаляемых пакетов по признаку ненужности другим пакетам  (Прочитано 4298 раз)

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
С аргументами кстати, даже удобнее,чем с файлом если их конечно можно несколько забивать.
Сколько угодно. Там список. И удаляемых пакетов и "сохраняемых" может быть несколько...
Списком в файле теоретически можно оптимизировать работу и улучшить безопасность  - записав туда системные пакеты, случайный снос которых чреват проблемами.

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
У меня тут может более старая версия питона (или не полностью установлен), надо на другой машине проверить.
$ python3 --version
Python 3.5.1

Актуальный p8

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
У меня старее 3.2.2. Тут я обновить не смогу, а другой бук на работу уволок.

Список в файле можно сделать типа конфига, где будут системные вещи, которые не надо каждый раз трогать. А для пользования вполне аргументы хороши.

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
Может бог с ними с минутами, для начала и так очень хорошо пусть хоть полчаса думает. Всё не руками делать. Пускай apt-cache будет, если он более корректный список выдаёт.

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
По мне наоборот... Но на apt-cache список для xfce был ЕМНИП десятки минут. Не приемлемо. Проблема в том что оно идет вглубь по зависимостям "до самого низа".  Если смотреть в сторону apt-cache то нужно оптимизировать сам алгоритм...

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
apt-cache может выдавать несколько пакетов любой из которых удовлетворяет зависимость. Да, там надо отсекать, причём сразу, неустановленные пакеты. Другой оптимизации не вижу. К тому же это потянет дополнительные проверки. Я не настаиваю именно на нём, rpm так rpm.
Пока можно и на этом остановиться и обдумать.
« Последнее редактирование: 18.03.2017 05:03:11 от stranger573 »

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
есть такая опция у apt-get remove - "-D"
Цитировать
-D  При удалении пакета стремиться удалить компоненты, от которых он зависит

Прогнал ее на таких параметрах на своей системе:
sudo apt-get remove -D xfce4-full xfce4-default xfce4-regular
Чтение списков пакетов... Завершено
Построение дерева зависимостей... Завершено
Следующие пакеты будут УДАЛЕНЫ:
.....
0 будет обновлено, 0 новых установлено, 102 пакетов будет удалено и 3 не будет обновлено.
Необходимо получить 0B архивов.
После распаковки будет освобождено 122MB дискового пространства.
Продолжить? [Y/n] ^C

И получил то-же количество пакетов к удалению (102) что выдал мой скрипт.
Судя по исходникам apt-get, при отработке опции -D тоже производится проверка на то, что удаляемый по зависимостям пакет не требуется кому нибудь еще...
Причем, если я правильно понял код, он работает также рекурсивно - если по зависимостям в удаляемые попал некоторой на итерации хотя-бы один новый пакет,  то  запускается по крайней мере еще один проход.
Там есть нюанс - ему нужно правильно подложить начальный список пакетов к удалению. Например, для полного удаления xfce нужно указать все три метапакета - xfce4-full, xfce4-default, xfce4-regular.

bool pkgProblemResolver::RemoveDepends()
{
   bool Debug = _config->FindB("Debug::pkgRemoveDepends",false);
   bool MoreSteps = true;
   while (MoreSteps == true)
   {
      MoreSteps = false;
      for (pkgCache::PkgIterator Pkg = Cache.PkgBegin();
   Pkg.end() == false; Pkg++)
      {
if (Cache[Pkg].Delete() == false)
    continue;
for (pkgCache::DepIterator D = Pkg.CurrentVer().DependsList();
      D.end() == false; D++)
{
    if (D->Type != pkgCache::Dep::Depends &&
D->Type != pkgCache::Dep::PreDepends)
       continue;
   
    pkgCache::PkgIterator DPkg = D.TargetPkg();
    if (DPkg->CurrentVer == 0 || Cache[DPkg].Delete() == true)
       continue;
    if ((Flags[DPkg->ID] & Protected) == Protected)
       continue;
   
    bool Remove = true;

    // Check if another package not being removed or being
    // installed requires this dependency.
    for (pkgCache::DepIterator R = DPkg.RevDependsList();
R.end() == false; R++)
    {
       pkgCache::PkgIterator RPkg = R.ParentPkg();

       if (R->Type != pkgCache::Dep::Depends &&
   R->Type != pkgCache::Dep::PreDepends)
  continue;

       if ((Cache[RPkg].Install() &&
    (pkgCache::Version*)R.ParentVer() == Cache[RPkg].InstallVer &&
    Cache.VS().CheckDep(DPkg.CurrentVer().VerStr(), R) == true) ||
   (RPkg->CurrentVer != 0 &&
    Cache[RPkg].Install() == false &&
    Cache[RPkg].Delete() == false &&
    Cache.VS().CheckDep(DPkg.CurrentVer().VerStr(), R) == true))
       {
  Remove = false;
  break;
       }
    }

    if (Remove == false)
       continue;

    // Also check every virtual package provided by this
    // dependency is required by packages not being removed,
    // or being installed.
    for (pkgCache::PrvIterator P = DPkg.CurrentVer().ProvidesList();
P.end() == false; P++)
    {
       pkgCache::PkgIterator PPkg = P.ParentPkg();
       for (pkgCache::DepIterator R = PPkg.RevDependsList();
    R.end() == false; R++)
       {
  pkgCache::PkgIterator RPkg = R.ParentPkg();
       
  if (R->Type != pkgCache::Dep::Depends &&
      R->Type != pkgCache::Dep::PreDepends)
     continue;

  if ((Cache[RPkg].Install() &&
       (pkgCache::Version*)R.ParentVer() == Cache[RPkg].InstallVer &&
       Cache.VS().CheckDep(P.ProvideVersion(), R) == true) ||
      (RPkg->CurrentVer != 0 &&
       Cache[RPkg].Install() == false &&
       Cache[RPkg].Delete() == false &&
       Cache.VS().CheckDep(P.ProvideVersion(), R) == true))
  {
     Remove = false;
     break;
  }
       }
    }

    if (Remove == false)
       continue;

    if (Debug == true)
       clog << "Marking " << DPkg.Name() << " as a removable dependency of " << Pkg.Name() << endl;

    Cache.MarkDelete(DPkg);

    // Do at least one more step, to ensure that packages which
    // were being hold because of this one also get removed.
    MoreSteps = true;
}
      }
   }
   return true;
}


Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
Причем вот такая конструкция тоже работает как оказалось :-)
#apt-get remove -D xfce4-default xfce4-regular xfce4-full gvfs-backend-google+ mousepad+

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
Да, действительно в восьмёрке (возможно в семёрке тоже, в p6 точно нет) появилась опция -D. Надо будет повозиться, посмотреть как оно. :-)
Плюсы в apt-get всегда были, только с ними надо осторожнее, если пакет не установлен — он установится.
Тем не менее Ваш скрипт работает лучше, он ведь у вас по одному пакету всё вытянул. Да и работа со списком безопаснее, и помимо того не требует рута. Скрипт интереснее. :-)

У меня свой интерес тут есть. Поскольку на нескольких машинах p6 останется до конца жизни железа. Как на них пристроить это дело буду сам думать. Питон любой версии как оказалось можно подложить локально, только надо указать в скрипте путь к нему. Сейчас обложился букварями, изучаю (прекрасный повод для самообразования). Для p6 есть над чем подумать. В ней, пожалуй придётся всё таки apt-cache использовать, поскольку только он даёт именно имена пакетов. rpm очень любит выдавать имена библиотек или пути к ним — это не обрабатывается. Да и строки питоном же можно обрабатывать не обращаясь к башу. Может, что и получиться. :-)

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
Доточил до совместимости с 2.7 
Правда с 3-м python эта версия не совместима (из за print в stderr).
И тут еще по сравнению с прошлой версией пару мелких ошибок исправлено.
У меня по одному пакету вытягивает потому, что я "поднимаюсь" на уровень выше по обратным  зависимостям от удаляемого пакета при старте скрипта - и все от чего он зависит добавляю сразу в кандидаты на удаление.
#!/usr/bin/python
# -*- coding: utf-8 -*-

import subprocess
import sys
import copy

def get_dependency(package):
    if package == "":
        return None
    resp = subprocess.check_output(["rpm", "--requires", "--qf", "'%{REQUIRENAME}'", "-q", package],
                                   universal_newlines=True)
    s = str(resp)
    #    eprint(s)
    if s and len(s.strip()) > 0:
        return s.replace("'", "").split("\n")
    else:
        return None


def get_all_packages():
    resp = subprocess.check_output(["rpm", "-qa", "--qf", "'%{name}\n'"], universal_newlines=True)
    s = str(resp)
    return s.replace("'", "").split("\n")


def get_whats_req(package):
    args = ["/bin/sh", "-c",
            "rpm --test -e " + package +
            " 2>&1 >/dev/null |  egrep  -o -e 'для.+' | sed 's/для //' | sed 's/-[^-]*-[^-]*$//'"]

    resp = subprocess.check_output(args, universal_newlines=True, stderr=None)

    s = str(resp).strip("\n")
    if s and len(s.strip()) > 0:
        return s.replace("'", "").split("\n")
    else:
        return None


candidats = {}
all_packages = get_all_packages()

blacklist = set()
whitelist = set()


def process_pckg_list(pckg_list):
    # eprint(blacklist)
    for package in pckg_list:
        try:
            dep = candidats[package]
            has_before = True
        except KeyError:
            dep = set()
            candidats[package] = dep
            has_before = False

        try:
            dep_list = get_dependency(package)
            if dep_list is None:
                return True
        except subprocess.CalledProcessError:
            print >> sys.stderr , "error while processing :", package
            # xtree.pop(package)
            return False

        for s in dep_list:
            s = s.strip()
            if len(s) == 0:
                continue
            if "(" in s:
                continue
            if "/" in s:
                continue
            if s in whitelist:
                continue

            try:
                pname = s.strip().split()[0].strip()
            except IndexError:
                print >> sys.stderr, "index error:", s
                continue
            if pname in blacklist:
                continue
            # print(package, "::", s,"->",pname)
            dep.add(pname)
            if pname not in candidats:
                status = process_pckg_list([pname])
                if not status:
                    try:
                        if not has_before:
                            print >> sys.stderr, "blacklist:", pname
                            blacklist.add(pname)
                            del candidats[pname]
                    except KeyError:
                        pass

    return True


def main():
    args = sys.argv
    args.pop(0)
    args1 = set(copy.copy(args))
    for p in args:
        if p[-1] == '+':
            whitelist.add(p[0:-1])
            try:
                args1.remove(p)
            except KeyError:
                pass
            continue
        deps = get_whats_req(p)
        if deps:
            args1 = args1.union(set(deps))

    args1 -= whitelist
    print >> sys.stderr, args1
    print >> sys.stderr, whitelist

    process_pckg_list(args1)
    pass_count = 1

    class NeedPassException(Exception):
        pass

    dep_cache = {}

    while True:
        pass_count += 1
        need_next_pass = False
        try:
            for p in candidats:
                try:
                    deps = dep_cache[p]
                except KeyError:
                    try:
                        deps = get_whats_req(p)
                        dep_cache[p] = deps
                    except subprocess.CalledProcessError:
                        dep_cache[p] = []
                        continue
                if deps is None:
                    continue

                for r in deps:
                    r = r.strip()
                    if len(r) == 0:
                        continue
                    if r == p:
                        continue
                    if r not in all_packages:
                        continue
                    if r not in candidats or r in whitelist:
                        # noinspection PyUnusedLocal
                        need_next_pass = True
                        xlist = candidats[p]
                        for c1 in xlist:
                            try:
                                del candidats[c1]
                            except KeyError:
                                pass
                        try:
                            del candidats[p]
                        except KeyError:
                            pass

                        raise NeedPassException
                    pass
                pass
        except NeedPassException:
            continue
        if not need_next_pass:
            break
    pass
    result = ''
    print >> sys.stderr,  "Всего найдено пакетов для удаления:", len(candidats)
    for s in candidats.keys():
        result = result + s + " "
    print(result)


if __name__ == "__main__":
    main()
« Последнее редактирование: 18.03.2017 21:53:09 от ASte »

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
Если смотреть с сторону apt - то не знаю как в 6-ке, в 8-ке есть возможность добавлять к нему расширения на lua.
Это перспективнее и правильнее. Скрипт, построенный на вызовах apt работает долго потому, что apt подгружает в память свою базу пакетов и делает некие подготовительные действия перед основной работой. За счет этого работает корректнее чем rpm. Но если его дергать из командной строки много раз, то эти "подготовительные действия" выполняются при каждом вызове и от этого возникают тормоза.
Нужно использовать само высокоуровневое api, предоставляемое apt.
Примеры можно подсмотреть здесь:
apt-scripts - Lua scripts for APT
« Последнее редактирование: 18.03.2017 22:04:34 от ASte »

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
В p6 питон версий 2.6.6 и 3.2.2. В версии 3.2.2 нет методов list.copy() и list.clear() это появилось в 3.3. А скрипт для 2.7 в 2.6.6 даёт ошибку:
$ ./2DepTree.py medit
Traceback (most recent call last):
  File "./2DepTree1.py", line 42, in <module>
    all_packages = get_all_packages()
  File "./2DepTree1.py", line 22, in get_all_packages
    resp = subprocess.check_output(["rpm", "-qa", "--qf", "'%{name}\n'"], universal_newlines=True)
AttributeError: 'module' object has no attribute 'check_output'

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
apt-cache, полагаю, долго работает потому как для каждой зависимости разыскивает по всей базе пакеты её содержащие.
А rpm просто берёт то, что прописано в пакетах, т.е. собственно сами зависимости. По этим зависимостям надо уже отдельно запрашивать имена пакетов.

Оффлайн stranger573

  • Мастер
  • ***
  • Сообщений: 1 568
В версии питона 3.2.2 работает такой вариант (добавлен импорт copy, изменён соответственно метод списка, убраны все вхождения "sep="):
#!/usr/bin/python3

import subprocess
import sys
import copy


def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)


def getDependency(package):
    if package == "":
        return None
    # resp = subprocess.check_output(["/bin/sh", "-c", "apt-cache depends "+ package +" | grep -v '<' | grep -v Требует | sort | uniq | sed 's/-[^-]*-[^-]*$//'"],universal_newlines=True);

    resp = subprocess.check_output(["rpm", "--requires", "--qf", "'%{REQUIRENAME}'", "-q", package],
                                   universal_newlines=True);
    s = str(resp);
#    eprint(s)
    if s and len(s.strip()) > 0:
        return s.replace("'", "").split("\n")
    else:
        return None


def getAllpackages():
    resp = subprocess.check_output(["rpm", "-qa", "--qf", "'%{name}\n'"], universal_newlines=True);
    s = str(resp)
    return s.replace("'", "").split("\n")


def getWhatReq(package):
    # args = ["/bin/sh", "-c",  "apt-cache whatdepends  " + package + " | grep -v '<' | grep -v Требует | sort | uniq | sed 's/-[^-]*-[^-]*$//'"]
    # resp = subprocess.check_output(["rpm", "--whatrequires",  "--qf", "'%{name}\n'", "-q",  package  ],universal_newlines=True, stderr=None)
    args = ["/bin/sh", "-c",
            "rpm --test -e " + package + " 2>&1 >/dev/null |  egrep  -o -e 'для.+' | sed 's/для //' | sed 's/-[^-]*-[^-]*$//'"]

    resp = subprocess.check_output(args, universal_newlines=True, stderr=None)

    s = str(resp).strip("\n");
    if s and len(s.strip()) > 0:
        return s.replace("'", "").split("\n")
    else:
        return None


candidats = {}
all = getAllpackages()

blacklist = set()  # set(["dbus","control","grep","sed","samba_common","samba-client","udev","shadow-utils","tcb-utils","pam0-tcb","nss_tcb"])
whitelist = set()

def process_pckg_list(pckg_list):
    # eprint(blacklist)
    for package in pckg_list:
        try:
            dep = candidats[package]
            hasBefore = True
        except KeyError:
            dep = set()
            candidats[package] = dep
            hasBefore = False

        try:
            list = getDependency(package)
            if list == None:
                return True
        except subprocess.CalledProcessError:
            eprint("erron while processing :", package)
            # xtree.pop(package)
            return False

        for s in list:
            s = s.strip()
            if len(s) == 0:
                continue
            if "(" in s:
                continue
            if "/" in s:
                continue

            try:
                pname = s.strip().split()[0].strip()
            except IndexError:
                eprint("index error:", s)
                continue;
            if pname in blacklist:
                continue
            # print(package, "::", s,"->",pname)
            dep.add(pname)
            if pname not in candidats:
                status = process_pckg_list([pname])
                if not status:
                    try:
                        if not hasBefore:
                            eprint("blacklist:", pname);
                            blacklist.add(pname)
                            del candidats[pname]
                    except KeyError:
                        pass

    return True


def main():
    args = sys.argv  # [-1]
    args.pop(0)
    args1 = set(copy.copy(args))
    for p in args:
        if p[-1]== '+':
            whitelist.add(p[0:-1])
            try :
                args1.remove(p)
            except KeyError:
                pass
            continue
        deps = getWhatReq(p)
        if deps:
            args1  = args1.union(set(deps))

            # args1 = deps(set(args1))
    args1 -= whitelist
    eprint(args1)
    # eprint(blacklist)
    eprint(whitelist)

    process_pckg_list(args1)
    # main(["Ristetto"])

    #    print(len(candidats))
    #    print(candidats)

    #    print("------------------")
    needNextPass = False
    passCount = 1

    class NeedPassException(Exception):
        pass

    dep_cache = {}
    # eprint(candidats)

    while (True):
        # eprint ("------------pass ", passCount)
        passCount += 1
        needNextPass = False
        try:
            for p in candidats:
                try:
                    deps = dep_cache[p]
                except KeyError:
                    try:
                        deps = getWhatReq(p)
                        dep_cache[p] = deps
                    except subprocess.CalledProcessError:
                        # eprint("p=",p)
                        dep_cache[p] = []
                        continue
                if deps == None:
                    # eprint("getWhatReq(",p ,") return None" )
                    continue

                for r in deps:
                    r = r.strip()
                    if len(r) == 0:
                        continue
                    if r == p:
                        # eprint("skip [" + r + "]")
                        continue
                    if r in whitelist:
                        continue
                    if r not in all:
                        continue
                    if r not in candidats:
                        needNextPass = True
                        xlist = candidats[p]
                        for c1 in xlist:
                            try:
                                del candidats[c1]
                                # eprint("removed ",c1)
                            except KeyError:
                                pass
                        try:
                            del candidats[p]
                            # eprint("removed ", p)
                        except KeyError:
                            pass

                        raise NeedPassException
                    pass
                pass
        except NeedPassException:
            continue
        if not needNextPass:
            break
    pass
    result = ''
    eprint("Всего найдено пакетов для удаления:", len(candidats.keys()))
    for s in candidats.keys():
        result = result + s + " ";
    print(result)


if __name__ == "__main__":
    main()

Но вот результат работы удручающий, rpm вместо списка имён пакетов даёт сами библиотеки:
$ ./DepTree5.py medit
{'medit'}
set()
предупреждение: пакет libICE.so.6 не установлен
erron while processing : libICE.so.6
blacklist: libICE.so.6
предупреждение: пакет libSM.so.6 не установлен
erron while processing : libSM.so.6
blacklist: libSM.so.6
предупреждение: пакет libX11.so.6 не установлен
erron while processing : libX11.so.6
blacklist: libX11.so.6
предупреждение: пакет libatk-1.0.so.0 не установлен
erron while processing : libatk-1.0.so.0
blacklist: libatk-1.0.so.0
предупреждение: пакет libcairo.so.2 не установлен
erron while processing : libcairo.so.2
blacklist: libcairo.so.2
предупреждение: пакет libgdk-x11-2.0.so.0 не установлен
erron while processing : libgdk-x11-2.0.so.0
blacklist: libgdk-x11-2.0.so.0
предупреждение: пакет libgdk_pixbuf-2.0.so.0 не установлен
erron while processing : libgdk_pixbuf-2.0.so.0
blacklist: libgdk_pixbuf-2.0.so.0
предупреждение: пакет libgio-2.0.so.0 не установлен
erron while processing : libgio-2.0.so.0
blacklist: libgio-2.0.so.0
предупреждение: пакет libglib-2.0.so.0 не установлен
erron while processing : libglib-2.0.so.0
blacklist: libglib-2.0.so.0
предупреждение: пакет libgmodule-2.0.so.0 не установлен
erron while processing : libgmodule-2.0.so.0
blacklist: libgmodule-2.0.so.0
предупреждение: пакет libgobject-2.0.so.0 не установлен
erron while processing : libgobject-2.0.so.0
blacklist: libgobject-2.0.so.0
предупреждение: пакет libgthread-2.0.so.0 не установлен
erron while processing : libgthread-2.0.so.0
blacklist: libgthread-2.0.so.0
предупреждение: пакет libgtk-x11-2.0.so.0 не установлен
erron while processing : libgtk-x11-2.0.so.0
blacklist: libgtk-x11-2.0.so.0
предупреждение: пакет libpango-1.0.so.0 не установлен
erron while processing : libpango-1.0.so.0
blacklist: libpango-1.0.so.0
предупреждение: пакет libpangocairo-1.0.so.0 не установлен
erron while processing : libpangocairo-1.0.so.0
blacklist: libpangocairo-1.0.so.0
предупреждение: пакет libpython2.6.so.1.0 не установлен
erron while processing : libpython2.6.so.1.0
blacklist: libpython2.6.so.1.0
предупреждение: пакет libxml2.so.2 не установлен
erron while processing : libxml2.so.2
blacklist: libxml2.so.2
предупреждение: пакет liblzma.so.5 не установлен
erron while processing : liblzma.so.5
blacklist: liblzma.so.5
предупреждение: пакет libz.so.1 не установлен
erron while processing : libz.so.1
blacklist: libz.so.1
Всего найдено пакетов для удаления: 1
medit

Оффлайн ASte

  • Мастер
  • ***
  • Сообщений: 1 566
$ ./DepTree.py medit
set(['medit'])
set([])
Всего найдено пакетов для удаления: 1
medit
И rpm видимо тоже другой... У меня встречается подобная ругань, но ее не много.
Тогда нужно копать в сторону дополнения для apt.

Цитировать
Notice
------

APT-RPM supports translation of filenames to package names internally
since version 0.5.15cnc3, but filenames must be in the pkglists for
the internal version to work. It means that to access all files in the
system pkglists must be built with --bloat, so using this script is
still an option.