Всем привет! Меня зовут Шамиль, я ведущий инженер-разработчик в
КРОК. Помимо всего прочего мы в компании занимаемся ещё и
разработкой мобильных приложений для операционной системы Аврора,
есть даже центр компетенций по ней.
Для промышленной разработки мы, конечно же, пока используем связку
C++ и QML, но однажды подсев на "ржавую" иглу Rust, я не мог не
попробовать применить свой любимый язык программирования для
написания мобильных приложений. В этой статье я опишу эксперимент
по написанию простейшего приложения на Rust, предназначенного для
запуска на мобильном устройстве под управлением вышеупомянутой ОС.
Сразу оговорюсь, что легких путей я не искал эксперименты проводил
на сертифицированной версии Авроры, которая добавила огонька в этот
процесс. Но, как говорится, только защищённая ОС, только
хардкор.
Пара выходных у меня ушла только на то, чтобы запустить минимальное консольное приложение (речь о нём пойдёт в первой части), ещё пара дней на эксперименты с графическим интерфейсом, выбор оптимального подхода и запуск приложения с GUI (этому посвящена вторая часть повествования). В итоге получился минимальный скелет мобильного приложения, готового к сборке и запуску, на который при желании уже можно наращивать мясо.
Готовим окружение
Итак, работа будет вестись из-под Ubuntu Linux с уже установленным Rust. В качестве подопытного планшета выступает Aquarius NS220 с сертифицированной ОС Аврора последней (на момент написания статьи) версии 3.2.2 с включённым режимом разработчика, который обеспечивает связь по SSH, а также привилегированный доступ с правами суперпользователя.
Первым делом добавим средства кросскомпилятора для архитектуры ARM, так как на целевом планшете стоит именно такой процессор.
sudo apt install -y g++-arm-linux-gnueabihfrustup target add armv7-unknown-linux-gnueabihf
В сертифицированной версии ОС Аврора не разрешается запускать
неподписанные приложения. Подписывать надо проприетарной утилитой
из состава Aurora Certified SDK под названием
ompcert-cli
, которая поддерживает на входе только
пакет в формате RPM. Поэтому сразу установим замечательную утилиту
cargo-rpm,
которая возьмёт на себя всю рутинную работу по упаковке приложения
в RPM-пакет:
cargo install cargo-rpm
Саму процедуру подписывания RPM-пакета я описывать не буду, она неплохо документирована в справочных материалах ОС Аврора.
Aurora SDK можно скачать с сайта производителя.
Часть 1. Hello. World
TL;DR Исходники проекта можно найти в репозитории на Гитхабе.
Создаем минимальный проект
Создаём пустое приложение на Rust:
cargo new aurora-rust-helloworld
Пытаемся сгенерировать .spec файл для RPM-пакета:
cargo rpm init
Получаем ошибки, что не хватает некоторых полей в
Cargo.toml
, добавляем их:
Cargo.toml:
[package]name = "aurora-rust-helloworld"version = "0.1.0"authors = ["Shamil Yakupov <syakupov@croc.ru>"]edition = "2018"description = "Rust example for Aurora OS"license = "MIT"
Закидываем в папку .cargo
конфигурационный файл с
указанием правильного линкера для компоновки исполняемого файла под
архитектуру ARM:
.cargo/config.toml:
[target.armv7-unknown-linux-gnueabihf]linker = "arm-linux-gnueabihf-gcc"
Собираем RPM-пакет:
cargo rpm initcargo rpm build -v --target=armv7-unknown-linux-gnueabihf
Всё собралось, забираем RPM из папки
target/armv7-unknown-linux-gnueabihf/release/rpmbuild/RPMS/armv7hl
,
подписываем его, копируем на планшет и пытаемся установить:
$ devel-suPassword:# pkcon install-local ./aurora-rust-helloworld-0.1.0-1.armv7hl.rpm
Получаем ошибку:
Fatal error: nothing provides libc.so.6(GLIBC_2.32) needed by aurora-rust-helloworld-0.1.0-1.armv7hl
Смотрим версию glibc
на устройстве, и понимаем, что
она явно ниже той, что нам требуется:
$ ldd --versionldd (GNU libc) 2.28
Что ж, тогда попробуем забрать нужные библиотеки с планшета,
закинуть их в директорию lib
и слинковать с ними. Для
верности будем пользоваться линкером, входящим в состав Aurora SDK,
который закинем в директорию bin
. Для начала
посмотрим, какие именно библиотеки нам нужны. Меняем содержимое
.cargo/config.toml
:
[target.armv7-unknown-linux-gnueabihf]rustflags = ["-C", "link-args=-L lib"]linker = "bin/armv7hl-meego-linux-gnueabi-ld"
Пробуем собрать:
cargo build --release --target=armv7-unknown-linux-gnueabihf
Получаем ошибки:
aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lgcc_saurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lutilaurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lrtaurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lpthreadaurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lmaurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -ldlaurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lcaurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lutil
Копируем недостающие библиотеки с планшета:
mkdir -p libscp nemo@192.168.2.15:/usr/lib/libgcc_s.so ./libscp nemo@192.168.2.15:/usr/lib/libutil.so ./libscp nemo@192.168.2.15:/usr/lib/librt.so ./libscp nemo@192.168.2.15:/usr/lib/libpthread.so ./libscp nemo@192.168.2.15:/usr/lib/libm.so ./libscp nemo@192.168.2.15:/usr/lib/libdl.so ./libscp nemo@192.168.2.15:/usr/lib/libc.so ./libscp nemo@192.168.2.15:/usr/lib/libutil.so ./lib
Снова пытаемся собрать, получаем новую порцию ошибок:
aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: skipping incompatible /lib/libc.so.6 when searching for /lib/libc.so.6aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find /lib/libc.so.6aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: skipping incompatible /usr/lib/libc_nonshared.a when searching for /usr/lib/libc_nonshared.aaurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find /usr/lib/libc_nonshared.aaurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find /lib/ld-linux-armhf.so.3
Копируем недостающее:
scp nemo@192.168.2.15:/lib/libc.so.6 ./libscp nemo@192.168.2.15:/usr/lib/libc_nonshared.a ./libscp nemo@192.168.2.15:/lib/ld-linux-armhf.so.3 ./lib
Ещё надо подредактировать файл libc.so
(который
является фактически скриптом линкера), чтобы дать понять линкеру,
где надо искать библиотеки:
lib/libc.so:
/* GNU ld script Use the shared library, but some functions are only in the static library, so try that secondarily. */OUTPUT_FORMAT(elf32-littlearm)GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-armhf.so.3 ) )
Запускаем сборку RPM-пакета, копируем, пытаемся установить.
Здесь позволю себе небольшое лирическое отступление. Перед установкой RPM-пакета на сертифицированной версии ОС Аврора запускается RPM-валидатор утилита, которая проверяет, насколько собранный пакет удовлетворяет требованиям системы. И до тех пор, пока пакет не пройдёт валидацию, установить приложение не получится. Безопасность превыше всего.
Итак, мы видим, что валидатор выдал несколько ошибок:
вот таких
Desktop file============ERROR [/usr/share/applications/aurora-rust-helloworld.desktop] File is missing - cannot validate .desktop filePaths=====WARNING [/usr/share/aurora-rust-helloworld] Directory not foundERROR [/usr/share/applications/aurora-rust-helloworld.desktop] File not foundWARNING [/usr/share/icons/hicolor/86x86/apps/aurora-rust-helloworld.png] File not foundWARNING [/usr/share/icons/hicolor/108x108/apps/aurora-rust-helloworld.png] File not foundWARNING [/usr/share/icons/hicolor/128x128/apps/aurora-rust-helloworld.png] File not foundWARNING [/usr/share/icons/hicolor/172x172/apps/aurora-rust-helloworld.png] File not foundERROR [/usr/share/icons/hicolor/[0-9x]{5,9}/apps/aurora-rust-helloworld.png] No icons found! RPM must contain at least one icon, see: https://community.omprussia.ru/doc/software_development/guidelines/rpm_requirementsLibraries=========ERROR [/usr/bin/aurora-rust-helloworld] Cannot link to shared library: libutil.so.1Symbols=======ERROR [/usr/bin/aurora-rust-helloworld] Binary does not link to 9__libc_start_main@GLIBC_2.4.Requires========ERROR [libutil.so.1] Cannot require shared library: 'libutil.so.1'
Что ж, будем бороться с каждой ошибкой по списку.
Добавляем недостающие файлы
Добавим иконки и ярлык (файл с расширением desktop) в директорию
.rpm
.
.rpm/aurora-rust-helloworld.desktop:
[Desktop Entry]Type=ApplicationX-Nemo-Application-Type=silica-qt5Icon=aurora-rust-helloworldExec=aurora-rust-helloworldName=Rust Hello-World
Для того, чтобы копировать нужные файлы на этапе сборки
RPM-пакета, сделаем простенький Makefile
:
.PHONY: all clean install prepare release rpmall:@cargo build --target=armv7-unknown-linux-gnueabihfclean:@rm -rvf targetinstall:@scp ./target/armv7-unknown-linux-gnueabihf/release/aurora-rust-helloworld nemo@192.168.2.15:/home/nemo/@scp ./target/armv7-unknown-linux-gnueabihf/release/rpmbuild/RPMS/armv7hl/*.rpm nemo@192.168.2.15:/home/nemo/prepare:@rustup target add armv7-unknown-linux-gnueabihf@cargo install cargo-rpmrelease:@cargo build --release --target=armv7-unknown-linux-gnueabihfrpm:@mkdir -p ./target/armv7-unknown-linux-gnueabihf/release/rpmbuild/SOURCES@cp -vf .rpm/aurora-rust-helloworld.desktop ./target/armv7-unknown-linux-gnueabihf/release/rpmbuild/SOURCES@cp -rvf .rpm/icons ./target/armv7-unknown-linux-gnueabihf/release/rpmbuild/SOURCES@cargo rpm build -v --target=armv7-unknown-linux-gnueabihf
Обновим aurora-rust-helloworld.spec
:
%define __spec_install_post %{nil}%define __os_install_post %{_dbpath}/brp-compress%define debug_package %{nil}Name: aurora-rust-helloworldSummary: Rust example for Aurora OSVersion: @@VERSION@@Release: @@RELEASE@@%{?dist}License: MITGroup: Applications/SystemSource0: %{name}-%{version}.tar.gzSource1: %{name}.desktopSource2: iconsBuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root%description%{summary}%prep%setup -q%installrm -rf %{buildroot}mkdir -p %{buildroot}cp -a * %{buildroot}mkdir -p %{buildroot}%{_datadir}/applicationscp -a %{SOURCE1} %{buildroot}%{_datadir}/applicationsmkdir -p %{buildroot}%{_datadir}/icons/hicolor/86x86/appsmkdir -p %{buildroot}%{_datadir}/icons/hicolor/108x108/appsmkdir -p %{buildroot}%{_datadir}/icons/hicolor/128x128/appsmkdir -p %{buildroot}%{_datadir}/icons/hicolor/172x172/appscp -a %{SOURCE2}/86x86/%{name}.png %{buildroot}%{_datadir}/icons/hicolor/86x86/appscp -a %{SOURCE2}/108x108/%{name}.png %{buildroot}%{_datadir}/icons/hicolor/108x108/appscp -a %{SOURCE2}/128x128/%{name}.png %{buildroot}%{_datadir}/icons/hicolor/128x128/appscp -a %{SOURCE2}/172x172/%{name}.png %{buildroot}%{_datadir}/icons/hicolor/172x172/apps%cleanrm -rf %{buildroot}%files%defattr(-,root,root,-)%{_bindir}/*%{_datadir}/applications/%{name}.desktop%{_datadir}/icons/hicolor/*/apps/%{name}.png
Для сборки пакета теперь достаточно выполнить:
make rpm
Убираем зависимость от libutil.so
Я не нашёл способа, как убедить cargo
при вызове
команды линкера не передавать ключ -lutil
. Вместо
этого я решил создать скрипт-заглушку для линкера с какой-нибудь
незначащей командой.
lib/libutil.so:
/* GNU ld script Dummy script to avoid dependency on libutil.so */ASSERT(1, "Unreachable")
Да, не самый хороший способ. Если кто-нибудь знает, как сделать лучше, делитесь в комментариях.
Добавляем символ __libc_start_main
Перепробовав несколько способов, остановился на том, чтобы
добавить при линковке стандартный объектный файл
crt1.o
. Копируем его с планшета:
scp nemo@192.168.2.15:/usr/lib/crt1.o ./lib
И добавляем в команды линкера:
.cargo/config.toml:
[target.armv7-unknown-linux-gnueabihf]rustflags = ["-C", "link-args=-L lib lib/crt1.o"]linker = "bin/armv7hl-meego-linux-gnueabi-ld"
Однако при попытке сборки получаем ошибки:
undefined reference to `__libc_csu_fini'undefined reference to `__libc_csu_init'
Добавим заглушки этих функций в main.rs
:
src/main.rs:
#[no_mangle]pub extern "C" fn __libc_csu_init() {}#[no_mangle]pub extern "C" fn __libc_csu_fini() {}fn main() { println!("Hello, world!");}
Ещё один быстрый и грязный хак, зато теперь RPM-пакет проходит валидацию и устанавливается!
Момент истины близок, запускаем на планшете и получаем очередную ошибку:
$ aurora-rust-helloworld-bash: /usr/bin/aurora-rust-helloworld: /usr/lib/ld.so.1: bad ELF interpreter: No such file or directory
Смотрим зависимости:
$ ldd /usr/bin/aurora-rust-helloworldlinux-vdso.so.1 (0xbeff4000)libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xa707f000)librt.so.1 => /lib/librt.so.1 (0xa7069000)libpthread.so.0 => /lib/libpthread.so.0 (0xa7042000)libm.so.6 => /lib/libm.so.6 (0xa6fc6000)libdl.so.2 => /lib/libdl.so.2 (0xa6fb3000)libc.so.6 => /lib/libc.so.6 (0xa6e95000)/usr/lib/ld.so.1 => /lib/ld-linux-armhf.so.3 (0xa70e7000)
И видим динамическую линковку с библиотекой
ld-linux-armhf.so.3
. Если решать в лоб, то нужно
создать символическую ссылку /usr/lib/ld.so.1
/lib/ld-linux-armhf.so.3
(и это даже будет неплохо
работать). Но, к сожалению, такое решение не подходит. Дело в том,
что строгий RPM-валидатор не пропустит ни пред(пост)-установочные
скрипты в .spec-файле, ни деплой в директорию
/usr/lib
. Вообще список того, что можно, приведён
здесь.
Долгое и разнообразное гугление подсказало, что у линкера GCC
есть нужный нам ключ (dynamic-linker
), который
позволяет сослаться непосредственно на нужную зависимость. Правим
config.toml
:
.cargo/config.toml:
[target.armv7-unknown-linux-gnueabihf]rustflags = ["-C", "link-args=-L lib lib/crt1.o --dynamic-linker /lib/ld-linux-armhf.so.3"]linker = "bin/armv7hl-meego-linux-gnueabi-ld"
Собираем RPM-пакет, подписываем, копируем на планшет, устанавливаем и с замиранием сердца запускаем:
$ aurora-rust-helloworldHello, world!
Часть 2. Запускаем приложение с GUI
TL;DR Исходники проекта можно найти в репозитории.
В Авроре всё очень сильно завязано на Qt/QML, поэтому сначала я
думал использовать крейт qmetaobject.
Однако в комплекте с ОС идёт библиотека Qt версии 5.6.3, а
qmetaobject
, судя по описанию, требует минимум Qt 5.8.
И действительно, попытка сборки крейта приводит к ошибкам.
Поэтому я пошёл по пути точечных заимствований из исходников
qmetaobject
благо, лицензия позволяет.
Для начала копируем проект, созданный в предыдущей части, и
переименовываем его в aurora-rust-gui
.
Приступаем
Чтобы не утомлять читателя, сразу скажу, что для сборки понадобится скопировать с планшета ещё множество разных библиотек:
вот таких
scp nemo@192.168.2.15:/usr/lib/libstdc++.so ./libscp nemo@192.168.2.15:/usr/lib/libQt5Core.so.5 ./lib/libQt5Core.soscp nemo@192.168.2.15:/usr/lib/libQt5Gui.so.5 ./lib/libQt5Gui.soscp nemo@192.168.2.15:/usr/lib/libQt5Qml.so.5 ./lib/libQt5Qml.soscp nemo@192.168.2.15:/usr/lib/libQt5Quick.so.5 ./lib/libQt5Quick.soscp nemo@192.168.2.15:/usr/lib/libGLESv2.so.2 ./libscp nemo@192.168.2.15:/usr/lib/libpng16.so.16 ./libscp nemo@192.168.2.15:/usr/lib/libz.so.1 ./libscp nemo@192.168.2.15:/usr/lib/libicui18n.so.63 ./libscp nemo@192.168.2.15:/usr/lib/libicuuc.so.63 ./libscp nemo@192.168.2.15:/usr/lib/libpcre16.so.0 ./libscp nemo@192.168.2.15:/usr/lib/libglib-2.0.so.0 ./libscp nemo@192.168.2.15:/usr/lib/libsystemd.so.0 ./libscp nemo@192.168.2.15:/usr/lib/libQt5Network.so.5 ./libscp nemo@192.168.2.15:/lib/libresolv.so.2 ./libscp nemo@192.168.2.15:/usr/lib/libhybris-common.so.1 ./libscp nemo@192.168.2.15:/usr/lib/libicudata.so.63 ./libscp nemo@192.168.2.15:/usr/lib/libpcre.so.1 ./libscp nemo@192.168.2.15:/usr/lib/libselinux.so.1 ./libscp nemo@192.168.2.15:/usr/lib/liblzma.so.5 ./libscp nemo@192.168.2.15:/usr/lib/libgcrypt.so.11 ./libscp nemo@192.168.2.15:/usr/lib/libgpg-error.so.0 ./libscp nemo@192.168.2.15:/usr/lib/libcap.so.2 ./libscp nemo@192.168.2.15:/usr/lib/libsailfishapp.so.1 ./lib/libsailfishapp.soscp nemo@192.168.2.15:/usr/lib/libmdeclarativecache5.so.0 ./libscp nemo@192.168.2.15:/usr/lib/libmlite5.so.0 ./libscp nemo@192.168.2.15:/usr/lib/libdconf.so.1 ./libscp nemo@192.168.2.15:/usr/lib/libgobject-2.0.so.0 ./libscp nemo@192.168.2.15:/usr/lib/libQt5DBus.so.5 ./libscp nemo@192.168.2.15:/usr/lib/libdconf.so.1 ./libscp nemo@192.168.2.15:/usr/lib/libffi.so.6 ./libscp nemo@192.168.2.15:/usr/lib/libdbus-1.so.3 ./libscp nemo@192.168.2.15:/usr/lib/libgio-2.0.so.0 ./libscp nemo@192.168.2.15:/usr/lib/libgmodule-2.0.so.0 ./lib
А еще копируем заголовочные файлы, которые идут в составе Aurora SDK:
-
AuroraOS/mersdk/targets/AuroraOS-3.2.2.21-cert-armv7hl/usr/include/qt5
include/qt5
-
AuroraOS/mersdk/targets/AuroraOS-3.2.2.21-cert-armv7hl/usr/include/sailfishapp
include/sailfishapp
-
AuroraOS/mersdk/targets/AuroraOS-3.2.2.21-cert-armv7hl/usr/include/GLES3
include/GLES3
-
AuroraOS/mersdk/targets/AuroraOS-3.2.2.21-cert-armv7hl/usr/include/KHR
include/KHR
Для сборки проекта напишем скрипт build.rs
и укажем
его в Cargo.toml
.
build.rs:
fn main() { let include_path = "include"; let qt_include_path = "include/qt5"; let sailfish_include_path = "include/sailfishapp"; let library_path = "lib"; let mut config = cpp_build::Config::new(); config .include(include_path) .include(qt_include_path) .include(sailfish_include_path) .opt_level(2) .flag("-std=gnu++1y") .flag("-mfloat-abi=hard") .flag("-mfpu=neon") .flag("-mthumb") .build("src/main.rs"); println!("cargo:rustc-link-search={}", library_path); println!("cargo:rustc-link-lib=sailfishapp"); println!("cargo:rustc-link-lib=Qt5Gui"); println!("cargo:rustc-link-lib=Qt5Core"); println!("cargo:rustc-link-lib=Qt5Quick"); println!("cargo:rustc-link-lib=Qt5Qml");}
Cargo.toml:
[package]# ...build = "build.rs"[dependencies]cpp = "0.5.6"[build-dependencies]cpp_build = "0.5.6"#...
Теперь возьмёмся за само приложение. За создание инстанса
приложения у нас будет отвечать структура SailfishApp
по аналогии с приложением для Авроры, написанном на C++.
src/main.rs:
#[macro_use]extern crate cpp;mod qbytearray;mod qstring;mod qurl;mod sailfishapp;use sailfishapp::SailfishApp;#[no_mangle]pub extern "C" fn __libc_csu_init() {}#[no_mangle]pub extern "C" fn __libc_csu_fini() {}fn main() { let mut app = SailfishApp::new(); app.set_source("main.qml".into()); app.show(); app.exec();}
SailfishApp
это по сути обвязка (биндинги) к
соответствующему классу на C++. Берём за образец структуру
QmlEngine из крейта qmetaobject
.
use crate::qstring::QString;cpp! {{ #include <sailfishapp.h> #include <QtCore/QDebug> #include <QtGui/QGuiApplication> #include <QtQuick/QQuickView> #include <QtQml/QQmlEngine> #include <memory> struct SailfishAppHolder { std::unique_ptr<QGuiApplication> app; std::unique_ptr<QQuickView> view; SailfishAppHolder() { qDebug() << "SailfishAppHolder::SailfishAppHolder()"; int argc = 1; char *argv[] = { "aurora-rust-gui" }; app.reset(SailfishApp::application(argc, argv)); view.reset(SailfishApp::createView()); view->engine()->addImportPath("/usr/share/aurora-rust-gui/qml"); } };}}cpp_class!( pub unsafe struct SailfishApp as "SailfishAppHolder");impl SailfishApp { /// Creates a new SailfishApp. pub fn new() -> Self { cpp!(unsafe [] -> SailfishApp as "SailfishAppHolder" { qDebug() << "SailfishApp::new()"; return SailfishAppHolder(); }) } /// Sets the main QML (see QQuickView::setSource for details). pub fn set_source(&mut self, url: QString) { cpp!(unsafe [self as "SailfishAppHolder *", url as "QString"] { const auto full_url = QString("/usr/share/aurora-rust-gui/qml/%1").arg(url); qDebug() << "SailfishApp::set_source()" << full_url; self->view->setSource(full_url); }); } /// Shows the main view. pub fn show(&self) { cpp!(unsafe [self as "SailfishAppHolder *"] { qDebug() << "SailfishApp::show()"; self->view->showFullScreen(); }) } /// Launches the application. pub fn exec(&self) { cpp!(unsafe [self as "SailfishAppHolder *"] { qDebug() << "SailfishApp::exec()"; self->app->exec(); }) }}
Биндинги для используемых классов QByteArray
,
QString
, QUrl
копируем из того же
qmetaobject
и расфасовываемым по отдельным файлам.
Здесь приводить их не буду, если что, исходники можно посмотреть в
репозитории на GitHub.
Немного скорректируем заголовочный файл
sailfishapp.h
, чтобы он искал заголовочные файлы Qt в
правильных местах:
include/sailfishapp/sailfishapp.h:
// ...#ifdef QT_QML_DEBUG#include <QtQuick>#endif#include <QtCore/QtGlobal> // Было `#include <QtGlobal>`#include <QtCore/QUrl> // Было `#include <QUrl>`class QGuiApplication;class QQuickView;class QString;// ...
Осталось только добавить файлы QML и положить их в дистрибутив RPM.
все здесьqml/main.qml:
import QtQuick 2.6import Sailfish.Silica 1.0ApplicationWindow { cover: Qt.resolvedUrl("cover.qml") initialPage: Page { allowedOrientations: Orientation.LandscapeMask Label { anchors.centerIn: parent text: "Hello, Aurora!" } }}
qml/cover.qml:
import QtQuick 2.6import Sailfish.Silica 1.0CoverBackground { Rectangle { id: background anchors.fill: parent color: "blue" Label { id: label anchors.centerIn: parent text: "Rust GUI" color: "white" } } CoverActionList { id: coverAction CoverAction { iconSource: "image://theme/icon-cover-cancel" onTriggered: Qt.quit() } }}
.rpm/aurora-rust-gui.spec:
# ...Source3: qml# ...%install# ...mkdir -p %{buildroot}%{_datadir}/%{name}cp -ra %{SOURCE3} %{buildroot}%{_datadir}/%{name}/qml%cleanrm -rf %{buildroot}%files# ...%{_datadir}/%{name}/qml
Makefile:
# ...rpm:# ...@cp -rvf qml ./target/armv7-unknown-linux-gnueabihf/release/rpmbuild/SOURCES# ...
Собираем:
make cleanmake releasemake rpm
Подписываем, копируем, устанавливаем, запускаем из командной строки и вуаля:
$ devel-suPassword:# pkcon install-local ./aurora-rust-gui-0.1.0-1.armv7hl.rpmInstalling filesTesting changesFinishedInstalling filesStartingResolving dependenciesInstalling packagesDownloading packagesInstalling packagesFinishedDownloaded aurora-rust-gui-0.1.0-1.armv7hl (PK_TMP_DIR) Rust GUI example for Aurora OSInstalled aurora-rust-gui-0.1.0-1.armv7hl (PK_TMP_DIR) Rust GUI example for Aurora OS# exit$ aurora-rust-gui[D] __cpp_closure_14219197022164792912_impl:33 - SailfishApp::new()[D] SailfishAppHolder::SailfishAppHolder:15 - SailfishAppHolder::SailfishAppHolder()[D] unknown:0 - Using Wayland-EGLlibrary "libpq_cust_base.so" not found[D] __cpp_closure_16802020016530731597:42 - SailfishApp::set_source() "/usr/share/aurora-rust-gui/qml/main.qml"[W] unknown:0 - Could not find any zN.M subdirs![W] unknown:0 - Theme dir "/usr/share/themes/sailfish-default/meegotouch/z1.0/" does not exist[W] unknown:0 - Theme dir "/usr/share/themes/sailfish-default/meegotouch/" does not exist[D] onCompleted:432 - Warning: specifying an object instance for initialPage is sub-optimal - prefer to use a Component[D] __cpp_closure_12585295123509486988:50 - SailfishApp::show()[D] __cpp_closure_15029454612933909268:59 - SailfishApp::exec()
Вот так выглядит наше приложение с разных ракурсов:
Рабочий стол с ярлыком Главное окно приложения Панель задачПоследние штрихи
Приложение отлично стартует из командной строки при подключении
по SSH, однако никак не реагирует при попытке запуска с помощью
ярлыка. Путём некоторых экспериментов удалось установить, что для
этого надо экспортировать символ main
(RPM-валидатор
выдавал предупреждение на этот счёт, но некритичное).
Серия проб и ошибок показала, что надо добавить ещё один ключ
линкера: -export-dynamic
.
.cargo/config.toml:
[target.armv7-unknown-linux-gnueabihf]rustflags = ["-C", "link-args=-L lib lib/crt1.o -rpath lib --dynamic-linker /lib/ld-linux-armhf.so.3 -export-dynamic"]linker = "bin/armv7hl-meego-linux-gnueabi-ld"
После этого всё работает так, как и ожидается.
Заключение
Понятно, что до того, как использовать Rust в проде, ещё надо решить немало вопросов. Как минимум, я предвижу сложности с дополнительными зависимостями при подключении новых крейтов, извечные танцы с бубном вокруг сегфолтов при FFI-вызовах, увязывание систем владения Qt и Rust. Некоторые интересные подробности можно почерпнуть из статьи от автора qmetaobject-rs. Наверняка, время от времени будут всплывать и другие проблемы.
Плюс к этому, для того, чтобы использовать Qt-овские классы, к каждому из них необходимо писать биндинги.
Однако всё это стоит того, чтобы иметь возможность писать на Rust, с его мощной системой типов, средствами обеспечения безопасности при работе с памятью, приятной реализацией многопоточности, самым лучшим подходом к обработке ошибок и всеми теми вещами за которые мы его так любим.
Буду рад вопросам и замечаниям в комментариях. И ставьте лайк, подписывайтесь на канал :-)