Pull to refresh

Разработка приложений для Meego Harmattan

Reading time 14 min
Views 5.3K
Этот пост участвует в конкурсе „Умные телефоны за умные посты“.

image
В данной статье хотелось бы поделится с Хабрасообществом своим опытом по разработке софта с использованием QtComponents'ов на примере Meego Harmattan'а. Писать мы будем редактор заметок с синхронизацией средствами Ubuntu One.



Вся разработка будет вестись при помощи scratchbox'а, он имеет некоторые преимущества в сравнении с madde, но работает исключительно в linux системах.
Среди ключевых преимуществ хочется отметить то, что сборка производится в chroot'е и в случае armel для эмуляции используется qemu. Условия максимально приближены к боевым. Это позволяет избежать дополнительной возни с настройкой кросскомпиляции. Дополнительным плюсом является наличие apt-get'а, способного установить все зависимости, необходимые для сборки, что несомненно понадобится при написании приложения сложнее, чем helloworld.

Установка и настройка scratchbox'а



Для того, чтобы установить scratchbox нужно скачать и запустить от рута этот скрипт и в дальнейшем следовать его указаниям.
# ./harmattan-sdk-setup.py

После установки необходимо перелогинится, чтобы пользователь был успешно добавлен в группу sbox.
Запускать scratchbox мы будем с помощью команды:
$ /scratchbox/login

Если установщик правильно отработал, то должно появится приглашение примерно следующего содержания:
[sbox-HARMATTAN_ARMEL: ~] > 

Если login ругается, то попробуйте выполнить скрипт run_me_first.sh, лежащий в корне scratchbox'а. Нужный таргет можно выбрать с помощью sb_menu. Остальное руководство по использованию scratchbox'а можно найти здесь.

Создание cmake проекта



В качестве сборщика я использую не привычный qmake, а более мощный cmake, который умеет искать зависимости, имеет кучу опций настройки и гораздо лучше подходит для кроссплатформенной разработки. В данной статье я не буду сильно углубляться в разбор системы сборки, поэтому для лучшего понимания рекомендую прочесть эту статью.
Единственный минус в том, что cmake не умеет Symbian, поэтому об этой платформе пока можно забыть или же написать вручную специальный проект для сборки именно под эту платформу. Со всеми остальными cmake справляется с легкостью, поэтому в дальнейшем я планирую портировать это приложение на настольные системы и, возможно, на Андроид или даже на iOS.
Проект состоит из некоторого количества зависимых библиотек, которые подключены при помощи git submodule к основному репозиторию, для каждой из них написан свой cmake проект. Все они лежат в каталоге 3rdparty и подключены к основному проекту, поэтому сборка идёт сразу с основными зависимостями, которых нет в репозиториях harmattan'а.

Список 3rdparty библиотек:
  • QOauth — реализация протокола Oauth на Qt
  • k8json — очень быстрый парсер JSON
  • QmlObjectModel — Класс, реализующий модель — список обьектов

Помимо этого есть ещё внешние библиотеки, необходимые для сборки, но присутствующие в основных репах Harmattan'а, к ним относится qca, давайте сразу её установим, а также установим cmake:
[sbox-HARMATTAN_ARMEL: ~] > apt-get install libqca2-dev cmake

Для того, чтобы её можно было использовать необходимо написать специальный cmake файл, который бы смог найти каталог с заголовочными файлами библиотеки и сам файл библитеки для того, чтобы с ним слинковаться.
include(FindLibraryWithDebug)
if(QCA2_INCLUDE_DIR AND QCA2_LIBRARIES)
    # in cache already
    set(QCA2_FOUND TRUE)
else(QCA2_INCLUDE_DIR AND QCA2_LIBRARIES)
    if(NOT WIN32)
        find_package(PkgConfig)
        pkg_check_modules(PC_QCA2 QUIET qca2)
        set(QCA2_DEFINITIONS ${PC_QCA2_CFLAGS_OTHER})
    endif(NOT WIN32)

    find_library_with_debug(QCA2_LIBRARIES
        WIN32_DEBUG_POSTFIX d
        NAMES qca
        HINTS ${PC_QCA2_LIBDIR} ${PC_QCA2_LIBRARY_DIRS} ${QT_LIBRARY_DIR})

    find_path(QCA2_INCLUDE_DIR QtCrypto
        HINTS ${PC_QCA2_INCLUDEDIR} ${PC_QCA2_INCLUDE_DIRS} ${QT_INCLUDE_DIR}}
        PATH_SUFFIXES QtCrypto)

    include(FindPackageHandleStandardArgs)
    find_package_handle_standard_args(QCA2 DEFAULT_MSG QCA2_LIBRARIES QCA2_INCLUDE_DIR)
    mark_as_advanced(QCA2_INCLUDE_DIR QCA2_LIBRARIES)
endif(QCA2_INCLUDE_DIR AND QCA2_LIBRARIES)

В таком же стиле написан поиск большинства зависимостей. Для систем с pgkconfig'ом, к которым относится и Harmattan всё просто и ясно, для систем, где его нет, будем искать в каталоге $QTDIR. В случае, если cmake автоматически не нашел библиотеку, он предложит вручную задать переменные QCA2_INCLUDE_DIR QCA2_LIBRARIES. Такой подход здорово облегчает жизнь на системах, в которых отсутствует менеджер пакетов.
В cmake'е есть переменные, которые позволяют определить платформу, на которой собирается та или иная программа, например:
if(WIN32)
....
elseif(APPLE)
 ...
elseif(LINUX)
...
endif()

К сожалению, cmake ничего не знает про Harmattan, самым простым решением является запуск cmake'а с ключем -DHARMATTAN=ON. Теперь у нас определена переменная HARMATTAN, и можно писать подобные вещи:
if(HARMATTAN)
	add_definitions(-DMEEGO_EDITION_HARMATTAN) #дефайн для компилятора, без него приложение не будет разворачиваться на весь экран.
endif()

С помощью этих же переменных можно определять, какая именно реализация GUI будет устанавливаться.
if(HARMATTAN)
	set(CLIENT_TYPE meego)
	message(STATUS "Using meego harmattan client")
else()
	set(CLIENT_TYPE desktop)
	list(APPEND QML_MODULES QtDesktop)
	message(STATUS "Using desktop client")
endif()
set(QML_DIR "${CMAKE_CURRENT_SOURCE_DIR}/qml/${CLIENT_TYPE}")
...
install(DIRECTORY ${QML_DIR} DESTINATION ${SHAREDIR}/qml)

Для разработки большую часть времени будет достаточно QtSDK с harmattan quick components и ключа -DHARMATTAN при сборке. В scratchbox'е имеет смысл собирать уже более-менее конечные версии.

С++ плагин, реализующий Tomboy notes API



Сам API я решил вынести в отдельный qml модуль, который будет доступен через директиву import. Сделано это для удобства создания множества различных реализаций GUI интерфейса.
Самым сложным в процессе разработки оказалось реализовать авторизацию средствами OAuth, в процессе которой было перебрано несколько различных реализаций библиотек и на данный момент я остановился на QOauth, которая конечно не идеальна, но является вполне рабочей. На хабре есть статья с описанием этой библиотеки, поэтому сразу перейдем к решению насущных проблем. Перво-наперво нам нужно получить тот самый вожделенный token.
Дело это не хитрое, просто посылаем запрос на адрес и ждём, когда же нам прилетит запрос на basic авторизацию по https:
UbuntuOneApi::UbuntuOneApi(QObject *parent) :
	QObject(parent),
	m_manager(new QNetworkAccessManager(this)),
	m_oauth(new QOAuth::Interface(this))
{
...
	connect(m_manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
			SLOT(onAuthenticationRequired(QNetworkReply*,QAuthenticator*)));
}
...
	void UbuntuOneApi::requestToken(const QString &email, const QString &password)
{
	m_email = email;
	m_password = password;

	QUrl url("https://login.ubuntu.com/api/1.0/authentications");
	url.addQueryItem(QLatin1String("ws.op"), QLatin1String("authenticate"));
	url.addQueryItem(QLatin1String("token_name"), QLatin1Literal("Ubuntu One @ ") % m_machineName);

	qDebug() << url.toEncoded();

	QNetworkRequest request(url);
	QNetworkReply *reply = m_manager->get(request);
	reply->setProperty("email", email);
	reply->setProperty("password", password);

	connect(reply, SIGNAL(finished()), SLOT(onAuthReplyFinished()));
}
...
void UbuntuOneApi::onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth)
{
	auth->setUser(reply->property("email").toString());
	auth->setPassword(reply->property("password").toString());
}

Как вы могли заметить, для авторизации используется стандартный для QNetworkAccessManager'а сигнал authenticationRequired, а логин и пароль я просто запоминаю обычными пропертями. Удобно и не засоряет интерфейс лишними деталями.
По завершению в reply должен прийти ответ в json формате, который содержит искомый токен и прочую важную информацию. Тут-то нам и понадобится библиотека k8json.
	QNetworkReply *reply = static_cast<QNetworkReply*>(sender());
	QVariantMap response = Json::parse(reply->readAll()).toMap();
	if (response.isEmpty()) {
		emit authorizationFailed(tr("Unable to recieve token"));
	}

	m_token = response.value("token").toByteArray();
	m_tokenSecret = response.value("token_secret").toByteArray();
	m_oauth->setConsumerKey(response.value("consumer_key").toByteArray());
	m_oauth->setConsumerSecret(response.value("consumer_secret").toByteArray());

	QUrl url("https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/" + reply->property("email").toString());
	connect(get(url), SIGNAL(finished()), SLOT(onConfirmReplyFinished()));

Следующим шагом будет отправка подтверждения того факта, что мы получили токен (обратите внимание на последнюю строчку). В результате нам должен прийти ответ ok.
void UbuntuOneApi::onConfirmReplyFinished()
{
	QNetworkReply *reply = static_cast<QNetworkReply*>(sender());
	QByteArray data = reply->readAll();
	if (data.contains("ok")) {
		emit hasTokenChanged();

Если такое слово есть в ответе, то всё, можно радостно прыгать и посылать сигнал о том, что токен наконец получен и можно начинать работать с заметками, но не тут-то было! Для совместимости с tomboy api сервер заметок требует авторизацию посредством веб браузера. Пока мне не удалось обойти эту проблему и, скрипя зубами, мне пришлось добавить в приложение webkit окошко, которое содержит кнопку «разрешить данному пользователю доступ к заметкам». Этому webkit окошку мы даем указатель на наш QNetworkAccessManager и по успешному завершению авторизации он станет обладателем заветных cookies с данными, необходимыми для авторизации.
А чтобы пользователю по новой не пришлось вбивать логин и пароль, мы заполним эти поля через DOM дерево.
		QWebFrame *frame = page()->mainFrame();
		QWebElement email = frame->findFirstElement("#id_email");
		email.setAttribute("value", m_email);
		QWebElement pass = frame->findFirstElement("#id_password");
		pass.setAttribute("value", m_password);

		QWebElement submit = frame->findFirstElement("#continue");
		submit.setFocus();

Не забудем сохранить полученные кукисы, мы же хотим быть слишком навязчивыми.
void Notes::onWebAuthFinished(bool success)
{
	if (success) {
		QNetworkCookieJar *jar = m_api->manager()->cookieJar();
		QList<QNetworkCookie> cookies = jar->cookiesForUrl(m_apiRef);
		QSettings settings;
		settings.beginWriteArray("cookies", cookies.count());
		for (int i = 0; i != cookies.count(); i++) {
			settings.setArrayIndex(i);
			settings.setValue("cookie", cookies.at(i).toRawForm());
		}
		settings.endArray();
		sync();
	}
}

Для того, чтобы сервер успешно обрабатывал наши запросы нужно, чтобы они в заголовке содержали полученный нами ранее токен. Тут нам и пригодится QOauth.
QNetworkReply *UbuntuOneApi::get(const QUrl &url)
{
	QByteArray header = m_oauth->createParametersString(url.toEncoded(), QOAuth::GET, m_token, m_tokenSecret,
														QOAuth::HMAC_SHA1, QOAuth::ParamMap(),
														QOAuth::ParseForHeaderArguments);

	QNetworkRequest request(url);
	request.setRawHeader("Authorization", header);
	return m_manager->get(request);
}

Теперь с легким сердем можно приступать к реализации tomboy api.
Для простоты работы из qml'я, я каждую заметку решил представить отдельным QObject'ом, а список заметок реализовал через QObjectListModel, реализацию которой нашел на просторах qt labs'ов. У каждой заметки свой guid, зная который можно с ней работать. Guid генерируетя на клиентской стороне, для этого в Qt есть соответствующие методы, находящиеся в классе QUuid, поэтому при конструировании новой заметки нужно сгенерировать для неё уникальный идентификатор, по которому мы будем обращаться к ней в дальнейшем.
Note::Note(Notes *notes) :
	QObject(notes),
	m_notes(notes),
	m_status(StatusNew),
	m_isMarkedForRemoral(false)
{
	QUuid uid = QUuid::createUuid();
	m_guid = uid.toString();
	m_createDate = QDateTime::currentDateTime();
}

Основные действия с заметками:
  • Синхронизировать заметки с сервером
  • Добавить новую заметку
  • Обновить заметку
  • Удалить заметку

Исходя из этих действий и будем проектировать API, в модели заметок сделаем метод sync, а в самой заметке методы save, remove. Ну и конечно реализуем свойства title и content:
class Note : public QObject
{
	Q_OBJECT

	Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
	Q_PROPERTY(QString content READ content WRITE setContent NOTIFY textChanged)
	Q_PROPERTY(int revision READ revision NOTIFY revisionChanged)
	Q_PROPERTY(Status status READ status NOTIFY statusChanged)
	Q_PROPERTY(QDateTime createDate READ createDate NOTIFY createDateChanged)
...

Неплохой идеей также было бы добавить свойство статуса заметки, который можно было бы использовать в states'ах в qml'е.
	Q_ENUMS(Status)
public:
	enum Status
	{
		StatusNew,
		StatusActual,
		StatusOutdated,
		StatusSyncing,
		StatusRemoral
	};

Для этого мы используем волшебный макрос Q_ENUMS, который генерирует метаинформацию для перечислений. Теперь в qml коде можно получать их численное значение и сравнивать между собой.
            State {
                name: "syncing"
                when: note.status === Note.StatusSyncing

Удобно, читабельно и быстро. Всё-таки сравниваются числа, а не строки!
По умолчанию в QObjectListModel к элементу модели из делегата можно обращаться по имени object, но меня это не очень устраивает, поэтому я просто унаследовался от модели и поменял имя для роли ObjectRole на note.
NotesModel::NotesModel(QObject *parent) :
	QObjectListModel(parent)
{
	QHash<int, QByteArray> roles;
	roles[ObjectRole] = "note";
	setRoleNames(roles);
}


А теперь я рассмотрю создание самого qml модуля. Для того, чтобы нашу реализацию api можно было использовать через import в qml мы должны в нашем модуле создать класс, унаследованный от QDeclarativeExtensionPlugin и реализовать в нем метод registerTypes, который бы зарегистрировал все наши методы и классы.
void QmlBinding::registerTypes(const char *uri)
{
	Q_ASSERT(uri == QLatin1String("com.ubuntu.one"));

	qmlRegisterType<UbuntuOneApi>(uri, 1, 0, "Api");
	qmlRegisterType<ProgressIndicatorBase>(uri, 1, 0, "ProgressIndicatorBase");

	qmlRegisterUncreatableType<Notes>(uri, 1, 0, "Notes", tr("Use Api.notes property"));
	qmlRegisterUncreatableType<Account>(uri, 1, 0, "Account", tr("Use Api.account property"));
	qmlRegisterUncreatableType<Note>(uri, 1, 0, "Note", tr(""));
	qmlRegisterUncreatableType<NotesModel>(uri, 1, 0, "NotesModel", tr(""));
}

Q_EXPORT_PLUGIN2(qmlbinding, QmlBinding)

Вы наверное обратили внимание на assert и хотите спросить. А откуда же берётся это самое uri? А берётся оно из названия каталога, в котором лежит наш модуль. То есть Qt будет искать наш модуль в:
$QML_IMPORTS_DIR/com/ubuntu/one/

Но и это ещё не всё. Чтобы Qt нашла и заимпортила наш модуль нужно, чтобы в директории лежал правильно составленный файл qmldir, в котором перечислены бинарные плагины, qml и js файлы.
plugin qmlbinding


Разработка qml интерфейса для Meego Harmattan



Основной большинства приложений на Meego является элемент PageStackWindow, который, как это не странно, являет собой стек страниц. Страницы добавляются в стек при помощи метода push, а извлекаются при помощи pop'а. Одна из страниц должна быть назначена как исходная. У каждой страницы может быть свой собственный тулбар. Можно же нескольким страницам назначать один и тот же.
import QtQuick 1.1
import com.nokia.meego 1.0
import com.ubuntu.one 1.0 //наш искомый модуль с notes API

PageStackWindow {
	id: appWindow
	initialPage: noteListPage

	Api { //обьект, реализующий API
		id: api
		Component.onCompleted: checkToken()
		onHasTokenChanged: checkToken()

		function checkToken() {
			if (!hasToken)
				loginPage.open();
			else
				api.notes.sync();
		}
	}
...

Теперь давайте создадим все нужные нам страницы а также стандартный toolbar, в котором будет кнопка добавить заметку и меню с действиями:
...
	LoginPage {
		id: loginPage
		onAccepted: api.requestToken(email, password);
	}

	NoteListPage {
		id: noteListPage
		notes: api.notes
	}

	NoteEditPage {
		id: noteEditPage
	}

	AboutPage { id: aboutPage }
	ToolBarLayout {
		id: commonTools
		visible: true

		ToolIcon {
			iconId: "toolbar-add"
			onClicked: {
				noteEditPage.note = api.notes.create();
				pageStack.push(noteEditPage);
			}
		}
		ToolIcon {
			platformIconId: "toolbar-view-menu"
			anchors.right: (parent === undefined)? undefined: parent.right
			onClicked: (menu.status === DialogStatus.Closed)? menu.open(): menu.close()
		}
	}

	Menu {
		id: menu
		visualParent: pageStack
		MenuLayout {
			MenuItem {
				text: qsTr("About")
				onClicked: {menu.close(); pageStack.push(aboutPage)}
			}
			MenuItem {
				text: qsTr("Sync")
				onClicked: api.notes.sync();
			}
			MenuItem {
				text: api.hasToken ? qsTr("Logout") : qsTr("Login")
				onClicked: {
					if (api.hasToken)
						api.purge();
					else
						loginPage.open();
				}
			}
		}
	}


Теперь рассмотрим что же из себя представляет отдельная страница на примере NoteListPage, реализация которой лежит в NoteListPage.qml:
import QtQuick 1.1
import com.nokia.meego 1.0
import com.ubuntu.one 1.0

Page {
	id: noteListPage
	property QtObject notes: null
	tools: commonTools //тот самый тулбар, обьявленный в main.qml

	PageHeader { //Красивый оранжевый заголовок. По сути представляет из себя обычный оранжевый прямоугольник с текстом
		id: header
		text: qsTr("Notes:")
	}

	ListView {
		id: listView
		anchors.top: header.bottom
		anchors.left: parent.left
		anchors.right: parent.right
		anchors.bottom: parent.bottom
		anchors.margins: 11
		clip: true
		focus: true
		model: notes.model
		delegate: ItemDelegate {
			title: note.title //обращение к описанным выше свойствам заметки
			subtitle: truncate(note.content, 32)
			onClicked: {
				noteEditPage.note = note;
				pageStack.push(noteEditPage); //добавляем страничку в стек
			}

			function truncate(str, n, suffix) {
				str = str.replace(/\r\n/g, "");
				if (suffix === undefined)
					suffix = "...";
				if (str.length > n)
					str = str.substring(0, n) + suffix;
				return str;
			}
		}
	}

	ScrollDecorator {
		flickableItem: listView 
	}

}

В результате получится такая милая страничка:


Для окошка логина я использовал объект Sheet, который представляет из себя страничку, выезжающую сверху. Обычно с помощью него у пользователя запрашивают какую либо информацию.
import QtQuick 1.0
import com.nokia.meego 1.0
import "constants.js" as UI //еще один интересный финт ушами - js файл с константами. Заодно можно увидеть способ реализации namespace'ов в qml'е.

Sheet {
	id: loginPage
	property alias email: loginInput.text //альясы. На самом деле, теперь при обращении к этому свойству мы обращаемся к свойству loginInput.text
	property alias password: passwordInput.text

	content: Column { //содержимое sheet'а
		anchors.topMargin: UI.MARGIN_DEFAULT
		anchors.horizontalCenter: parent.horizontalCenter

		Image {
			id: logo
			source: "images/UbuntuOneLogo.svg"
		}

		Text {
			id: loginTitle

			width: parent.width
			text: qsTr("Email:")
			font.pixelSize: UI.FONT_DEFAULT_SIZE
			color: UI.LIST_TITLE_COLOR
		}

		TextField {
			id: loginInput
			width: parent.width
		}

		Text {
			id: passwordTitle

			width: parent.width
			text: qsTr("Password:")
			font.pixelSize: UI.FONT_DEFAULT_SIZE
			color: UI.LIST_TITLE_COLOR
		}

		TextField {
			id: passwordInput
			width: parent.width
			echoMode: TextInput.Password
		}
	}
	acceptButtonText: qsTr("Login")
	rejectButtonText: qsTr("Cancel")
}


Выглядеть всё это великолепие будет вот так:

На страничках редактирования и about нужно реализовать кнопку назад, которая бы возращала нас к списку заметок. Для этого commonTools уже не очень подходит, нужны свои тулбары:
	ToolBarLayout {
		id: aboutTools
		visible: true
		ToolIcon {
			iconId: "toolbar-back"
			onClicked: {
				pageStack.pop()
			}
		}
	}


Иконка запуска



Чтобы у приложения появилась иконка запуска создадим знакомый всем линуксоидам .desktop файл:
[Desktop Entry]
Name=ubuntuNotes
Name[ru]=ubuntuNotes
GenericName=ubuntuNotes 
GenericName[ru]=ubuntuNotes
Comment=Notes editor with sync
Comment[ru]=Редактор заметок с синхронизацией
Exec=/usr/bin/single-instance /opt/ubuntunotes/bin/ubuntuNotes %U
Icon=/usr/share/icons/hicolor/80x80/apps/ubuntuNotes.png
StartupNotify=true
Terminal=false
Type=Application
Categories=Network;Qt;

Обратите внимание на секцию Exec: таким образом мы говорим, что приложение не может быть запущено несколько раз. Если мы хотим чтобы у приложения был красивый сплеш, то можно использовать утилиту invoker.
Exec=/usr/bin/invoker --splash=/usr/share/apps/qutim/declarative/meego/qutim-portrait.png --splash-landscape=/usr/share/apps/qutim/declarative/meego/qutim-landscape.png --type=e /usr/bin/qutim

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

Сборка deb пакета



Для сборки используется стандартный dpkg-buildpackage и обычный debian, который для удобства называется debian_harmattan, а перед непосредственно сборкой выставляется симлинк debian_harmattan > debian. Секция control стандартная для debian пакетов и ее создание уже подробно было описано во многих статьях на Хабре. Рекомендую к прочтению эту серию статей.

Содержимое control файла:
Source: ubuntunotes
Section: user/network
Priority: extra
Maintainer: Aleksey Sidorov <gorthauer87@ya.ru>
Build-Depends: debhelper (>= 5),locales,cmake, libgconf2-6,libssl-dev,libxext-dev,libqt4-dev,libqca2-dev,libqca2-plugin-ossl, libqtm-dev
Standards-Version: 3.7.2

Package: ubuntunotes
Section: user/network
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends},libqca2-plugin-ossl
Description: TODO
XSBC-Maemo-Display-Name: ubuntuNotes
XSBC-Bugtracker: https://github.com/gorthauer/ubuntu-one-qml

Package: ubuntunotes-dbg
Section: debug
Priority: extra
Architecture: any
Depends: ${misc:Depends}, qutim (= ${binary:Version})
Description: Debug symbols for ubuntuNotes
 Debug symbols to provide extra debug info in case of crash.

Для ведения changelog'а не лишним было бы установить прогу dch из пакета devscripts. Использовать же ее очень просто:
$ dch - i

rules файл, благодаря использованию debhelper'ов, оказался уж очень простым:
#!/usr/bin/make -f
%:
	dh $@ 
override_dh_auto_configure:
	dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/opt/ubuntunotes -DHARMATTAN=ON
override_dh_auto_install:
	dh_auto_install --destdir=$(CURDIR)/debian/ubuntunotes

Этот же файл подойдет почти для любого проекта с минимальными изменениями.
Сборка пакета тоже тривиальна:
$ ln -s ./debian_harmattan ./debian
$ dpkg-buildpackage -b 


Заключение



Теперь можно спокойно устанавливать и запускать получившийся пакет и наслаждаться быстрым и отзывчивым интерфейсом. Исходные коды для самостоятельной сборки можно скачать на гитхабе. Там же лежит собранный deb пакет. Я надеюсь эта статья поможет начинающим разработчикам под Harmattan и не только быстрее начать писать свои первые приложения. В будущем я, возможно, постараюсь получше осветить Хабрасообществу тонкости работы с cmake'ом, многие уже жаловались на недостаток статей про него.
Tags:
Hubs:
+40
Comments 9
Comments Comments 9

Articles