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

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

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

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

Для промышленной разработки мы, конечно же, пока используем связку 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/


Интересные статьи

Интересные статьи

Привет, Хабр! Работа со встроенной рекламой в приложениях на платформе Huawei Mobile Services ведётся с помощью сервиса Ads Kit. Сервис предоставляет пользователям персон...
Есть статьи о недостатках Битрикса, которые написаны программистами. Недостатки, описанные в них рядовому пользователю безразличны, ведь он не собирается ничего программировать.
Если у вас есть интернет-магазин и вы принимаете платежи через Интернет, то с 01 июля 2017 года у вас есть онлайн-касса.
1С Битрикс: Управление сайтом (БУС) - CMS №1 в России по версии портала “Рейтинг Рунета” за 2018 год. На рынке c 2003 года. За это время БУС не стоял на месте, обрастал новой функциональностью...
Тема статьи навеяна результатами наблюдений за методикой создания шаблонов различными разработчиками, чьи проекты попадали мне на поддержку. Порой разобраться в, казалось бы, такой простой сущности ка...