Drive your career as React Developer with Symphony Solutions!
×Закрыть

Swift на Windows: практические советы, проблемы и инструменты

Меня зовут Александр Смарусь, и я Product Engineering Lead в Readdle. Я работаю над приложением Spark — популярным почтовым клиентом с миллионами пользователей на iOS, macOS и Android. Сейчас наша команда занимается версией для Windows, чтобы Spark стал доступен и на ПК.

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

Экспериментировать со Swift на новой платформе мы начали больше года назад. У нас уже был успешный опыт со Spark для Android, в котором базовая часть кода общая с iOS/macOS благодаря Swift. Поэтому возможность расшириться на Windows выглядела крайне привлекательно.

Мы начали изучать текущее состояние проекта, и почти сразу стало понятно, что сообщество разработки Swift уже приложило немало усилий к портированию на Windows. Компилятор был вполне живой, в стандартных библиотеках реализованы необходимые слои. И хоть на тот момент там ещё очень многое работало не совсем корректно, разработка велась очень активно. Всё серьезно. Из того, что было необходимо именно нам, бОльшая часть либо функционировала, либо выглядела не очень сильно сломанной. «Починим», — подумали мы и взялись за работу. Забегая вперёд, скажу, что некоторые сторонние зависимости на C/C++ принесли нам больше головной боли, чем сам Swift. А после проработки основных концепций работа по переносу ядра Spark на Windows стала довольно простой и рутинной.

Итак, что у нас есть сейчас:

  • Инструментарий для сборки тулчейна и SDK Swift
  • 9 модулей на Swift (255 739 строк кода, 2 133 исходных файла)
  • 3 сторонних модуля на Swift
  • 1452 теста (на базе XCTest)
  • CI на Windows для отслеживания состояния тестов
  • Система сборки, отчасти основанная на собственных скриптах, отчасти на CMake

Тулчейн и SDK Swift

Начать работу со Swift на Windows достаточно легко. Просто следуйте инструкциям из проекта //swift/build. Но нам требовалось нечто большее. Поскольку поддержка Windows всё ещё в разработке, постоянно возникала необходимость исследовать разнообразные проблемы в стандартных библиотеках, а иногда и в самом компиляторе. Нужен был инструмент, позволяющий собирать собственную версию Swift. И пересобирать какую-нибудь отдельно взятую часть. И так много, много раз.

Мы начали с довольно простого сборочного скрипта, основанного на Azure Pipelines compnerd`а. Со временем он превратился в гибкий инструмент, который позволяет регулярно получать свежие сборки Swift, следить за их состоянием, применять патчи, проводить эксперименты и так далее.

Сторонние модули

Модули на чистом Swift, такие как CryptoSwift и OAuthSwift, заработали практически без усилий.

CryptoSwift — это сборник криптографических алгоритмов, реализованных на Swift. Популярная библиотека определённо не нуждается в представлении. Нам понадобилось сделать лишь несколько правок импортов системных библиотек, заменить вызовы arc4random_uniform на стандартный random(in:) и в одном месте заменить mlock на его Windows-аналог, VirtualLock. Насколько мне известно, чуть позже разработчики CryptoSwift убрали оттуда arc4random_uniform, поэтому текущая версия стала более совместимой с Windows «из коробки».

OAuthSwift. Ещё одна известная библиотека с говорящим названием. Наряду с arc4random_uniform и исправлением импортов пришлось написать замену недостающей функции CFStringConvertEncodingToIANACharSetName. Эта функция из библиотеки CoreFoundation, и она недоступна (не является частью SDK) на не-Darwin платформах. OAuthSwift также содержит немного UI-кода, который привязан к фреймворкам macOS/iOS. Но ядро Spark не работает с UI, поэтому тот код мы просто исключили из сборки.

Можно выделить несколько советов, которые помогут подготовить ваш код к сборке под Windows:

  • Замените arc4random_uniform на соответствующий вызов random(in:):
let rand = arc4random_uniform(length)    // До
let rand = UInt32.random(in: 0..<length) // После

Достаточно очевидная рекомендация, ведь эти функции появились ещё в Swift 4.2. Должна быть веская причина, чтобы всё ещё пользоваться старым способом.

  • Если в коде используются какие-либо функции из стандартной библиотеки C, добавьте импорт ucrt:
#if canImport(ucrt)
import ucrt
#endif
  • В библиотеке swift-corelibs-foundation разделили привычный Darwin-разработчикам Foundation на три части: Foundation, FoundationNetworking и FoundationXML. Поэтому, если ваш код использует, например, сетевой функционал, вам потребуется явным образом подключить FoundationNetworking:
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
  • Убедитесь, что у вас не используется функционал из CoreFoundation. Мы привыкли воспринимать эту библиотеку как должное, но на платформах, отличных от Darwin, CoreFoundation не доступен и считается не более, чем внутренней деталью реализации Foundation.

Система сборки

Сейчас Swift Package Manager почти готов к полноценному использованию на Windows, но год назад это было не так. Даже CMake, который уже долгое время остаётся самым гибким решением для Swift-проектов на этой платформе, поддерживал Swift только на базовом уровне. Но мы же инженеры, в конце концов, и наше любимое занятие — писать велосипеды. Можно сделать что-то своё. Swift — это современный компилятор, основанный на llvm. Передать ему горстку исходных файлов, указать желаемый результат (библиотека или исполняемый файл), при необходимости включить оптимизацию, подсказать пути к библиотекам, и готово!

Очень советуем посмотреть эти примеры с CMake, но для самых простых экспериментов подойдет и командная строка:

set SWIFTC=%SystemDrive%\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin\swiftc.exe
set SDKROOT=%SystemDrive%\Library\Developer\Platforms\Windows.platform\Developer\SDKs\Windows.sdk
set SWIFTFLAGS=-sdk %SDKROOT% -I %SDKROOT%\usr\lib\swift -L %SDKROOT%\usr\lib\swift\windows -O

%SWIFTC% -emit-executable %SWIFTFLAGS% HelloWorld.swift -o HelloWorld.exe

Конечно, полноценный продукт содержит множество компонентов и зависимостей. Даже примитивная система сборки должна как-то разрешать пути, обрабатывать связи между модулями, управлять ресурсами, позволять настраивать весь процесс. В итоге у нас получилась связка PowerShell-скриптов, которая со всем этим неплохо справляется. Но мы обязательно перейдем на Swift Package Manager, как только он будет готов. Это позволит унифицировать сборку на всех платформах, которые поддерживаются в Spark.

Swift и Node.js

Ещё одним испытанием было принятие решения о реализации пользовательского интерфейса. Решали довольно долго, взвешивали все за и против, проверяли некоторые перспективные идеи на практике. После всестороннего обсуждения остановились на Electron в качестве фронтенда для будущего Spark на Windows. Это означало, что нам требуется не просто собрать ядро Spark на Windows, но и сделать его загружаемым модулем для Node.js.

Аддон для Node.js на чистом Swift? Звучит интригующе, ведь в разработке таких вещей царит C и C++. Но всё оказалось на удивление просто. Разработку начали на macOS. Всё-таки пользоваться Xcode для отладки и написания кода очень удобно, а node-gyp, инструмент для сборки аддонов Node.js, может сгенерировать полноценный проект под эту IDE. Swift, в свою очередь, отлично работает с C-заголовками N-API.

Единственную хитрость пришлось провернуть, чтобы определить точку входа. Дело в том, что аддон должен правильно себя зарегистрировать путём определения специальной функции. Хотя написать её на Swift вполне реально, это не очень удобно. N-API предоставляет простой в использовании однострочный C-макрос, под которым скрывается несколько уровней макросной магии. Вот бы как-то начать в коде на C и оттуда безболезненно передать управление в Swift. К сожалению, если вызвать C из Swift не представляет каких-либо проблем, то обратная операция пока официально не поддерживается. Что совсем не значит, что это невозможно! У нас же есть атрибут @_cdecl! Судя по вот этому посту от Joe Groff, с ним связаны некоторые риски и ограничения, но у нас всего один маленький вызов. Что может пойти не так?

Шалость удалась, мы сделали аддон для Node.js... на macOS. А как же Windows? Как уже упоминалось, мы использовали node-gyp и Xcode. Очевидно, что на Windows так не получится. Там со сборкой лучше всего справляется CMake. Но node-gyp не просто собирает исходники, он делает ещё кое-что полезное: выкачивает необходимые для интеграции с Node файлы, подсказывает компилятору, где они лежат, включает кое-какие важные флаги. Вот если бы был какой-то инструмент, объединяющий в себе CMake и node-gyp... И такой есть! Cmake-js делает в точности то же, что и node-gyp, только на основе CMake. С ним всё встало на свои места. Аддон успешно собрался на Windows и запустился внутри Node.js и Electron. Немного жаль, что cmake-js не попал в поле зрения сразу, ведь тогда можно было бы не тратить время на альтернативы.

И это всё?

К глубокому сожалению, эта статья не способна вместить в себя всё интересное о Swift на Windows. Мы сфокусировались на том, что было сделано, но за рамками осталось очень много подробностей о том, как это было сделано. Планируем рассказать вам обо всём в отдельной истории.

Итого

С момента начала нашего путешествия Swift на Windows проделал огромный путь. В библиотеках исправили множество багов, компилятор перестал падать от вида необычного кода, Swift Package Manager уже в шаге от выпуска. Вы наверняка слышали, что недавно вышел Swift 5.3. Это первый релиз с официальной поддержкой Windows.

Сообщество разработки Swift приложило огромное количество усилий, чтобы воплотить в жизнь поддержку новой платформы. Отдельно хочется отметить вклад Saleem Abdulrasool, который не только пишет огромное количество кода, но и активно помогает новичкам, на практике показывает потенциал Swift и вдохновляет других на первые шаги в этой области.

Если вы думаете о расширении поддержки существующего кода на платформы кроме macOS/iOS, то Swift почти наверняка позволит это сделать. Если вы поддерживаете небольшую Swift-библиотеку, добавить поддержку Windows не составит большого труда.

LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Вообще, нужно чтобы люди начали открывать для себя Haxe. Пишете core логику на нем, конвертите в Objective-C++(.mm который) и юзаете в нативном ObjC\Swift проекте. Конвертите в JS -> юзаете без бубнов под нодой\браузером.
Можно даже одним махом и под WPF его завести, т.к. Haxe умеет и в шарп.
Весьма годная весчь для кроссплатформенных решений )

а для UI чего-то подобного не завалялось?

Именно на хаксе есть такая вот штука: haxeui.org
Насколько оно рабочее — хз, не пробовал.

А вообще, с хаксом можно использовать любые нативные библиотеки, так что привязать какой-то сторонний UI — можно. Так, я его когда-то с Qt использовал. Другой вопрос, что это потребует некоторой дополнительной работы.

А еще, ниже про флаттер написали... И кстати да, он явно лучше чем электрон ) Однако, десктопы там еще в альфа\бета состоянии.

Flutter вже є;))

Сейчас наша команда занимается версией для Windows, чтобы Spark стал доступен и на ПК

чем ваша поделка лучше Outlook?

После всестороннего обсуждения остановились на Electron в качестве фронтенда для будущего Spark на Windows

OMG вот это просто комбо.

Всё-таки пользоваться Xcode для отладки и написания кода очень удобно

сильное утверждение

В чем смысл начинать разработку на сырой платформе, если есть родные инструменты? WPF + C# дадут очень и очень большую фору SWIFT + Electron. К тому же UI будет нативный, а не рисованный в бразуре (которым Электрон и является).

Начинать не призываем, это было бы чересчур. Но если у кого-то, как у нас, есть готовый базовый код под Darwin, то возможностей по переносу на win стало определённо больше. UI-фреймворк на любой вкус можно взять. С WPF вот, кстати, ещё не видел примеров. Любопытно было бы посмотреть.

Даже МС «нарисовали» свою VSCode на Electron. И что-то не вижу аналогичный продукт на wpf, который дал бы фору VSCode. Смотря на портфолио приложений на Electron, язык не поворачивается назвать эту платформу сырой. С таким же успехом можно назвать сырой и node.js.
Напомните пару популярных приложений на Avalonia? Все так что касается кроссплатформенных UI приложений, то сырыми еще являются как раз .нет технологии. Хотя, безусловно, wpf очень хороший инструмент для разработки под windows.

Вы статью внимательно читали? Им нужно было приложение под Windows. Для этого они взяли связку Swift + Electron. Вы реально считаете, что Swift на windows зрелая платформа? Ну а node.js я бы в приличном обществе даже вспомнить бы не стал :) И при чем тут Avalonia? Это отдельный от WPF продукт.

у них огромная кодовая база на свифте. Поэтому они натянули ужа на ежа и запили свой велосипед для запуска на вендах. Им нужен был только GUI.

у них огромная кодовая база на свифте

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

Все логично, ведь у них изначально были аппы только для яблочной платформы

У меня вопрос вызвало утверждение, что Electron это сырая платформа и я привел аргумент в пользу ее зрелости. Ну, а в том, что Swift для Win это хорошее решене — есть сомнения :) Но уж если смогли прикрутить в таком виде с минимальными затратами — респект!

а какие альтернативы для UI были кроме электрона? И как тогда будет обстоять дела с маковским клиентом? тоже на электрон или останется нативным? Если второе, то почему не решили сделать, как для андроида (гуй родной, а бизнес логика шареная на свифте)

Рассматривали полностью нативные варианты на WPF, UWP. И даже Win32. Свифт неплохо там справляется, и это была бы очень интересная задача для разработки. Смотрели и более универсальные решения типа QT и wxWidgets. Кажется, это даже не всё. Точно не всё. Остановились на электроне по ряду причин. Тут, пожалуй, хватит на ещё целую статью, довольно холиварную. Кроссплатформенность — жирный плюс в нашем случае. Хотелось и хочется оставить себе возможность такого манёвра. Сыграло роль и то, что UI-часть довольно тонка, её реализацию можно менять. Нативный макосный клиент пока не бросаем. Такое возможно, но мне трудно тут озвучить конкретные планы, это ведь далеко не всегда технический вопрос.

А что если посмотресть в сторону Avalonia? Тот же WPF , но кроссплатформ и еще есть пару фич:
— css подобные стили
— reactiveUI

c таким успехом можно было и поковырять это зло github.com/Microsoft/WinObjC

Остановились на электроне по ряду причин. Тут, пожалуй, хватит на ещё целую статью, довольно холиварную.

а я бы почитал

Подписаться на комментарии