Swift на FreeBSD
Потрошим новомодный язык «из мира моды, гламура и космической педерастии» для запуска в мрачных реалиях BSD.
Король гламура
Swift is a general-purpose programming language that’s approachable for newcomers and powerful for experts.It is fast, modern, safe, and a joy to write.
Словом это такой модный язык программирования, в основном для пассивных любителей продукции Apple.
import Figlet @main struct FigletTool { static func main() { Figlet.say("Привет питушок!") } }
Продвигается в качестве замены «монструозному» Objective-C для всех видов прикладной разработки для продукции Apple, поэтому все современные iOS разработчики пишут код как раз на Swift.
И сейчас мы им всем будем подрывать пердаки — сборкой всего тулчейна из исходников еще и на неподдерживаемой ОС.
Шатая устои
Разумеется я не первый и не единственный такой быстрый умный и хитрый, кто решил портировать и запустить новомодную штуку на тостер там где ей не место — как только крупная корпорация выкладывает что-то в опенсорс, немедленно начинается специальная олимпиада:
Занимаются таким серьезные бородатые дяди, владеющие «тайным ниндзюцу» и набором из 1001 хака для запуска «здесь и сейчас» любой дичи в любой жопе вселенной.
А дальше начинается скучная обыденность:
разработка открытой штуки едет себе дальше а разово произведенное портирование будучи описанным в статье быстро теряет актуальность и забывается.
Потому что разовая работа для Тик-Тока из серии «запустить любой ценой» имеет мало общего с постоянной поддержкой апстрима — сильно другие затраты и усилия.
Что-то такое и произошло с портом Swift для FreeBSD: еще в 2016 м году был сделан порт, который поддерживался три года, но ввиду отсутствия #банутых интереса сообщества порт был заброшен:
Копаясь в исходниках Swift и скриптах сборки можно найти упоминания и OpenBSD и Haiku и даже Android — апстрим видимо принимает абсолютно все.
Но насколько оно все работоспособно на практике, думаю можно оценить по шагам ниже.
Тулчейн Swift развивается очень динамично, даже минорные версии (5.7 — 5.8) сильно отличаются, версия 5.10 отличается от 5.8 на ~100 исходных файлов — что сильно дох#я для C/C++ на которых оно все написано.
Для поддержки всего это цирка в апстриме нужно немеряно сил и времени, которого у меня разумеется нет, поэтому была взята одна актуальная версия, на которой и производились все описанные шаги.
Инструкция ниже написана для последней стабильной версии на момент написания — 5.10, с некоторыми вставками для особенностей сборки 5.7 и 5.8 версий.
Собирай и властвуй
За основу я взял вот эту инструкцию, с рядом изменений, все манипуляции производились на FreeBSD 14.1.
Первым делом ставим необходимые пакеты:
pkg install bash cmake e2fsprogs-libuuid git ninja python3
Следующим шагом необходимо немного изменить системные заголовки:
The libc++ headers in FreeBSD 13.1-RELEASE require slight modifications in order to build Swift.
- The implementation of
std::pair
must be modified such that its copy constructor is trivial (as the C++ standard requires). For historical reasons, this is not currently the case in FreeBSD 13.1 (though there is ongoing work to fix it for FreeBSD 14.0). - The libc++
module.modulemap
requires a small tweak to fix thestd.depr.stdint_h
module. See this bug report for details.
Нужно исправить два заголовочных файла:
/usr/include/c++/v1/__config
--- __config +++ __config @@ -127,7 +127,7 @@ # endif // Feature macros for disabling pre ABI v1 features. All of these options // are deprecated. -# if defined(__FreeBSD__) +# if defined(__FreeBSD__) && (0) # define _LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR # endif #endif
Номера строк не совпадают, поскольку патч для 13й FreeBSD, поэтому применить патч не получится, но суть думаю понятна и так.
if defined(__FreeBSD__)
if defined(__FreeBSD__) && (0)
которое никогда не отработает и таким образом данная опция будет отключена.
/usr/include/c++/v1/module.modulemap
+ module stdint_h { + header "stdint.h" + export * + // FIXME: This module only exists on OS X and for some reason the + // wildcard above doesn't export it. + export Darwin.C.stdint + }
Несмотря на + в начале строки, такой блок уже есть и все что вам нужно это его найти и добавить в конец строку:
export Darwin.C.stdint
Следующим шагом забираем исходники:
mkdir swift && cd swift git clone -b release/5.10 --depth=1 https://github.com/apple/swift git clone -b swift/release/5.10 --depth=1 https://github.com/apple/llvm-project git clone -b release/5.10 --depth=1 https://github.com/apple/swift-cmark cmark git clone -b release/5.10 --depth=1 https://github.com/apple/swift-syntax git clone -b swift/release/5.10 --depth=1 https://github.com/apple/swift-experimental-string-processing.git
Дальше необходимо создать скрипт сборки, моя версия отличается от оригинальной:
#!/bin/sh SRCROOT="`pwd`/.." cmake \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_SHARED_LINKER_FLAGS=-Wl,--undefined-version \ -DLLVM_ENABLE_PROJECTS=clang \ -DLLVM_TARGETS_TO_BUILD=X86 \ -DLLVM_EXTERNAL_PROJECTS="cmark;swift" \ -DLLVM_EXTERNAL_CMARK_SOURCE_DIR="${SRCROOT}/cmark" \ -DLLVM_EXTERNAL_SWIFT_SOURCE_DIR="${SRCROOT}/swift" \ -DSWIFT_PATH_TO_SWIFT_SYNTAX_SOURCE="${SRCROOT}/swift-syntax" \ -DSWIFT_ENABLE_DISPATCH=OFF \ -DSWIFT_IMPLICIT_CONCURRENCY_IMPORT=OFF \ -DSWIFT_USE_LINKER=ld \ -DSWIFT_BUILD_STATIC_STDLIB=ON \ -DBOOTSTRAPPING_MODE=BOOTSTRAPPING \ -DSWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING=ON \ -DSWIFT_PATH_TO_STRING_PROCESSING_SOURCE="${SRCROOT}/swift-experimental-string-processing" \ -G Ninja \ ../llvm-project/llvm
Скрипт необходимо расположить на уровень ниже каталога build:
Проблема с линковщиком
Достаточно долго я вообще не мог собрать тулчейн даже до первой стадии, ломались как устаревшие 5.7 и 5.8 версии так и новая 5.10.
Путем долгого гугления нашелся вот такой пост:
In llvm17, the linker option --no-allow-shlib-undefined became default. If there are any symbol not present in the library specified in the version script, the linker exits with error.
Для неграмотных: в LLVM поменялось поведение по-умолчанию, ключ который был когда-то опциональным стал внезапно обязательным — т. е. теперь эта логика применяется по-умолчанию.
Для отключения нужен вот этот ключ:
LDFLAGS+= -Wl,--undefined-version
Что в условиях кастомного скрипта сборки и cmake превратилось в:
-DCMAKE_SHARED_LINKER_FLAGS=-Wl,--undefined-version \
Патчим скрипты cmake
Но описанного выше оказалось недостаточно, нужен еще один интересный патч, без которого сборка падает на второй стадии — те когда собранный компилятор Swift собирает свое окружение уже на Swift:
Без этой волшебной строчки сборка будет падать с настолько фееричными ошибками, что вы не найдете их упоминания ни в одном поисковике.
if(SWIFT_HOST_VARIANT_SDK MATCHES "LINUX|ANDROID|OPENBSD|FREEBSD") target_link_options(${target} PRIVATE "SHELL:-Xlinker -z -Xlinker nostart-stop-gc") endif() endif()
Этот блок нужно вставить в скрипт swift/cmake/modules/AddSwift.cmake, примерное место на скриншоте ниже:
После всех этих правок можно наконец пробовать запускать сборку:
mkdir build && cd build
сначала выполняем скрипт configure.sh:
../configure.sh
ninja
Если сборка пройдет без ошибок, можно переходить к установке собранной версии:
env DESTDIR=/opt/app/swift-sdk ninja install-compiler install-autolink-driver install-stdlib install-sdk-overlay
В результате в каталоге /opt/app/swift-sdk будет структура аналогичная бинарной сборке окружения Swift, поставляемой официально — с сайта Apple.
Запуск
К сожалению у компилятора Swift есть проблема с поиском своих библиотек под FreeBSD, ему надо немного помочь с помощью LD_LIBRARY_PATH:
LD_LIBRARY_PATH=/opt/app/swift-sdk/usr/local/lib/swift/freebsd