software-development
August 12

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.

И сейчас мы им всем будем подрывать пердаки — сборкой всего тулчейна из исходников еще и на неподдерживаемой ОС.

Некоторым даже понравится.

Шатая устои

Разумеется я не первый и не единственный такой быстрый умный и хитрый, кто решил портировать и запустить новомодную штуку на тостер там где ей не место — как только крупная корпорация выкладывает что-то в опенсорс, немедленно начинается специальная олимпиада:

кто первым запустит это на Linux затем на Haiku *BSD

Занимаются таким серьезные бородатые дяди, владеющие «тайным ниндзюцу» и набором из 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.

  1. 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).
  2. The libc++ module.modulemap requires a small tweak to fix the std.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

После этого должна нормально отрабатывать компиляция:

А вот REPL (динамическое выполнение) оказался сломан: