Запускаем Rust-приложение на мобильной ОС Аврора

от автора

Всем привет! Меня зовут Шамиль, я ведущий инженер-разработчик в КРОК. Помимо всего прочего мы в компании занимаемся ещё и разработкой мобильных приложений для операционной системы Аврора, есть даже центр компетенций по ней.

Для промышленной разработки мы, конечно же, пока используем связку C++ и QML, но однажды подсев на «ржавую» иглу Rust, я не мог не попробовать применить свой любимый язык программирования для написания мобильных приложений. В этой статье я опишу эксперимент по написанию простейшего приложения на Rust, предназначенного для запуска на мобильном устройстве под управлением вышеупомянутой ОС. Сразу оговорюсь, что легких путей я не искал — эксперименты проводил на сертифицированной версии Авроры, которая добавила огонька в этот процесс. Но, как говорится, только защищённая ОС, только хардкор.

Пара выходных у меня ушла только на то, чтобы запустить минимальное консольное приложение (речь о нём пойдёт в первой части), ещё пара дней — на эксперименты с графическим интерфейсом, выбор оптимального подхода и запуск приложения с GUI (этому посвящена вторая часть повествования). В итоге получился минимальный “скелет” мобильного приложения, готового к сборке и запуску, на который при желании уже можно наращивать “мясо”.

Готовим окружение

Итак, работа будет вестись из-под Ubuntu Linux с уже установленным Rust. В качестве подопытного планшета выступает Aquarius NS220 с сертифицированной ОС Аврора последней (на момент написания статьи) версии 3.2.2 с включённым режимом разработчика, который обеспечивает связь по SSH, а также привилегированный доступ с правами суперпользователя.

Первым делом добавим средства кросскомпилятора для архитектуры ARM, так как на целевом планшете стоит именно такой процессор.

sudo apt install -y g++-arm-linux-gnueabihf rustup 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 init cargo rpm build -v --target=armv7-unknown-linux-gnueabihf

Всё собралось, забираем RPM из папки target/armv7-unknown-linux-gnueabihf/release/rpmbuild/RPMS/armv7hl, подписываем его, копируем на планшет и пытаемся установить:

$ devel-su Password: # 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 --version ldd (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_s aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lutil aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lrt aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lpthread aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lm aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -ldl aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lc aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find -lutil

Копируем недостающие библиотеки с планшета:

mkdir -p lib scp nemo@192.168.2.15:/usr/lib/libgcc_s.so ./lib scp nemo@192.168.2.15:/usr/lib/libutil.so ./lib scp nemo@192.168.2.15:/usr/lib/librt.so ./lib scp nemo@192.168.2.15:/usr/lib/libpthread.so ./lib scp nemo@192.168.2.15:/usr/lib/libm.so ./lib scp nemo@192.168.2.15:/usr/lib/libdl.so ./lib scp nemo@192.168.2.15:/usr/lib/libc.so ./lib scp 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.6 aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find /lib/libc.so.6 aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: skipping incompatible /usr/lib/libc_nonshared.a when searching for /usr/lib/libc_nonshared.a aurora-rust-helloworld/bin/armv7hl-meego-linux-gnueabi-ld: cannot find /usr/lib/libc_nonshared.a aurora-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 ./lib scp nemo@192.168.2.15:/usr/lib/libc_nonshared.a ./lib scp 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 file  Paths ===== WARNING [/usr/share/aurora-rust-helloworld] Directory not found ERROR [/usr/share/applications/aurora-rust-helloworld.desktop] File not found WARNING [/usr/share/icons/hicolor/86x86/apps/aurora-rust-helloworld.png] File not found WARNING [/usr/share/icons/hicolor/108x108/apps/aurora-rust-helloworld.png] File not found WARNING [/usr/share/icons/hicolor/128x128/apps/aurora-rust-helloworld.png] File not found WARNING [/usr/share/icons/hicolor/172x172/apps/aurora-rust-helloworld.png] File not found ERROR [/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_requirements  Libraries ========= ERROR [/usr/bin/aurora-rust-helloworld] Cannot link to shared library: libutil.so.1  Symbols ======= 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=Application X-Nemo-Application-Type=silica-qt5 Icon=aurora-rust-helloworld Exec=aurora-rust-helloworld Name=Rust Hello-World

Для того, чтобы копировать нужные файлы на этапе сборки RPM-пакета, сделаем простенький Makefile:

Makefile
.PHONY: all clean install prepare release rpm  all: 	@cargo build --target=armv7-unknown-linux-gnueabihf  clean: 	@rm -rvf target  install: 	@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-rpm  release: 	@cargo build --release --target=armv7-unknown-linux-gnueabihf  rpm: 	@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:

.rpm/aurora-rust-helloworld.spec
%define __spec_install_post %{nil} %define __os_install_post %{_dbpath}/brp-compress %define debug_package %{nil}  Name: aurora-rust-helloworld Summary: Rust example for Aurora OS Version: @@VERSION@@ Release: @@RELEASE@@%{?dist} License: MIT Group: Applications/System Source0: %{name}-%{version}.tar.gz Source1: %{name}.desktop Source2: icons  BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root  %description %{summary}  %prep %setup -q  %install rm -rf %{buildroot} mkdir -p %{buildroot} cp -a * %{buildroot} mkdir -p %{buildroot}%{_datadir}/applications cp -a %{SOURCE1} %{buildroot}%{_datadir}/applications mkdir -p %{buildroot}%{_datadir}/icons/hicolor/86x86/apps mkdir -p %{buildroot}%{_datadir}/icons/hicolor/108x108/apps mkdir -p %{buildroot}%{_datadir}/icons/hicolor/128x128/apps mkdir -p %{buildroot}%{_datadir}/icons/hicolor/172x172/apps cp -a %{SOURCE2}/86x86/%{name}.png %{buildroot}%{_datadir}/icons/hicolor/86x86/apps cp -a %{SOURCE2}/108x108/%{name}.png %{buildroot}%{_datadir}/icons/hicolor/108x108/apps cp -a %{SOURCE2}/128x128/%{name}.png %{buildroot}%{_datadir}/icons/hicolor/128x128/apps cp -a %{SOURCE2}/172x172/%{name}.png %{buildroot}%{_datadir}/icons/hicolor/172x172/apps  %clean rm -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-helloworld 	linux-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-helloworld Hello, 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 ./lib scp nemo@192.168.2.15:/usr/lib/libQt5Core.so.5 ./lib/libQt5Core.so scp nemo@192.168.2.15:/usr/lib/libQt5Gui.so.5 ./lib/libQt5Gui.so scp nemo@192.168.2.15:/usr/lib/libQt5Qml.so.5 ./lib/libQt5Qml.so scp nemo@192.168.2.15:/usr/lib/libQt5Quick.so.5 ./lib/libQt5Quick.so  scp nemo@192.168.2.15:/usr/lib/libGLESv2.so.2 ./lib scp nemo@192.168.2.15:/usr/lib/libpng16.so.16 ./lib scp nemo@192.168.2.15:/usr/lib/libz.so.1 ./lib scp nemo@192.168.2.15:/usr/lib/libicui18n.so.63 ./lib scp nemo@192.168.2.15:/usr/lib/libicuuc.so.63 ./lib scp nemo@192.168.2.15:/usr/lib/libpcre16.so.0 ./lib scp nemo@192.168.2.15:/usr/lib/libglib-2.0.so.0 ./lib scp nemo@192.168.2.15:/usr/lib/libsystemd.so.0 ./lib scp nemo@192.168.2.15:/usr/lib/libQt5Network.so.5 ./lib  scp nemo@192.168.2.15:/lib/libresolv.so.2 ./lib scp nemo@192.168.2.15:/usr/lib/libhybris-common.so.1 ./lib scp nemo@192.168.2.15:/usr/lib/libicudata.so.63 ./lib scp nemo@192.168.2.15:/usr/lib/libpcre.so.1 ./lib scp nemo@192.168.2.15:/usr/lib/libselinux.so.1 ./lib scp nemo@192.168.2.15:/usr/lib/liblzma.so.5 ./lib scp nemo@192.168.2.15:/usr/lib/libgcrypt.so.11 ./lib scp nemo@192.168.2.15:/usr/lib/libgpg-error.so.0 ./lib scp nemo@192.168.2.15:/usr/lib/libcap.so.2 ./lib  scp nemo@192.168.2.15:/usr/lib/libsailfishapp.so.1 ./lib/libsailfishapp.so scp nemo@192.168.2.15:/usr/lib/libmdeclarativecache5.so.0 ./lib scp nemo@192.168.2.15:/usr/lib/libmlite5.so.0 ./lib scp nemo@192.168.2.15:/usr/lib/libdconf.so.1 ./lib scp nemo@192.168.2.15:/usr/lib/libgobject-2.0.so.0 ./lib scp nemo@192.168.2.15:/usr/lib/libQt5DBus.so.5 ./lib scp nemo@192.168.2.15:/usr/lib/libdconf.so.1 ./lib scp nemo@192.168.2.15:/usr/lib/libffi.so.6 ./lib scp nemo@192.168.2.15:/usr/lib/libdbus-1.so.3 ./lib scp nemo@192.168.2.15:/usr/lib/libgio-2.0.so.0 ./lib scp 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/qt5include/qt5

  • AuroraOS/mersdk/targets/AuroraOS-3.2.2.21-cert-armv7hl/usr/include/sailfishappinclude/sailfishapp

  • AuroraOS/mersdk/targets/AuroraOS-3.2.2.21-cert-armv7hl/usr/include/GLES3include/GLES3

  • AuroraOS/mersdk/targets/AuroraOS-3.2.2.21-cert-armv7hl/usr/include/KHRinclude/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.

src/sailfishapp.rs
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.6 import Sailfish.Silica 1.0  ApplicationWindow {     cover: Qt.resolvedUrl("cover.qml")      initialPage: Page {         allowedOrientations: Orientation.LandscapeMask          Label {             anchors.centerIn: parent             text: "Hello, Aurora!"         }      }  }

qml/cover.qml:

import QtQuick 2.6 import Sailfish.Silica 1.0  CoverBackground {     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  %clean rm -rf %{buildroot}  %files # ... %{_datadir}/%{name}/qml

Makefile:

# ... rpm: 	# ... 	@cp -rvf qml ./target/armv7-unknown-linux-gnueabihf/release/rpmbuild/SOURCES # ...

Собираем:

make clean make release make rpm

Подписываем, копируем, устанавливаем, запускаем из командной строки и вуаля:

$ devel-su Password: # pkcon install-local ./aurora-rust-gui-0.1.0-1.armv7hl.rpm Installing files Testing changes Finished Installing files Starting Resolving dependencies Installing packages Downloading packages Installing packages Finished Downloaded  	aurora-rust-gui-0.1.0-1.armv7hl (PK_TMP_DIR)          		Rust GUI example for Aurora OS Installed   	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-EGL library "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, с его мощной системой типов, средствами обеспечения безопасности при работе с памятью, приятной реализацией многопоточности, самым лучшим подходом к обработке ошибок и всеми теми вещами за которые мы его так любим.

Буду рад вопросам и замечаниям в комментариях. И ставьте лайк, подписывайтесь на канал 🙂

ссылка на оригинал статьи https://habr.com/ru/company/croc/blog/544900/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *