Пробуем запустить свой проект на Qt WebAssembly за час.

Да, столько времени у меня занял запуск простой демки на своем pet-project (другое дело, что демка вышла бесполезной, но об этом позже). Имеем: linux со всякими стандартными штуками для разработки и небольшой проект для доступа к БД, написанный на Qt (проект собирается как на CMake, так и на QMake, не знаю зачем так). Делаем все максимально лениво, поэтому вместо сборки чего-то руками, тупо скачаем Qt for WebAssembly c помощью online-установщика (поди вспомни еще свой аккаунт в Qt). Я взял последний Qt 5.15, ставим на скачивание, идем читать доку.

Сразу же напрягло отсутствие Widgets в списке поддерживаемых модулей на wiki-странице, но и в списке не поддерживаемых я тоже его не нашел, так что будем надеяться, что оно заработает (спойлер: оно заработает). Список поддерживаемых модулей:
* qtbase
* qtdeclarative
* qtquickcontrols2
* qtwebsockets
* qtsvg
* qtcharts
* qtmqtt

На той же вики я прочитал, что для Qt 5.15 лучше всего подходит Emscripten SDK версии 1.39.8. Что ж, его и возьмем =)
Вскользь прочитал, что там какая-то беда с многопоточностью, но потоки я завести еще не успел (лень-матушка), с довольной ухмылкой идем дальше.

Давайте поставим уже этот Emscripten SDK, чем бы он ни был:

$ git clone https://github.com/emscripten-core/emsdk.git
$ cd emsdk/
$ ./emsdk install 1.39.8
$ ./emsdk activate 1.39.8

Видим такую подсказку в выводе:

- To conveniently access emsdk tools from the command line,
  consider adding the following directories to your PATH:
    /home/ieo/Documents/projects/emsdk
    /home/ieo/Documents/projects/emsdk/node/12.18.1_64bit/bin
    /home/ieo/Documents/projects/emsdk/upstream/emscripten
- This can be done for the current shell by running:
    source "/home/ieo/Documents/projects/emsdk/emsdk_env.sh"
- Configure emsdk in your bash profile by running:
    echo 'source "/home/ieo/Documents/projects/emsdk/emsdk_env.sh"' >> $HOME/.bash_profile
    

Выберем первый способ (хватит же на попробовать):

$ source ./emsdk_env.sh

Проверим, что все встало:

$ em++ --version

emcc (Emscripten gcc/clang-like replacement) 1.39.8 (commit 60e3a51c7ba8bb96a47d06280b0b1f8ef711608b)

Видим нужную нам версию в выводе, как-то даже слишком просто, чувство незаслуженной гордости требует двигать дальше.

Давайте сперва проверим, что Qt вообще запустится в браузере (ага, онлайн демку с их сайта то нельзя запустить). Отрубаем в проекте через опции сборки все, кроме UI на Qt (хорошо, что эти опции есть), что также исключит из сборки кучу библиотек, с ними разберемся позже (нет).
Дока по emscripten предлагает использовать ./emconfigure и ./emmake вместо сами поняли чего, однако Qt предлагает вызывать qmake из папочки, куда мы поставили Qt WebAssembly. Разбираться со сборкой через CMake что-то лень, да и отпуск у меня, QMake так QMake =)

$ cd .. && mkdir build && cd build
$ ~/Qt/5.15.0/wasm_32/bin/qmake ../project_name

Собираем:

$ make -j4

Собралось! Запускаем встроенный в emsdk простецкий веб-сервер, который засунули в папочку emsdk, указываем ему путь до проекта:

$ emrun --no_browser --port 8080 ~/Documents/projects/project_name/project_name.html

Открываем в FireFox ссылочку: http://localhost:8080/project_name.html (вы можете открыть демку здесь)

It works! (помните, так apache говорил?) Скрин диалога из лисицы и нативное окно для сравнения:

Qt Widgets WebAssembly
Qt Widgets Native

Как видим, работают стандартные виджеты QDialog, QComboBox, QSpinBox и тд и все это без изменения Qt кода вообще (хотя чего-то не сохраняется в QSettings).

Давайте поглядим что получилось на выходе по размеру (жЫр, но я ждал под сотню метров):

2,8K project_name.html
298K project_name.js
13M  project_name.wasm
22K  qtloader.js

Раскупориваем либу для доступа к БД (-lmysqlclient) и ... что-то не собирается. Значит, настало время читать инструкции.

В документации я прочитал про следующие ограничения/особенности WASM:

0. Библиотеки.

Нужно собрать либы самому в bitcode. Нельзя просто так взять и войти в Мордор готовую либу (*.a. *.so) с вашей нативной системы (ну, логично), нужно собрать ее в байткод, либо воспользоваться портом (если вам повезло и он есть).

Спортированные либы, кт нам понадобятся (нет):

  • libc/libc++
  • zlib

Список всех портов https://github.com/emscripten-ports

Еще я нашел такие (интересные для меня) порты:

1. Динамическое связывание

Динамические библиотеки (.so) поддерживаются (конечно же собранные в байткод!), но на финальном этапе сборки при генерации JS они будут слинкованы как статические. Более того, компилятор emcc игнорирует комманды по линковке динамических либ при линковке биткода (т.е. не на финальной стадии), чтобы избежать злосчастной проблемы с дубликатами символов. (на этом моменте я с ужасом вспоминаю, как когда-то давно на iOS из-за ограничений App Store приходилось статически линковать кучу библиотек и однажды возник конфликт BoringSSL и OpenSSL).

2. Неподдержка TCP

И тут внезапно (нет) выяснилось, что браузеры не поддерживают прямой доступ к TCP сокетам, нужный для работы mysqlclient, да и других сетевых клиентов для БД, да и много для чего еще. Emscripten пытается решить эту проблему эмулированием работы Posix Sockets API через WebSocket (они-то конечно же в браузере работают), но это означает, что на стороне сервера нужно делать мост в обратном направлении (делать из WebSocket TCP), что конечно же крайне непоходящее решение.

Можно конечно же взять и попробовать chrome.sockets.tcp и какой-нибудь драйвер поверх него, но это доступно только в расширениях Chrome и то со специальными пермишнами (FIXME). Облом! UPD: уже после заметил, что "сырые" сокеты прямо в браузере сейчас под обсуждением.

Выводы

Итак, Qt в браузере через WebAssembly заработало, но сделать полезное приложение у меня не получилось из-за ограничений браузера. Веб-разработчики героически пытаются писать полезные приложения, но нативщина никуда не делась и надеюсь не денется.