Andrei Yankovich
Andrei Yankovich24 березня 2020 р. 09:50

Випуск утиліти для розгортання додатків C++/Qt і QML CQtDeployer версії 1.4.0 (двійкова коробка)

Майже півроку вийшло велике оновлення утиліти розгортання CQtDeployer.
У цьому оновленні багато нововведень, але основний акцент робиться на створення пакетів.


CQtDeployer 1.4.0

Виправлення

  • Виправлено виведення довідки в консоль, перед виведенням перераховується реальний розмір консолі, що дозволяє коректно передавати текст.
  • Виправлено роботу з деплоєм плагінів Qt. Тепер плагіни отримують не всі системні залежності, а тільки qt. Вилучення системних залежностей призводило до збою додатків через несумісні бібліотеки плагінів.
  • Дрібні виправлення помилок та покращення.

Нові особливості

  • Додана підтримка пошуку qmake із системного середовища.
  • Додана можливість ініціалізувати репозиторій для подальшого пакування, аналогічно git init.
  • Додана підтримка пакетів Qt Install Framework. Тепер можна запакувати дистрибутив у установник.
  • Додана можливість розбивати кінцевий дистрибутив на кілька пакетів.
  • Додано можливість уніфікувати створення пакетів для фінальної роздачі.
  • Додана підтримка додавання скриптів користувача в скрипти запуску додатків.
  • Додано підтримку вилучення системних залежностей для Windows.
  • Додана підтримка RPATH для Linux. Тепер cqtdeployer може самостійно визначити необхідний qmake для розгортання програми.
  • Додана підтримка пошуку потрібної залежності на ім'я бібліотеки.
  • Додана підтримка бібліотек Qt із репозиторіїв дистрибутивів Linux.
  • Доданий новий псевдонім для команди запуску (cqt та cqtdeployer.cqt) для швидкого розгортання програм.
  • Додано підтримку нативного імені команди для windows. Тепер можна запустити cqtdeployer з команди cqtdeployer в cmd і powershell.

Нові опції

  • init – ініціалізує файл cqtdeployer.json (файл конфігурації). Наприклад: cqtdeployer init — для ініціалізації конфігурації базового пакета. cqtdeployer -init multi - для ініціалізації конфігурації декількох пакетів.
  • noCheckRPATH - відключає автоматичний пошук шляхів до qmake у файлах (тільки для Linux).
  • noCheckPATH - відключає автоматичний пошук шляхів до qmake у системному оточенні.
  • ExtractPlugins – примусово витягує всі залежності плагінів.
    -qif - Створює установник в кінці розгортання.
  • extraLibs - додає шаблон для додаткової бібліотеки, яка має бути включена до дистрибутиву.
  • customScript - додає скрипт користувача в скрипт запуску програми.
    --targetPackage [пакет; tar1, пакет; tar2] - використовується для формування пакетів, позначає списки цільових файлів для конкретних пакетів.
    — recOut — вказує, до якої папки буде додано ресурси після розгортання.
  • name – задає ім'я пакета.
  • description - встановлює опис пакету
  • deployVersion – встановлює версію пакету
  • releaseDate – встановлює дату випуску пакета.
  • icon - задає піктограму пакета.
  • publisher – встановлює видавця пакета.
  • qifStyle - Задає шлях до файлу стилів CSS або задає стиль за промовчанням. Доступні стилі: квазар
  • qifBanner - Встановлює шлях до png-файлу банера.
  • qifLogo - Встановлює шлях до png-файлу логотипу.

Посилання для скачування

Установник:

Інсталятор можна завантажити на соціальній сторінці github

Знімок

Отримати його з Snap Store

Увага

У снап-версії обов'язково дайте програмі доступ до читання інших процесів. Тому що це необхідно для створення установника та коректного пошуку qt.

Детальний розбір найцікавіших змін.

Перше, на що слід звернути увагу, це те, що CQtDeployer навчився працювати з RPATH (лише для Linux) та PATH. Це означає, що якщо ваша програма зібрана за допомогою RPATH (а RPATH в qt включений за замовчуванням) або ваш qmake прописаний в PATH, то вам не потрібно вказувати шлях до qmake. CQtDeployer сам знайде qmake.

Перевіримо практично.
Я створив простий консольний додаток за допомогою Qt.

#include <QString>
#include <QDebug>
int main(int, char *[])
{
    QString str = "hello CQtDeployer 1.4";
    qInfo() << str;
    return 0;
}

Я використовуватиму систему складання cmake, тому що вона більш актуальна, ніж qmake.

andrei@HP:~/Hello$ tree 
.
├── CMakeLists.txt
├── CMakeLists.txt.user
└── main.cpp

0 directories, 3 files

Створіть папку для збирання.

andrei@HP:~/Hello$ mkdir build 

Запустіть cmake у створеній папці.

andrei@HP:~/Hello/build$ cmake .. -DCMAKE_PREFIX_PATH=~/Qt/5.14.1/gcc_64
-- Configuring done
-- Generating done
-- Build files have been written to: /home/andrei/Hello/build

створення проекту

andrei@HP:~/Hello/build$ make 
Scanning dependencies of target Hello_autogen
[ 25%] Automatic MOC and UIC for target Hello
[ 25%] Built target Hello_autogen
Scanning dependencies of target Hello
[ 50%] Building CXX object CMakeFiles/Hello.dir/Hello_autogen/mocs_compilation.cpp.o
[ 75%] Building CXX object CMakeFiles/Hello.dir/main.cpp.o
[100%] Linking CXX executable Hello
[100%] Built target Hello

Перевіряємо нашу програму.

andrei@HP:~/Hello/build$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Hello  Hello_autogen  Makefile

І запускаємо cqtdeployer минаючи його програму без qmake.

andrei@HP:~/Hello/build$ cqtdeployer -bin Hello 
Deploy ...
flag targetDir not  used. use default target dir : "/home/andrei/Hello/build/DistributionKit"
target deploy started!!
copy : "/home/andrei/Hello/build/Hello"
extract lib : "/home/andrei/Hello/build/DistributionKit//bin//Hello"
copy : "/home/andrei/Qt/5.14.1/gcc_64/lib/libQt5Core.so.5"
copy : "/home/andrei/Qt/5.14.1/gcc_64/lib/libicuuc.so.56"
copy : "/home/andrei/Qt/5.14.1/gcc_64/lib/libicui18n.so.56"
copy : "/home/andrei/Qt/5.14.1/gcc_64/lib/libicudata.so.56"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_ar.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_bg.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_ca.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_cs.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_da.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_de.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_en.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_es.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_fi.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_fr.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_gd.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_he.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_hu.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_it.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_ja.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_ko.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_lv.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_pl.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_ru.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_sk.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_uk.qm"
copy : "/home/andrei/Qt/5.14.1/gcc_64/translations/qtbase_zh_TW.qm"
try deploy msvc
deploy done!

О диво, тепер наша програма повністю автономна.
Перевірте це.

andrei@HP:~/Hello/build$ cd DistributionKit/
andrei@HP:~/Hello/build/DistributionKit$ tree 
.
├── bin
│   ├── Hello
│   └── qt.conf
├── Hello.sh
├── lib
│   ├── libicudata.so.56
│   ├── libicui18n.so.56
│   ├── libicuuc.so.56
│   └── libQt5Core.so.5
└── translations
    ├── qtbase_ar.qm
    ├── qtbase_bg.qm
    ├── qtbase_ca.qm
    ├── qtbase_cs.qm
    ├── qtbase_da.qm
    ├── qtbase_de.qm
    ├── qtbase_en.qm
    ├── qtbase_es.qm
    ├── qtbase_fi.qm
    ├── qtbase_fr.qm
    ├── qtbase_gd.qm
    ├── qtbase_he.qm
    ├── qtbase_hu.qm
    ├── qtbase_it.qm
    ├── qtbase_ja.qm
    ├── qtbase_ko.qm
    ├── qtbase_lv.qm
    ├── qtbase_pl.qm
    ├── qtbase_ru.qm
    ├── qtbase_sk.qm
    ├── qtbase_uk.qm
    └── qtbase_zh_TW.qm

3 directories, 29 files
andrei@HP:~/Hello/build/DistributionKit$ 

Корінь програми:

image

Бібліотеки, необхідні для роботи програми:

image

Як видно з прикладу, програма повністю зібрана.

Каркас установника Qt

Друга новація, про яку варто знати, - це можливість формувати QIF-установники з коробки. Все, що потрібно для прикладу, це додати опцію qif в команду упаковки.

Приклад використання.

andrei@HP:~/Hello/build$ cqtdeployer -bin Hello qif 

Всього одна проста команда і програма набуває презентабельного вигляду.

image

Цей установник підтримує мінімальну інтеграцію дистрибутивів Linux та Windows. А саме: створення ярликів, та реєстрація програми в ОС.
Якщо вас з якоїсь причини не влаштовує зовнішній вигляд цього установника, ви можете змінити його за допомогою qifStyle прапора. На момент версії 1.4 cqtdeployer підтримує лише 2 стилі (native та quasar).

Приклад стилю квазара:

image

Ви також можете використовувати власну таблицю стилів qss. Для цього передайте шлях до файлу qss або css замість імені стилю.
Наприклад, розглянемо таку таблицю стилів qss.

Style.qss:

QWidget
{
    color: white;
    background-color: rgb(65, 65, 65);
}

QPushButton
{
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(150, 150, 150, 60%), stop:1 rgba(50, 50, 50, 60%));
    border-color: rgb(60, 60, 60);
    border-style: solid;
    border-width: 2px;
    border-radius: 9px;
    min-height: 20px;
    max-height: 20px;
    min-width: 60px;
    max-width: 60px;
    padding-left: 15px;
    padding-right: 15px;
}

QPushButton:pressed, QPushButton:checked
{
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(50, 50, 50, 60%), stop:1 rgba(150, 150, 150, 60%));
}

Перевіримо, що ми отримуємо в цьому випадку.

cqtdeployer -bin Hello qif -qifStyle ./../style.qss

image

Ось, власне, і темна тема інсталятора.

Розбивка на пакети

І, напевно, останнє важливе оновлення, про яке варто знати, це можливість розбивати великий мультибінарний проект на підпроекти.

Ця функція найскладніша з усіх перерахованих, тому що для її використання потрібно багато тексту. Тому я рекомендую використовувати конфігураційний файл.

Для початку ускладнимо наш проект, додавши в нього ще 2 виконувані файли. Я не став морочитися і просто зробив дві копії своєї утиліти Hello.

Для полегшення роботи з пакетами необхідно ініціалізувати каталог.

cqtdeployer init

Це ще одна нова функція, яка створює файл CQtDeployer.json, в який ми будемо писати наші конфігурації замість того, щоб передавати параметри утиліті.

{
    "binDir": ".",
    "clear": true,
    "libDir": "./",
    "recursiveDepth": 5
}

Тепер зробимо 2 пакети з трьох наших програм. Для цього вкажіть:

{
    "binDir": ".",
    "clear": true,
    "libDir": "./",
    "recursiveDepth": 5,
    "targetPackage": [
        ["Dstro1", "Hello1"],
        ["Dstro2", "Hello2"],
        ["Dstro2", "Hello3"]
    ]
}

Зверніть увагу, що мені довелося явно вказати прив'язку Dstro2 Hello2 Hello3. На жаль, під час версії 1.4 cqtdeployer було неможливо аналізувати цільові перерахування. Зверніть увагу, що якщо я напишу замість Hello1 Hello, то відбір буде проводитись за всіма збігами та будуть обрані всі 3 програми.
Тож давайте подивимося, що сталося.

cqtdeployer

.
├── Dstro1
│   ├── bin
│   │   ├── Hello1
│   │   └── qt.conf
│   ├── Hello1.sh
│   ├── lib
│   │   ├── libicudata.so.56
│   │   ├── libicui18n.so.56
│   │   ├── libicuuc.so.56
│   │   └── libQt5Core.so.5
│   └── translations
│       ├── qtbase_ar.qm
│       ├── qtbase_bg.qm
│       ├── qtbase_ca.qm
│       ├── qtbase_cs.qm
│       ├── qtbase_da.qm
│       ├── qtbase_de.qm
│       ├── qtbase_en.qm
│       ├── qtbase_es.qm
│       ├── qtbase_fi.qm
│       ├── qtbase_fr.qm
│       ├── qtbase_gd.qm
│       ├── qtbase_he.qm
│       ├── qtbase_hu.qm
│       ├── qtbase_it.qm
│       ├── qtbase_ja.qm
│       ├── qtbase_ko.qm
│       ├── qtbase_lv.qm
│       ├── qtbase_pl.qm
│       ├── qtbase_ru.qm
│       ├── qtbase_sk.qm
│       ├── qtbase_uk.qm
│       └── qtbase_zh_TW.qm
└── Dstro2
    ├── bin
    │   ├── Hello2
    │   ├── Hello3
    │   └── qt.conf
    ├── Hello2.sh
    ├── Hello3.sh
    ├── lib
    │   ├── libicudata.so.56
    │   ├── libicui18n.so.56
    │   ├── libicuuc.so.56
    │   └── libQt5Core.so.5
    └── translations
        ├── qtbase_ar.qm
        ├── qtbase_bg.qm
        ├── qtbase_ca.qm
        ├── qtbase_cs.qm
        ├── qtbase_da.qm
        ├── qtbase_de.qm
        ├── qtbase_en.qm
        ├── qtbase_es.qm
        ├── qtbase_fi.qm
        ├── qtbase_fr.qm
        ├── qtbase_gd.qm
        ├── qtbase_he.qm
        ├── qtbase_hu.qm
        ├── qtbase_it.qm
        ├── qtbase_ja.qm
        ├── qtbase_ko.qm
        ├── qtbase_lv.qm
        ├── qtbase_pl.qm
        ├── qtbase_ru.qm
        ├── qtbase_sk.qm
        ├── qtbase_uk.qm
        └── qtbase_zh_TW.qm

8 directories, 60 files

Як видно з дерева результатів, у нас вийшло 2 дистрибутиви.
1. Dstro1 - містить додаток Hello1
2. Dstro2 - містить 2, що залишилися.

Тепер перевіримо, що буде, якщо це запакувати установщиком. Додайте для параметра qif значення true у CQtDeployer.json: qif: true, .

{
    "binDir": ".",
    "clear": true,
    "qif": true,
    "libDir": "./",
    "recursiveDepth": 5,
    "targetPackage": [
        ["Dstro1", "Hello1"],
        ["Dstro2", "Hello2"],
        ["Dstro2", "Hello3"]
    ]

image

Як видно зі скріншота, тепер у нас 2 пакети при встановленні.

Нові псевдоніми

І останнє невелике, але приємне доповнення: тепер до cqtdeployer додані нові команди.

  • сqt — це швидкий спосіб розгорнути вашу програму. Це полегшує виклик розгортання.
  • Приклад:
    cqt myApp — це те саме, що cqtdeployer -bin myApp .

  • cqtdeployer.cqt - те саме, що і cqt, але для пакета snap.

  • У windows-версії тепер не потрібно додавати знак % для виклику утиліти.

  • У версії для windows тепер немає потреби додавати знак % для виклику утиліти.
    Тепер виклик виглядає як у Linux. (cqtdeployer)

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

o
  • 24 березня 2020 р. 14:51

спасибо, за обзор!

Andrei Yankovich
  • 24 березня 2020 р. 15:29

Если будут вопросы, пишите. Найдем решение)

Ruslan Polupan
  • 26 березня 2020 р. 07:47

Супер. Спасибо!

D
  • 26 березня 2020 р. 08:50

Добрый день. У меня есть приложение с библиотеками собранными отдельно. Как мне сделать так, чтобы эти библиотеки помещались в установщик и при установке приложение были вместе с исполняемым файлом? И почему утилита при использовании -bin или -bindir создает ярылки для каких-то библиотек, но не для исполняемого файла? Я использую Windows.

Andrei Yankovich
  • 26 березня 2020 р. 09:27
  • (відредаговано)
  1. Откройте cmd, перейдите в папку с вашими проектами, запустите cqtdeployer init.
  2. Должен появится файл CQtDeployer.json, откройте его.
{
    "bin": [
        "./path/to/my/file.exe",
    ],
    "qmake": "C:/path/to/my/qmake.exe",
    "clear": true,
    "libDir": "./",
    "recursiveDepth": 5,
    "qif": true
}

Если все ваши библиотеки требуются для работы ./path/to/my/file.exe т они автоматически попадут в инсталятор. если ваши библиотеки очень далеко запрятаны от расположения json файла то можете по больше выставить значение "recursiveDepth": 5,
путь "./path/to/my/file.exe", должен обязательно начинается с "." и быть относительным от json файла.

Обратите внимание на то что в Windows версии все еще нужно явно указывать путь к qmake которым был собран проект.
Исключение составляет тот факт что если qmake уже прописан у вас в окружении PATH, тогда cqtdeployer будет использовать значение из PATH.
затем сохраняете ваш файл и повторно запускаете cqtdeployer.

что касается ярлыков на библиотеки то звучит как ошибка. Собирите максимум информации о ваших дествиях и опишите ошибку на GitHub

Andrei Yankovich
  • 26 березня 2020 р. 10:29

можно еще так:

cqtdeployer -bin "path/to/my/file.exe" -qmake "C:/path/to/my/qmake.exe" -libDir "./" -recursiveDepth 5 qif

это тоже самое, просто гораздо удобнее когда инициализация будет у вас записана в файл, и вам не придется постоянно водить эту длинную строку для развертывания.

D
  • 26 березня 2020 р. 11:56

LibDir вообще не видит библиотеки. Пробовал разные попытки записи, исходя из вашего сообщения. Библиотеки лежат рядом с исполняемым файлом, json там же. Если использовать binDir вместо bin тогда библиотеки добавляются. А при установке создадутся ярлыки на все библиотеки, но не на исполняемый файл. Оно так и должно работать?

Andrei Yankovich
  • 26 березня 2020 р. 12:39
  • (відредаговано)

нет, так не должно быть, вы уверены что ваш исполняемый файл зависит от ваших библиотек ? или он подключает их как плагины?
а с ярлыками это точно ошибка, уже завел задачу на это .

Andrei Yankovich
  • 26 березня 2020 р. 12:42

@Den125 можно взгялнуть на ваш проект ?

D
  • 26 березня 2020 р. 12:51

Мой исполняемый файл не зависит от библиотек. Библиотеки подключаются при необходимости в процессе работы программы.

Andrei Yankovich
  • 26 березня 2020 р. 13:13
  • (відредаговано)

тогда это плагины )
для них есть отделный флаг extraPlugin

{
    "bin": [
        "./path/to/my/file.exe",
    ],
    "qmake": "C:/path/to/my/qmake.exe",
    "extraPlugin": [
        "./plugin1.dll",
        "./plugin2.dll",
    ],
    "clear": true,
    "libDir": "./",
    "recursiveDepth": 5,
    "qif": true
}
D
  • 26 березня 2020 р. 13:34

Это сработало, но библиотеки при установке находятся в папке plugins. А можно их вынести к исполняемому файлу? Можно ли в установщик закинуть файл не библиотеку и не исполняемый, например *.jar?

Andrei Yankovich
  • 26 березня 2020 р. 13:37

тогда переместите ваши библиотеки из extraPlugin в bin

{
    "bin": [
        "./path/to/my/file.exe",
        "./plugin1.dll",
        "./plugin2.dll",
    ],
    "qmake": "C:/path/to/my/qmake.exe",
    "clear": true,
    "libDir": "./",
    "recursiveDepth": 5,
    "qif": true
}
Andrei Yankovich
  • 26 березня 2020 р. 13:37

но появятся ярлыки, сейчас поправим

Andrei Yankovich
  • 27 березня 2020 р. 02:47

Ошибка с ярлыками в Windows исправлена в версиии 1.4.0.4

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 01:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 06:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 03:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Слідкуйте за нами в соціальних мережах