software-development
September 7, 2023

Патчим на живую: разборки с Flatpak

«Плевое дело», «тут работы на пару минут» — знакомо? Конечно знакомо, еще не родился программист, не дающий такую оценку чужим задачам направо и налево.

Рассказываю на конкретном примере чем это может закончиться.

Постановка

Существует на свете такое банальное интересное приложение Evince, универсальный просмотрщик документов разных форматов.

Разрабатывается в рамках проекта Gnome, выглядит как-то так:

Немного вру, это уже кастомная сборка (внимание на иконку)

Вообщем так получилось, что ввиду специфики Gnome, а именно их подхода к тотальному урезанию и упрощению интерфейса, в данной программе не сделали никакого способа поменять цвет фона страницы:

он либо ярко белый с черными буквами, либо черный, белыми — т. н. «ночной режим».

Вот такой вот программный фашизм.

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

Вот так и возникла эта задача, с поста на одном известном форуме.

Я конечно же решил, что столь серьезному джентельмену поправить такое — как «сходить до ветру»: быстро, просто и без последствий.

Но конечно дальше начались сплошные будни разведки разработки и разнообразные сложности.

Эти самые цвета фона страницы разработчики зашили непросредственно в код, без возможности изменения через стили оформления Gtk (графический фреймворк, на котором реализован интерфейс) или тему самого окружения Gnome.

Что уже весело.

Так что нужно было сделать правку и сборку приложения, сгенерировав еще и финальный пакет для установки.

Надо сказать что обойтись «малой кровью» — подсказками по сборке для пользователя, который все это затеял не вышло.

С одной стороны была нехватка опыта (пользователь был самым обычным пользователем, не разработчиком), с другой — оочень большая специфика этого проекта и очевидное отсутствие документации.

Так что пришлось проходить правку и сборку целиком своими силами.

Сборка проекта реализована через новомодные и стандартные для новых выпусков Gnome meson и ninja, а итоговый дистрибьютив это образ Flatpak — все «как в лучших домах Парижу», но разумеется п#здец и ужас для всех непричастных.

Все манипуляции проводились на Mageia Linux 9

Хотя первую успешную сборку evince я сделал все же на FreeBSD, по традиции.

Правка

Собственно правки кода всего две, в разных местах одного и того же файла libdocument/ev-document-misc.c, обе достаточно тривиальны.

Первая в районе 485й строки:

void
ev_document_misc_invert_surface (cairo_surface_t *surface) {
	cairo_t *cr;

	cr = cairo_create (surface);

	/* white + DIFFERENCE -> invert */
//  заменяем вот эти два вызова
//	cairo_set_operator (cr, CAIRO_OPERATOR_DIFFERENCE);
//	cairo_set_source_rgb (cr, 1., 1., 1.);
//  на вот такие:
	cairo_set_operator (cr, CAIRO_OPERATOR_DARKEN);
	cairo_set_source_rgb (cr, 0.8, 0.9098, 0.8117647);

	cairo_paint(cr);
	cairo_destroy (cr);
}

Эта правка заставит использовать «мразотно-зеленый» (не знаю как правильно он называется) цвет фона вместо черного при включении «ночного режима».

Вторая правка находится в районе 512й строки:

void
ev_document_misc_invert_pixbuf (GdkPixbuf *pixbuf)
{
	guchar *data, *p;
	guint   width, height, x, y, rowstride, n_channels;

	n_channels = gdk_pixbuf_get_n_channels (pixbuf);
	g_assert (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB);
	g_assert (gdk_pixbuf_get_bits_per_sample (pixbuf) == 8);

	/* First grab a pointer to the raw pixel data. */
	data = gdk_pixbuf_get_pixels (pixbuf);

	/* Find the number of bytes per row (could be padded). */
	rowstride = gdk_pixbuf_get_rowstride (pixbuf);

	width = gdk_pixbuf_get_width (pixbuf);
	height = gdk_pixbuf_get_height (pixbuf);
	for (x = 0; x < width; x++) {
		for (y = 0; y < height; y++) {
			/* Calculate pixel's offset into the data array. */
			p = data + x * n_channels + y * rowstride;
			/* Change the RGB values*/
			// заменяем вот это:
			//p[0] = 255 - p[0];
			//p[1] = 255 - p[1];
			//p[2] = 255 - p[2];
			// на это:
			 p[0] = 204; /* cc */
             p[1] = 232; /* e8 */
             p[2] = 207; /* cf */
		}
	}
}

Результат этих правок выглядит вот так:

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

Все счастливы и довольны, если бы не одно но:

сборка «дистрибьютива» в виде образа Flatpak.

Быстрое решение или Gnome Builder

Поскольку я не следил особо за прогрессом разработки проекта Gnome — не был в курсе о существовании аж целой специализированной IDE, которая команда создала и поддерживает для своих проектов.

С ее помощью весь процесс от скачивания исходников, внесения изменений и до генерации .flatpak файла оказался тривиальным:

  1. Открываешь Gnome builder
  2. Нажимаешь Clone repository.. и вставляешь ссылку в поле Repository URL: https://gitlab.gnome.org/GNOME/evince.git и жмешь Clone repository
  3. Делаешь свои правки
  4. Жмешь Run, чтобы собрать и проверить работоспособность
  5. Жмешь Creating flatpak bundle

Вот так выглядит открытый и собранный проект:

Если вы занимались разработкой под Mac/iOS то могли заметить знакомые очертания XCode

Повторюсь что я не знал о существовании сего замечательного инструмента, поэтому полез делать сборку и упаковку вручную.

Собственно я постоянно так делаю, еще временами ору Гойдааа! во весь голос.

Особенности профессии, что поделать.

Пох#й пляшем: долгое и сложное решение

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

git clone https://gitlab.gnome.org/GNOME/evince.git

Поскольку мы настоящие джентельмены — никаких релизных веток использовать не будем, только trunk master current main, только хардкор!

Дальше запускаем meson:

meson -Dlibhandy:examples=false -Dlibhandy:tests=false -Dlibhandy:vapi=false -Dlibhandy:glade_catalog=disabled -Dlibhandy:introspecti
on=disabled _build

Внимательно смотрим вывод, поскольку будут сыпаться ошибки об отсутствующих библиотеках — их все последовательно придется ставить в систему.

После того как meson отработает без ошибок, надо запустить ninja:

ninja -C _build all

Это уже непосредственно компиляция.

Обе полные команды сборки находятся в файле .gitlab-ci.yml откуда их можно вытащить при изменениях.

_build это название временного каталога, в который пишется результат сборки. После того как ninja отработает без ошибок, пожно попробовать запустить результат:

./_build/shell/evince

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

(evince:7168): EvinceDocument-WARNING **: 22:14:12.259: Error opening directory “/usr/local/lib64/evince/4/backends”: No such file or directory

Чтобы это обойти можно поставить «системый» evince из репозитория ОС, тогда собранный успешно подхватит эти библиотеки и запустится:

Если вы дошли до этого места — поздравляю, ибо это была лишь подготовка.

Теперь возьмемся всерьез за Flatpak.

Файлы, описывающие сборку находятся в папке build-aux/flatpak,основной это org.gnome.Evince.json и нам нужно будет его поправить.

Ключевая проблема с Flatpak заключается в том что он «слишком умный», поэтому сам выкачивает все зависимости, сам собирает и сам упаковывает.

Естественно из-за столь большого круга задач он лажает и падает.

Бери ношу по себе чтоб не падать при ходьбе.

Простой совет авторам, который они конечно никогда не услышат и не поймут. (поскольку не говорят по-русски)

Именно поэтому мы выше запускали meson и ninja вручную.

Второй важный момент в flatpak это использование репозитория git в качестве источника для сборки. Поскольку наши правки локальны, то это немедленно создает проблему:

изменения в исходниках не попадают в сборку.

Чтобы решить эту проблему и заодно разобраться со стадиями Flatpak, мы берем и выносим сборку Flatpak из проекта — в совершенно отдельную папку.

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

 /opt/work/tmp/evince-build

Копируем внутрь скрипты сборки:

cp /opt/src/evince/build-aux/*.json /opt/work/tmp/evince-build

И правим последний блок в org.gnome.Evince.json, меняем вот это:

 "sources": [
                {
                    "type": "git",
                    "branch": "main",
                    "url": "https://gitlab.gnome.org/GNOME/evince.git"
                }
            ]

на это:

"sources": [
                {
                    "type": "dir",
                    "path": "/opt/src/evince"
                }
            ]

Где в path указываем полный путь до исходников.

После этого будут подхватываться ваши правки в коде без необходимости их коммита в репозиторий.

Дальше запускаем сборку:

flatpak-builder --keep-build-dirs --user --disable-rofiles-fuse stage1 org.gnome.Evince.json

Пару слов о том откуда взялась эта команда со всеми параметрами.

Вот отсюда.

Дело в том что совсем недавно проект Gnome реализовал для себя специальный плагин для Gitlab CI, поэтому вместо явных команд сборки и упаковки в файле проекта теперь лишь вызов этого плагина:

flatpak:
    extends: .flatpak
    stage: build

    variables:
        MANIFEST_PATH: "build-aux/flatpak/org.gnome.Evince.json"
        RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo"
        FLATPAK_MODULE: "evince"
        MESON_ARGS: "-Dnautilus=false -Dgtk_doc=false"
        APP_ID: "org.gnome.Evince"
    artifacts:
        # Rewrite paths and reports to "remove" testlog.txt and junit files,
        # which do not exists in evince build, yet.
        paths:
            - "${BUNDLE}"
            - 'repo.tar'
            - '.flatpak-builder/build/${FLATPAK_MODULE}/_flatpak_build/meson-logs/meson-log.txt'
        reports:
            junit: []

Удачи разобраться, как говорится. Не зная этого момента, вызвать правильно сборку вручную не выйдет. Вот такое тут убойное веселье.

Дальше подготавливаем бандл:

flatpak build-export stage2 stage1

Создаем .flatpak файл:

flatpak build-bundle stage2 evince-patched.flatpak

В результате в корне текущего каталога будет файл evince-patched.flatpak — это и есть тот самый готовый образ, который можно распространять и ставить в другие ОС:

flatpak install evince-patched.flatpak

Поскольку это development-версия, в ее зависимостях указаны development- версии библиотек Gnome, которые выкладываются в специальном репозитории.

Без указания этого репозитория установка не пройдет:

flatpak remote-add gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo

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

Эпилог

Эта статья идеально иллюстрирует хитровы#банность сложность современного софта, где давно уже недостаточно знать один лишь язык на котором написана программа, а актуальные знания включают в себя опыт работы с линуксами, средами разработки, инструментами сборки, ядерными реакторами и чертом лысым — словом со всей возможной дичью, встречающейся на просторах ИТ-индустрии последние десять-двадцать лет.

Я потратил примерно 5 минут на саму правку, еще 10 на сборку и блин три дня горячей #бли на итоговую сборку .flatpak файла!

Имейте ввиду такие расклады при оценке сроков в ваших проектах, ибо подобная разбивка, где 5% это сама работа, а 95% — непонятная долбильня головой в стену встречается все чаще и чаще.