В «Северстали» внедрены большие корпоративные системы, такие как SAP или QMET, но есть и много разных задач, которые закрывает собственная разработка, и задачи у этой разработки редко бывают простыми. А значит, и требования к инструментам разработки бывают достаточно специфическими. Что делать, если вашим разработчикам потребовался gcc-9 под CentOS, а его нет в общедоступных репозиториях? Конечно, засучить рукава и создать требуемые пакеты. Вот только задача эта выглядит просто только на первый взгляд.
Если вам интересно, какие грабли разложены на пути к замене системного компилятора, и как мы с ними справились, добро пожаловать под кат.
Stage 1. Собственно сборка gcc
Здесь казалось бы всё просто: берём gcc.spec от пакета gcc-8.3.1, меняем 8 на 9, запускаем rpmbuild –bb, долго ждём? Да, но нет. Для начала придётся пересмотреть и поправить все патчи, а заодно ещё и поставить binutils посвежее, благо это несложно. Потом, мы же не просто так компилятор меняем, нам же ещё какие-нибудь nvptx-tools подавай, а это значит, что когда сборка закончится и начнется тестирование, тесты в libgomp, завязанные на выгрузку кода, начнут виснуть и застревать в разных странных позах.
Решения тут может быть два:
Консервативное: сказать разработчикам «извините, не шмогла» и отключить nvptx-tools.
Экспериментальное: предупредить разработчиков, что nvptx они используют на свой страх и риск, после чего запустить rpmbuild, дождаться застревания в тестах и отстреливать их руками. Вы получите массовые tests failed в результатах, но искомые пакеты будут собраны.
Stage 2. Package libgcc.i686 has inferior architecture
Итак, мы складываем все эти замечательные gcc-9.3.1-3.el8.x86_64.rpm, gcc-offload-nvptx-9.3.1-3.el8.x86_64.rpm и т.д. и т.п. в отдельный репозиторий, индексируем его, подключаем в /etc/yum.repos.d, говорим dnf update и… Нет, ну вы правда думали, что он возьмёт и сразу поставится? Как бы не так. Как известно, 64-разрядные дистрибутивы семейств Debian и RedHat для семейств процессоров x86 в глубине души немножечко 32-разрядные (то есть, по умолчанию существуют в режиме multilib), и поэтому вам потребуется или объявить multilib пережитком прошлого и снести 32-разрядные библиотеки системного компилятора, или создать соответствующие пакеты (libgcc.i686, libgfortran.i686, libgomp.i686, libquadmath.i686 и libstdc++.i686) для новой версии. Как ни забавно, но решения тут тоже может быть два:
Рекомендованное производителем: поставить mock и выполнить полную сборку для i686, наступив на все сопутствующие грабли (nvptx, например, лучше сразу выключить).
Ленивое на скорую руку: дело в том, что 32-битные версии библиотек на самом деле собираются вместе с 64-битными, но никуда в итоге не попадают. В стандартной версии gcc.spec они вообще тупо удаляются, но это как раз недолго и закомментировать. А потом скопировать gcc.spec в libgcc-i686.spec, вымарать из него всю секцию %build, а в %install написать примерно следующее:
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}
tar cf - -C %{_buildrootdir}/%{name}-%{version}-%{release}.x86_64 usr | tar xf - -C %{buildroot}
FULLPATH=%{buildroot}%{_prefix}/lib/gcc/%{gcc_target_platform}/%{gcc_major}
FULLEPATH=%{buildroot}%{_prefix}/libexec/gcc/%{gcc_target_platform}/%{gcc_major}
# fix some things
mkdir -p %{buildroot}/%{_lib}
mv -f %{buildroot}%{_prefix}/%{_lib}/libgcc_s.so.1 %{buildroot}/%{_lib}/libgcc_s-%{gcc_major}-%{DATE}.so.1
chmod 755 %{buildroot}/%{_lib}/libgcc_s-%{gcc_major}-%{DATE}.so.1
ln -sf libgcc_s-%{gcc_major}-%{DATE}.so.1 %{buildroot}/%{_lib}/libgcc_s.so.1
mkdir -p %{buildroot}%{_datadir}/gdb/auto-load/%{_prefix}/%{_lib}
mv -f %{buildroot}%{_prefix}/%{_lib}/libstdc++*gdb.py* \
%{buildroot}%{_datadir}/gdb/auto-load/%{_prefix}/%{_lib}/
pushd %{name}-%{version}-%{DATE}/libstdc++-v3/python
for i in `find . -name \*.py`; do
touch -r $i %{buildroot}%{_prefix}/share/gcc-%{gcc_major}/python/$i
done
touch -r hook.in %{buildroot}%{_datadir}/gdb/auto-load/%{_prefix}/%{_lib}/libstdc++*gdb.py
popd
for f in `find %{buildroot}%{_prefix}/share/gcc-%{gcc_major}/python/ \
%{buildroot}%{_datadir}/gdb/auto-load/%{_prefix}/%{_lib}/ -name \*.py`; do
r=${f/$RPM_BUILD_ROOT/}
%{__python3} -c 'import py_compile; py_compile.compile("'$f'", dfile="'$r'")'
%{__python3} -O -c 'import py_compile; py_compile.compile("'$f'", dfile="'$r'")'
done
rm -rf %{buildroot}%{_prefix}/%{_lib}/%{name}
Теперь достаточно сказать rpmbuild –bb libgcc-i686.spec где-то в соседнем терминале, пока gcc развлекается своим torture, и вуаля, наши 32-битные пакеты у нас в кармане (в смысле, в $RPM_BUILD_ROOT/RPMS/i686). Мы копируем их в наш репозиторий, индексируем его, запускаем dnf makecache –repo gcc-9 && dnf update и… Нет, обновить компилятор всё ещё нельзя.
Stage 3. Annobin и libtool
Те, кто внимательно смотрит на параметры сборки на линуксах линеек RHEL и CentOS, могли заметить, что по умолчанию в gcc подключен плагин annobin. У этого плагина есть неприятная привычка привязываться к версии компилятора, поэтому его придется пересобрать. Детали в принципе изложены в самом annobin.spec в комментариях, поэтому отметим только, что пересобрать его придётся минимум дважды: сперва, используя ещё системный gcc 8.3.1, поправить требование к версии gcc, чтобы gcc < %{gcc_next} превратилось в gcc <= %{gcc_next}, потом, уже заменив gcc, пересобрать заново, вернув требование gcc < %{gcc_next} и раскомментировав строку %undefine _annotated_build – иначе вообще ничего собираться не будет. Ну и для чистоты можно пересобрать в третий раз, вернув _annotated_build на место, а предыдущие две версии пакетов (переходную и без аннотации бинарей) из репозитория удалить.
Остается libtool. Этот джентльмен тоже жёстко привязывается к версии gcc, но к счастью, зависимость эта односторонняя, поэтому libtool можно просто удалить перед заменой gcc, затем собрать его заново новым gcc и добавить соответствующий пакет в наш репозиторий gcc-9.
Как ни странно, на этом всё. У нас есть отдельный репозиторий, подключив который, можно поставить требуемую версию компилятора, а при необходимости вернуть обратно системную версию (командой dnf downgrade gcc), хотя необходимости такой у нас не возникало.
Хабравчане-девопсы, а какие у вас бывали нестандартные запросы от разработчиков?
Sovigod
Какие-то у вас решения из разряда битв с ветряными мельницами.
1. Если вам так хочется самим рулить версиями пакетов — возьмите gentoo|arch, будут вам на выбор gcc от 6.5 до 11.0 и без танцев с бубном. Все патчи предоставит дистрибутив.
2. Ну или по современному — делайте сброку в докер контейнере. Благо у gcc есть официальные образы с версиями от 8 и выше. Тогда сменить версию можно будет поменяв пару цифр в пайплайне сборки приложения.
my2coins
Gentoo не вполне соответствует корпоративным представлениям о стабильности, требуемой для продуктивных решений. Ну а сборка в докере у нас тоже есть. Просто не везде докер хорошо подходит, особенно когда требуется выжать из железа все соки.
quarckster
Какие ещё соки? Контейнеры это просто средство изоляции, там нет никаких абстракций дополнительных от железа.
my2coins
Накладные расходы на виртуализацию все равно есть. А ещё бывают нюансы с доступом ко всяким там железкам из контейнера. Это мы, впрочем, уже победили )
quarckster
Контейнеры не средство виртуализации, там нет никакого гипервизора. По сути это chroot на стероидах.
Tangeman
Сеть и доступ к файлам там таки виртуализируются — со всеми вытекающими. Да, это не то же самое что и в KVM, но всё равно накладные расходы есть, и они очень ощутимы если очень много ввода-вывода (с сетью или файлами), особенно если это всё крутится на недоверенной системе со всеми примочками против спектра и компании, и особенно если этих контейнеров много на одной железке.