On Wed, Aug 30, 2006 at 08:10:50PM +0400, Dmitry V. Levin wrote: > > (Алгоритм оптимизации BuildRequires обсуждался вчера в частной > > переписке. Правда, спросонья я потерял к нему интерес. Если это > > интересно кому-то ещё, тогда можно перенести обсуждение сюда.) > > Думаю, что лучше перенести сюда - вдруг среди нас есть специалист/практик > по теории графов? Возможна даже более агрессивная оптимизация, чем предложенная и реализованная в rpm-utils-0.9.2. Вот пример, когда этой оптимизации как бы недостаточно: $ cat sdltest.c #include int main() { void *p = SDL_Init; return !!p; } $ packagereq -o /dev/stdout -- gcc `sdl-config --cflags` sdltest.c -lSDL packagereq: building requires list: esound libSDL-devel esound libSDL-devel $ Вопрос: почему остался esound? Ситуация такая: при линковке используется только пакет libSDL-devel (через симлинк /usr/lib/libSDL.so), но не сам libSDL. Вместе с тем, при линковке через libSDL.so линкер цепляет все его пререквизиты, включая /usr/lib/libesd.so.0 из пакета esound. Получается, что список {esound,libSDL-devel} не поддается оптимизации, потому что из списка выпал собственно libSDL. То есть как бы не удается достроить цепочку libSDL-devel -> libSDL -> esound, с которой текущая оптимизация дала бы результат. Думаю, что эта ситуация довольно типична. Просто в списке /etc/buildreqs/packages/essential присутствует паттерн ^lib[^-]+$, из-за которого все библиотечные пакеты удаляются; а esound не вписывается в это правило по названию. Этот паттерн сам по себе довольно нечестный. Используя buildreq2 (где вся оптимизация основывалась исключительно на зависимостях), я обнаружил несколько ошибок в пакетах, когда пакет lib%name-devel не требовал соответствующего пакета lib%name. В этом случае lib%name и lib%name-devel подряд присутствовали в BuildRequires. См. напр. #6753 libopenslp-devel should depend on libopenslp #6754 libradiusclient-devel should depend on libradiusclient #6755 libsubversion-devel should depend on libsubversion #6756 libtidy-devel should depend on libtidy Я вижу два подхода, которые в большей или меньшей степени способствуют, с одной стороны, более агрессивной оптимизации, с другой -- избавлению от нечестного паттерна. 1) Низкоуровневый подход (в меньшей степени). Добавить и использовать специальную опцию в packageof. Для каждого заданного файла, помимо того, чтобы пробивать его по rpmdb, нужно специальным образом обрабатывать симлинки. А именно, для каждого симлинка делать realpath() и полученный путь ещё раз пробивать по rpmdb. Какой недостаток у этого подхода? Это вопрос философский. :) То есть недостаток довольно тонкий -- настолько тонкий, что не является препятствием для реализации, но непременно подлежит обсуждению. Речь идет о том, что при использовании симлинка используется не cтолько сам симлинк, сколько файл, на который показывает симлинк (симлинк и файл могут находится в разных пакетах). При этом симлинк может показывать, вообще говоря, куда угодно. Я ведь исхожу из того, что мы достраиваем цепочку "вовнутрь", то есть вглубь дерева, а симлинк можеть дать результат и "вовне". Здесь можно представить себе альтернативы. То есть можно на выходе получить слишком специфические зависимости. Далее, приходит в голову, что раскрывать симлинки таким образом нужно только для системных вызовов типа open, но не для stat и особенно не для lstat. Здесь опять же грёбаный Unix way оказывается недостаточно гибким. 2) Высокоуровневый подход (в большей степени). По списку пакетов на входе выстраиваем транзитивное замыкание и далее уже оптимизируем транзитивное замыкание, а не сам список пакетов. Это как раз изменение в условии задачи, о котором я писал: -P_0\subset P +P_0\subset [P] Прикол здесь в том, что в некоторых случаях на выходе оптимизации могут появиться пакеты, которые не входят в начальный список P. Здесь есть две проблемы. 1) Собственно выстраивание транзитивного замыкания по текущей базе rpm. Поскольку будет использоваться что-то вроде rpm --whatprovides, возможны двусмысленности, о которых писал vsu. Если нечто предоставляется сразу двумя пакетами, тогда не ясно, что делать. Например, если какой-то пакет требует webclient, при этом webclient предоставляют elinks и konqueror, тогда уже страшно подумать, в какую сторону может пойдет процесс. По двусмысленным зависимостям транзитивное замыкание наверное лучше не достраивать. 2) Нельзя также достраивать транзитивное замыкание в сторону увеличения количества циклов. В общем этот подход более сложный, но над ним стоит подумать. (vsu на самом деле написал о более тонкой проблеме, над которой также стоит подумать.) Что дает этот подход? Эксперименты с buildreq2 показывают, что этот подход делает полностью ненужным список /etc/buildreqs/packages/essential. Точнее, в buildreq2 этот список hardcoded и состоит всего из двух пакетов -- basesystem и rpm-build. Фактически в списке essential содержатся паттерны для транзитивного замыкания basesystem + rpm-build плюс один нечестный паттерн -- ^lib[^-]+$. Если же добавить в rpm-build зависимость на basesytem, тогда достаточно просто как бы "обрубать" топологию пакетов на уровне rpm-build (точнее, "вычеркивать" rpm-build в решете), вследствие чего все пререквизиты rpm-build будут автоматически вычеркнуты. Таким образом, подход, основанный на зависимостях, полностью себя оправдывает. Короче, предлагаю в ближайшей перспективе реализовать подход №1 -- он достаточно прост в реализации, и у него не просматривается грубых недостатков. Нечестный паттерн ^lib[^-]+$ после этого по идее станет не нужен.