Макрос %clang_gcc_wrapper в ROSA. Сборка RPM-пакетов компилятором Clang вместо GCC.

Начиная с версии 6.0.1-3 пакета llvm/clang в дистрибутиве ROSA Fresh есть макрос %clang_gcc_wrapper.

Код макроса

Для тех, кому проще посмотреть его код, чем читать объяснение:
clang-gcc-wrapper.macros
clang-gcc-wrapper.sh

Как при сборке пакета задается компилятор

Существует 2 основных компилятора: GCC и Clang (LLVM). Дистрибутив Роса собирается с помощью GCC, однако для некоторых пакетов используется clang. Обычно это либо очень увесистые проекты (LibreOffice), либо те, разработчики которых сами перешли на clang и используют много функций, специфичных для него (Chromium, Firefox).

Строго говоря, стандарта, как задать компилятор, нет. Но GNU Make издавна используются переменные:

  • CC для задания компилятора языка Си, принимающая значения gcc или clang
  • CXX для задания компилятора языка Си++, принимающая значения g++ или clang++

Другие системы сборки обычно также понимают эти переменные.

Есть 2 способа задать переменные для make:

  1. В аргументах вызова, например:
    make CC=clang CXX=clang++
  2. Переменными окружения:

    1. Установив их глобально (документация:
      export CC=clang
      export CXX=clang
      make
      
    2. Или задав переменные окружения только для одной команды, чтобы они не влияли на все последующие:
      env CC=clang CXX=clang++ make

Так вот, в простых случаях для сборки пакета с помощью clang вместо gcc достаточно в RPM-спеке сделать так:

...
%build
export CC=clang
export CXX=clang
...

Дальше может идти, например, %make.

Флаги компилятора

Для флагов (параметров) компилятора обычно используются переменные:

  • CFLAGS для параметров вызова компилятора Си (gcc, clang)
  • CXXLAGS или CPPFLAGS для параметров вызова компилятора Си++ (g++, clang++)

При сборке RPM устанавливается стандартный набор флагов компилятора, за который отвечает макрос %optflags.
На Ubuntu он очень прост (да, RPM работает на Ubuntu, sudo apt install rpm):

$ rpm --eval '%optflags'
-O2 -g

На Росе сложнее:

$ rpm --eval '%optflags'
-O2 -Wa,--compress-debug-sections -gdwarf-4 -fvar-tracking-assignments -frecord-gcc-switches -Wstrict-aliasing=2 -pipe -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -ffat-lto-objects -fno-delete-null-pointer-checks -fstack-protector --param=ssp-buffer-size=4 -fPIC

Clang версии 6.0 падает в ошибку «Неизвестный флаг» на флагах -frecord-gcc-switches и -fvar-tracking-assignments. Это главная проблема, из-за которой появилась эта статья.

Вообще рекомендую прочитать статью «Оптимизация GCC» на Gentoo Wiki.

Переменным окружения CFLAGS и CPPFLAGS до начала сборки в качестве значения присваивается вот этот набор. Чтобы не присваивался, в начале спека пишут:
%global %{optflags} %{nil}

Далее можно стандартными для шелла методами манипулировать значениями этих переменных.

Подводные камни

Смотрите баг 9567. Суть в том, что макросы %qmake_qt4 и %qmake_qt5 в Росе сделаны так, что в них жестко прописывается набор флагов компилятора из %optflags, которые переопределяют то, что будет задано переменными окружениями и параметрами сборки.

Макрос %clang_gcc_wrapper

Изначально этого макроса не было. Была задача собрать QtWebEngine с помощью clang, т.к. на ABF не хватало ресурсов на сборку с помощью gcc.

Здесь можно посмотреть дерево исходников пакета qt5-qtwebengine на момент, когда он был собран с помощью костылей, в дальнейшем переродившихся в макрос %clang_gcc_wrapper.

Имевшийся код в минимальными изменениями был переделан в макрос, чтобы вместо копирования одного и того же кода из множества строк между спеками просто использовать макрос.

Не все понимают одну важную вещь: при сборке RPM для каждой секции (%prep, %build и др.) создается свой шелл-скрипт, который просто тупо выполняется с параметром -e, что означает фатальный сбой всего скрипта при сбое любой промежуточной команды. Раскрытие макроса означает, что макрос преобразуется в набор шелл-кода, который просто записывается во временный файл со скриптом, который затем выполнится. Всё.

Напомню ссылки на исходники макроса:
сам макрос: clang-gcc-wrapper.macros
вспомогательный скрипт: clang-gcc-wrapper.sh

Как вы уже догадались, текст "%clang_gcc_wrapper" в RPM-спеке просто заменяется на код clang-gcc-wrapper.macros, который потом выполняется в скрипте сборки.

Пример использования этого макроса:

%build
%clang_gcc_wrapper

Живой пример — спек qtiplot (но это несколько сложный случай с нюансами, там потом вручную задаются CFLAGS, %optflgs отключены, но обычно этого не нужно делать).

Код clang-gcc-wrapper.macros делает следующие хаки:

  1. В папке с распакованными исходниками собираемого пакета создает папку local_bin1
  2. Скрипт clang-gcc-wrapper.sh, находящийся в составе пакета clang по адресу /usr/share/clang/clang-gcc-wrapper.sh, копирует в local_bin/clang и local_bin/clang++
  3. В файле local_bin/clang «_CLANG_» заменяется на вывод команда which clang, то есть /usr/bin/clang
  4. В файле local_bin/clang++ «_CLANG_» заменяется на вывод команда which clang++, то есть /usr/bin/clang++
  5. Переменная PATH задается таким образом, что local_bin/ находится слева, благодаря чему по команде clang будет вызываться скрипт local_bin/clang, а не /usr/bin/clang, аналогично для clang++
  6. Задаются переменные окружения CC, CXX, CPP, CLANG2, CLANGXX2 так, чтобы они имели абсолютное знаничение пути к файлам local_bin/clang или local_bin/clang++ начиная от корня системы
  7. Проверяется, что эти значения действительно указывают в нужное место

Скрипт clang-gcc-wrapper.sh, который теперь стал local_bin/clang и local_bin/clang++, принимает все вызовы clang или clang++, смотрит, есть ли в них пробелмные флаги компилятора, то есть -fvar-tracking-assignments или -frecord-gcc-switches, вырезает их и вызывает настоящий clang или clang++, то есть /usr/bin/clang или /usr/bin/clang++, сохраняя все имевшиеся параметры, только лишь вырезав проблемные флаги. В лог сборки при этом записывается, какие флаги были и стали.

Все просто и костыльно, как видите 🙂

Если идет речь о системе сборки qmake, то потребуется еще немного телодвижений, пример по аналогии с qtiplot:

%build
%clang_gcc_wrapper
%qmake_qt5 \
	QMAKE_CC="$CC" \
	QMAKE_CXX="$CXX"

То есть лучше дополнительно задать параметры qmake QMAKE_CC и QMAKE_CXX, в результате чего QMAKE_CC будет указывать в local_bin/clang, а QMAKE_CXX в local_bin/clang++. Смотрите документацию Qt.

В целом макрос %clang_gcc_wrapper можно использовать и там, где было бы достаточно

%global %{optflags} %{nil}
...
%build
export CC=clang
export CXX=clang++

Благодаря использованию макроса можно обойтись без %global %{optflags} %{nil}, то есть сохранить дополнительные флаги безопасности, которые clang понимает.


Примечания

1) Если папка local_bin уже есть в исходниках, то лучше придумать свой любое другое название, например, bin_sdlnflskkhkl и задать его:

%build
export BIN_DIR="bin_sdlnflskkhkl"
%clang_gcc_wrapper

2) Переменные CLANG и CLANGXX я придумал сам, чтобы их можно было использовать в дальнейшем, но, кажется, нигде не использовал.

Отправить ответ

avatar
  Subscribe  
Сообщать по почте