Новый, модный и красивый язык разработки от Apple прямо на ваших глазах будет с особым цинизмом пропатчен, собран из исходников и запущен на FreeBSD. Снова.

Король разработки от Apple

Немного матчасти для тех кто не знает:

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 для продукции Apple, который затем выложили в открытый доступ и приспособили под более широкое применение.

На момент написания статьи существуют официальные сборки Swift для MacOS, Windows и всех основных дистрибутивов Linux.

Ни одна из *BSD систем (кроме старой шутки про корни MacOS) разумеется не поддерживается, но патчи для этих систем каким‑то невероятным образом постоянно попадают в апстрим.

Код на Swift выглядит как-то так:

import Figlet

@main
struct FigletTool {
  static func main() {
      Figlet.say("Привет Хабр!")
  }
}

Язык продвигается в качестве замены «монструозному» Objective-C для всех видов прикладной разработки под продукцию Apple, поэтому например все современные iOS разработчики пишут код как раз на Swift.

Swift и FreeBSD

Еще в далеком 2016м году был сделан порт, который поддерживался целых три года силами одного человека, но ввиду отсутствия существенного интереса сообщества и огромных темпов развития самого Swift — порт в итоге был заброшен:

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

Копаясь в исходниках Swift и скриптах сборки я нашел упоминания и OpenBSD и Haiku и даже Android — апстрим видимо принимает абсолютно любую дичь.

Но насколько оно все работоспособно на практике, думаю можно самостоятельно оценить по описанным шагам ниже.

Как уже отмечал выше, тулчейн Swift развивается очень динамично, поэтому даже минорные версии (5.7 — 5.8) сильно отличаются, например разница между версией 5.10 и 5.8 примерно в сотне новых исходных файлов, что очень много для кода на C++ на котором в основном все и написано.

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

Собирай и властвуй

За основу я взял вот эту ныне устаревшую инструкцию, шаги которой пришлось менять для новой версии 5.10 и запуска на FreeBSD 14.1 вместо авторской 13й .

Первый и самый простой шаг это установка необходимых пакетов:

pkg install bash cmake e2fsprogs-libuuid git ninja python3

Тут все просто и очевидно.

Но следующим же шагом необходимо сотворить невероятную дичь — немного изменить системные заголовки:

The libc++ headers in FreeBSD 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)

которое никогда не отработает и таким образом опция _LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR будет отключена.

/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

Думаю по расположению файлов вы уже догадались, что правки придется вносить из-под root.

Следующим шагом забираем исходники:

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

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

Итого

Вне экосистемы Apple, Swift выглядит и работает откровенно не очень — сборки тулчейна получаются огромные из‑за добавляемого внутрь LLVM, поддержка языка дальше подсветки синтаксиса есть фактически только в официальном XCode, работающем под MacOS.

Текущее состояние открытой версии Swift напоминает Rust или Haskell:

бурное развитие с забиванием на обратную совместимость и постоянно ломающееся окружение

К чему это приведет покажет время, но на сегодняшний день я бы не рекомендовал вести сколь‑нибудь серьезную разработку на Swift за пределами экосистемы Apple — слишком много самых невероятных ошибок порождает использование LLVM «под капотом».

Комментарии (6)


  1. volkahowl
    22.08.2024 06:56

    SwiftUI и эмуляторов нет получается?
    Плюс сейчас публикация возможна вроде только через Apple Developer — App Store Connect. То есть получается без Xcode все равно не обойтись?


    1. alex0x08 Автор
      22.08.2024 06:56
      +1

      Все описанное только в MacOS, Swift вне MacOS это просто один голый язык, без всего.


  1. slonopotamus
    22.08.2024 06:56

    if defined(__FreeBSD__) && (0)

    Зачем так делать? Это никакой здравомыслящий апстрим к себе не примет. Почему не удалить весь блок #if?


    1. alex0x08 Автор
      22.08.2024 06:56

      Потому что это системный заголовок самой FreeBSD, который не имеет отношения к Swift.


  1. sappience
    22.08.2024 06:56
    +1

    Оффтопик

    Обои рабочего стола на заглавной картинке это, как я понимаю, аллюзия на Данилу Багрова из фильма "Брат"?


    1. alex0x08 Автор
      22.08.2024 06:56

      Ага, нейросеть попросили нарисовать Эльзу в стиле первого "Брата", получилось неплохо :)