company_banner

Запускаем 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, с его мощной системой типов, средствами обеспечения безопасности при работе с памятью, приятной реализацией многопоточности, самым лучшим подходом к обработке ошибок и всеми теми вещами за которые мы его так любим.

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


    UPD. Со мной связались наши партнёры из компании “Открытая мобильная платформа”, занимающейся разработкой и внедрением ОС Аврора, и рекомендовали попробовать базовый релиз 3.4, там Rust вошёл в стандартную поставку при обновлении движка браузера на ESR 60, а GCC обновился до версии 8.3. Если тема вызовет интерес, то поделимся и новым опытом.

    КРОК
    IT-компания

    Comments 9

      0

      При подключении по SSH трафик девайса начинает маршрутизироваться на ПК и приложения перестают ходить в мобильную сеть. Не встречали такого?
      Вообще, не попадались ли способы определять, есть ли подключение к сети (передача данных)?

        +2
        Такой проблемы не встречал, правда при подключении по SSH пользуюсь Wi-Fi, а не мобильным интернетом. Задачи детектировать именно подключение к сети не было, обычно детектировали только связь с сервером.
        0
        Вот такой вопрос: захотел я, например, занятся разработкой для ОС Аврора.
        Можно ли купить старенькую Sony Xperia, на которую накатить Sailfish X Free, и разрабатывать?
        И будет ли приложение, написаное для Sailfish X, совместимо с ОС Аврора?

        Просто, пока такое впечатление, что сейчас порог вхождения намеренно завышен. Походил по сайту omprussia.ru, цена одного смартфона 107 365 рублей (Защищённый смартфон MIG C55), на другие, вообще, цены не пишут.
          +2
          Jolla Sailfish имеет высокую степень совместимости с ОС Аврора, но это разные ОС, поэтому стопроцентной гарантии дать нельзя. Если использовать стандартные Qt/QML компоненты, то с большой вероятностью приложение будет работать на обеих ОС. Могут только возникнуть нюансы на сертифицированной версии Авроры, о которых я писал в статье. Мы, например, даже настроили запуск наших приложений на десктопных ОС (Windows/Linux/macOS), что существенно упрощает отладку.

          Более гарантированным способом отладки является запуск приложения на эмуляторе, который входит в состав Aurora SDK. Он полностью имитирует среду, которая будет на мобильном устройстве.
            +1
            Эмулятор — хорошая идея!
            Можно собрать проект на SDK Sailfish X, а запустить под эмулятором ОС Аврора.
            +1

            Ищите INOI R7, INOI Tab8
            возможно по барахолкам. Это были дивайсы по цене 10-15к рублей новые.
            Еще дивайсы — Qtech QMP-M1-N, Blackview BV6000s, MiG C55 и планшеты INOI T8, «Аквариус» NS208, «Аквариус Cmp» NS220 и «БайтЭрг» МВK-2020


            Также для разработки можно использовать бесплатное, полностью с открытым исходным кодом, кросс-платформенное решение Rhomobile — https://github.com/rhomobile/rhodes
            Описание (включая использование для "Аврора") — http://files.tau-platform.com/Documents%20/2021_Rhomobile_and_Russian_OS_Tau_Technologies_RUS.pdf
            Это гибридная платформа похожая на PhoneGap/Cordova. Разработка на веб технологиях.

            –5
            Почему авророй ось назвали? Мы же не при чичичипи живем, да и не в древней Греции.
              +1
              Почему Авророй… назвали? Мы же не… в древней Греции

              Написал человек, чьё имя (согласно профиля) — Денис.
              Имя Денис произошло от др.-греч. Διονύσιος и греч. Διόνυσος, означающего «принадлежащий, посвящённый Дионису». А Вас, почему по-гречески назвали?
                –2
                А Вас, почему по-гречески назвали?

                Что вы ко мне обращаетесь как к публичной персоне?
                Написал человек, чьё имя (согласно профиля) — Денис.
                Имя Денис произошло от др.-греч. Διονύσιος и греч. Διόνυσος, означающего «принадлежащий, посвящённый Дионису».

                Имя я не сам выбирал, вестимо. Меня так Мама назвала. Емнип, с ее слов — по настоянию свекрови, стало бы моей бабули по линии отца. Да и в профиле, что угодно может быть написано. Почему согласно профиля, а не профилю?

            Only users with full accounts can post comments. Log in, please.