На днях возникла необходимость собрать более свежий gcc.

История такова: у меня есть на руках slackware-14.2, там софт не новый и не весь, который есть на свете. Так или иначе библиотеку ПО приходится расширять. Понадобился мне gitea, а для его сборки надо nodejs и golang. Вы можете сказать, что, мол, есть же бинарики на сайте, тем более это гошка - запустится на любой вменяемой тачке. Сразу скажу, что это не спортивно. Да и потом мы же не поработать сюда пришли :) Итак, берём последний nodejs и, упс, не собирается. Залазием в него, а там в системных требованиях сто лет назад (чуть ли не с 10-й версии) уже проставили gcc-6 в качестве требования. Заранее. То есть 12.19.1 ещё работало с gcc-5.5.0, а 12.20.0 - уже нет. Акей. Что там с гошкой? Да примерно та же история - версия 1.14.12 собирается, а что-то более свежее - 1.14.13 или 1.15.6 - уже нет.

Можно собрать и пользоваться старыми версиями nodejs и go-шки и они скорее всего не откажутся поработать с новой gitea (спойлер - с текущей на момент написания заметки версией, 1.13.1, они действительно работают). Но мы не ищем лёгких путей.

Итак, нам нужен новый gcc. Вот прям самый новый. Можно, конечно, взять 9-ку или даже 8-ку (она-то уж точно стабильная - скорее всего для 8-ки уже не будет даже патч-версий), типа, они не такие новые, но будут постабильнее 10-ки, однако, в slackware-current уже поставили на вооружение gcc-10.2.0 так что, дабы сильно не переделывать слакбилд, возьмём её. Ну, то есть как - не переделывать? Slackware-14.2 всё ещё не отменяла поставку .la-файлов, поэтому этот момент в слакбилде мы прорабатываем. Далее берём слаку в докере, lxc или в виртуалке (ахтунг, нужно приличное количество оперативки: на 4 ядра примерно 6+ гигов) и проверяем собираемость слакбилда. Долго, но успешно.

В своём дефолтном виде нам gcc-10.2.0 не подходит, он же системный, а значит он пересечётся с тем gcc, который уже установлен в slackware, а так делать нельзя. (Нет, конечно, так сделать можно, но надо позаботиться и пересобрать всё и вся с 10-м gcc, а это в наши планы не входит).

Ну чтоже, у нас есть альтернатива - собрать этот gcc как "ещё один" компилятор. Это не самый лучший и не самый "удобный" вариант, но что поделать. И да, это решение налагает некоторые ограничения на собираемые новой gcc-ой пакеты - придётся модифицировать слакбилды: не только выставлять переменные CC и CXX, но и некоторые другие. О них чуть позже.

Итак сказано - а теперь неплохо бы сделать... И тут начинается цирк: Оказывается, если gcc ставить в prefix и попутно задать libdir, который не заканчивается на lib или lib64, то gcc ведёт себя прилично: то есть ставит свои либы прям в указанный каталог и всё получается красиво. Но если libdir заканчивается каталогами lib или lib64, то gcc ставит либы в... этот каталог + x86_64-slackware-linux/10.2.0/lib64 (в моём случае target и host системы - это x86_64-slackware-linux, как вы уже догадались). Интеллектуальненький, однако, gcc. Я-то хотел мимикрировать под системный gcc, только поставиться "рядом" и бинарники сложить не в /usr/bin (кстати, с бинарниками интересная картина - они тоже именуются "интересно", но я в этот момент не вникал), чтобы не затереть бинари системного gcc, а в /usr/lib64/gcc/x86_64-slackware-linux/10.2.0/bin . Соответственно, библиотеки пошли бы в /usr/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64 . А у меня получается, что либы попадают вообще в /usr/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64 - то есть какой-то странный, дикий путь... от системного gcc отличается и весьма ощутимо, слишком длинноват - совсем не то что надо.

Ладно, чёрт с ним. Будем выправлять. Надо исправить Makefile.in и Makefile.am... собственно, и не только. Искать надо libsubdir. Собственно, target у них описывается переменной target_noncanonical - это вторая подстрока, которую надо искать.

Выясняется, что каталоги include и finclude ставятся тоже по этому странному длинному пути с lib64 на конце. Это всё правится в тех же makefile-ах - переменные sanincludedir fincludedir cafexeclibdir gfor_cdir libsubincludedir gdc_include_dir и для libstdc++ gxx_include_dir.

Акей, теперь время правильно указать опции сборки, помимо правильного положения --prefix и --exec-prefix (оба /usr), надо прописать всякие --bindir, --sbindir. Далее, --includedir=/usr/include и надо не забыть, что libstdc++ у нас - это важная либа, для неё include-ы надо прописывать отдельно: --with-gxx-include-dir=/usr/include/c++/$VERSION . Также надо проверить, что мы собираем компилятор в 3 присеста --enable-bootstrap --enable-stage1-checking. Slackware-current собирает свой дефолтный компилятор с новым стандартом строк для C++11, а нам это не надо, указываем --with-default-libstdcxx-abi=gcc4-compatible . И на всякий пожарный указываем --disable-canonical-system-headers, чтобы gcc не мудрил там чего-то с путями к заголовкам (правильно это или нет я, если честно, не знаю, но вроде пока что на какие-то проблемы с этой опцией я не наткнулся).

Вроде на первый взгляд с опциями разобрались. Теперь надо не забыть, что у gcc есть info-файлы, man-файлы и locale. Они все в системе уже есть, и, поскольку я себе слабо представляю, как ими пользоваться, если они в нестандартных местах, то ничего более умного, как просто удалить их, я не придумал. Зато не пересекаемся с системным gcc.

Также надо ещё обратить внимание, что слакбилд через patchelf удаляет rpath из бинарников собранного компилятора. Видимо, либо охота за байтами свободного места, либо очередное проявление слабоумия и паранойи безопасТников - убираем эту строку, мы же не хотим получить сломанный, наглухо безопасТный компилятор. Нам не шашечки, нам ехать надо.

Ну и самое вкусное на последок - я во время преобразования путей лихим делом запатчил makefile-ы для libgo, а имено - путь в переменной toolexeclibgodir, после этого развалилась возможность собирать гугловский go, это неприятно. Решения проблемы два - либо не трогать toolexeclibgodir либо поставить симлинк c /usr/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64/go на /usr/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64/go/10.2.0/x86_64-slackware-linux . Я выбрал второй вариант.

Судя по всему, мне ещё предстоит поесть немного багов, связанных с путями. То есть у меня есть ещё про запас ada|gnat, gnu fortran, но на практике их негде применить. Я не знаю популярного ПО, которое бы было бы написано на этих языках. Таким образом, проверить их работоспособность мне не на чем.

Естественно, получившийся c/c++ надо ещё уметь применить. Если с гошкой надо выставить GOROOT в /usr/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64 и этого по идее должно хватить, то с сями выставить надо несколько других переменных:

export CC=/usr/lib64/gcc/x86_64-slackware-linux/10.2.0/bin/gcc
export CXX=/usr/lib64/gcc/x86_64-slackware-linux/10.2.0/bin/g++
export CPP=/usr/lib64/gcc/x86_64-slackware-linux/10.2.0/bin/cpp
export CFLAGS="$CFLAGS -I/usr/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64/include -I/usr/include/c++/10.2.0 -I/usr/include/c++/10.2.0/x86_64-slackware-linux"
export CXXFLAGS="$CFLAGS"
export LDFLAGS="-L/usr/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64 -static-libstdc++ -static-libgcc"
export LD_LIBRARY_PATH="/usr/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64"
export LD_RUN_PATH="/usr/lib64/gcc/x86_64-slackware-linux/10.2.0/lib64"
export PATH="/usr/lib64/gcc/x86_64-slackware-linux/10.2.0/bin:$PATH"

в таком варианте у меня всё начало как следует собираться.

И на этом можно было бы поставить точку, если бы не одно соображение, почему, собственно, выходит такое странное при работе с компилятором gcc, откуда берутся "магические" пути и так далее.

В стиле теорий заговоров, которые нынче популярны на ютубах и в прочих местах, типа рентв, предположу, что идея была в том, чтобы сделать gcc перемещаемым. Чтобы gcc во-первых сам "угадывал" правильные пути, а во-вторых мог быть с этим всем хозяйством подвинут в какой-нибудь отдельный каталог и не потерял при этом свою работоспособность. Например, я собираю его в префикс ${HOME}/gcc/10 и это оказалась такая удачная сборка, что некий другой user захотел её унести к себе в хомяк на другой системе. Но ему не надо, чтобы хомяк назывался по имени моего пользователя, он же хочет положить этот gcc к себе в хомяк... Соответственно, в этом случае gcc должен уметь работать по относительным путям. "Относительно чего", спросите вы? Относительно положения бинарников, конечно - то есть получается, что инклудники будут в ../include/c++/10.2.0, ../include/c++/10.2.0/x86_64-slackware-linux либы в ../lib64/gcc/x86_64-slackware-linux/10.2.0 и так далее. Но это всё работает, если я explicitly не задаю libdir и libexecdir, а если задам и заканчиваться они будут не на lib|lib64 и libexec, то судя по всему, я теряю способность gcc к "самоопределению" путей. Тем не менее, с гошкой, как минимум в случае бутстрапа гуглового go с путями не всё так просто. Поскольку сам golang умеет себя собирать и бутстрапить, то вариант со сборкой гошки через gccgo - это вроде как "запасной" случай, поэтому там всё рассчитано в основном на сборку golang-ом от гошки, который в системе (на линуксе) как правило (по иронии в том числе) является "вторым" go.

Собственно, таким образом можно оправдать магию с путями до библиотек. Но оправдание это довольно жиденькое, ибо почему не дать возможность выбрать использовать автомагию в путях или использовать абсолютные пути и не крутить мозги? В конце концов можно вообще при сборке задавать через переменные где что находится, уж ежли мы после сборки, например, передвинули какие-то каталоги куда-то. Почему не дать возможность вшить "запланированные" пути, по которым будут инклудники, а дополнительные пути не задавать переменными окружения (тогда вшитые будут fallnack-ом)? Вобщем, вопросов как всегда больше, чем ответов.

На сём, думаю, пока закончить охренительную историю, связанную с кастомным gcc. Если я буду возвращаться к теме gcc, то это уже будет в отдельных заметках.

Next Post