Портируем brogue на OpenBSD

В университетские годы, практически сразу после перехода на Linux, я от таких игр как Gothic 2, Morrowind и Fallout, перешел к новым развлечениям: многопользовательским текстовым MUDам и рогаликам. Так как друзей, готовых наслаждаться текстовыми битвами, у меня не нашлось, а я был слишком робок чтобы влиться в коллектив уже играющих людей, то мады пришлось забросить и остановиться на оффлайновых развлечениях.

Перепробовав несколько разных рогаликов я прикипел душой к самому красочному и простому из них - Brogue. Мне нравилось его изучать, разбираться как работают те или иные механики, нравилось исследовать этажи, ища новые свитки, оружие или хотя бы немного еды. Мне нравилось то, как игра своей случайностью создавала для меня уникальные игровые ситуации, из которых я так же уникально выбирался. Либо же уникально умирал.

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

Готовимся к работе

Все новые порты делаются для current версии OpenBSD. Обновитесь хотя бы до снапшота, чтобы ваша локальная система не сильно отличалась от той, в которую порт будут принимать.

Для работы над новым портом вам нужно дерево уже существующих. Обычно его кладут в /usr/ports. Чтобы не собирать порты под рутом и не класть их в домашнюю директорию - добавьте себя в группу wsrc.

doas user mod -G wsrc username

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

# замените на то, что выбрали выше
USER=anoncvs
HOST=mirror.osn.de

cd /usr
doas mkdir -m 775 ports
doas chown root:wsrc ports/

# запускаем именно из /usr
cvs -qd $USER@$HOST:/cvs checkout -P ports

Чтобы сэкономить время и трафик, можно вместо выкачивания всех портов через cvs предзагрузить текущий срез с какого-нибудь сервера. Тогда скрипт для выкачки будет выглядеть как-то так.

# замените на то, что выбрали выше
HOST=mirror.yandex.ru

cd /tmp
wget $HOST/pub/OpenBSD/snapshots/ports.tar.gz

cd /usr
doas tar xzf /tmp/ports.tar.gz
doas chown -R :wsrc ports/
doas chmod -R 775 ports/

У снапшотов нет .sig файла, так что придется обойтись без проверки подписи.

Чтобы cvs знал, откуда брать обновления, после скачивания портов выполните следующий скрипт, либо выставьте переменную окружения CVSROOT, либо используйте ключ командной строки -qd.

# замените на то, что выбрали выше
USER=anoncvs
HOST=mirror.osn.de

cd /usr/ports
echo "$USER@$HOST:/cvs" > CVS/Root

Чтобы не возвращаться к этой теме потом, сразу замечу, что порты надо держать в актуальном состоянии, периодически выкачивая себе новые изменения. Делается этот вот так.

cd /usr/ports
cvs -q up -Pd -A

Куда положить и как скачать

В /usr/ports много директорий, и большая часть из них - категории, содержащие порты. Так как мы портируем игру, то тут с категориями все просто, мы берем games и создаем директорию brogue внутри. Давайте скопируем в неё шаблон Makefile, который мы будем потихоньку заполнять.

mkdir -p /usr/ports/games/brogue
cp /usr/ports/infrastructure/templates/Makefile.template \
 /usr/ports/games/brogue/Makefile

Большая часть строк в Makefile закомментирована, мы заполним всё обязательное и будем раскомментировать интересующие нас строки в процессе работы над портом.

COMMENT =		roguelike game by Brian Walker with X11 support
CATEGORIES =	games

HOMEPAGE =	https://sites.google.com/site/broguegame/

MAINTAINER =	Anton Konyahin <me@konyahin.xyz>

Хоть DISTNAME и MASTER_SITES раскомментированы с самого начала, нам они не нужны, просто удалите эти строчки. Brogue живет на гитхабе и для него у нас есть очень удобный набор переменных, делающих ненужными эти две.

Обратите внимание на то, что после знака равно стоит знак табуляции. Это стандарт оформления, так что лучше его придерживаться.

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

GH_ACCOUNT =	tmewett
GH_PROJECT =	BrogueCE
GH_TAGNAME =	v1.12

PKGNAME =	brogue-${GH_TAGNAME:S/v//}

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

Здесь же мы указали имя пакета, добавив к названию игры версию из имени тега. Команда, использованная через двоеточие, заменяет (S - substitute) v на пустую строку, что в итоге даст нам просто 1.12.

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

make makesum
make checksum

Вы получите несколько предупреждений о том, что не всё заполнено в нашем Makefile, пока игнорируйте их. В конце вывода второй команды вы должны увидеть строку >> (SHA256) BrogueCE-1.12.tar.gz: OK, сигнализирующую о том, что с архивом (и подписью) всё в порядке.

После выполнения make makesum в директории с портом должен появиться файл distinfo, в нем хранится SHA256 подпись архива и его размер.

Теперь мы можем разархивировать архив командой make extract. Файлы будут сложены в /usr/ports/pobj/ИМЯ ПАКЕТА/ИМЯ АРХИВА/. Но искать его не нужно, просто сделайте make show=WRKSRC.

Следующим важным вопросом является вопрос лицензий. Для разработчиков важно, чтобы лицензия как на само ПО, так и на используемые им ресурсы, позволяла опакечивание и распространение в бинарном виде. Если лицензия этого сделать не позволяет, то пользователю придется самому собирать приложение из портов.

В случае с Brogue все достаточно просто: код распространяется под GNU Affero General Public License, а все игровые ресурсы - под Creative Commons Attribution-ShareAlike 4.0. Понять это можно ознакомившись с двумя LICENSE.txt файлами, один из которых лежит в корне проекта, а второй в /bin/assets. Эти лицензии позволяют опакечивание ПО, так что укажем это в нашем Makefile.

# Code: AGPLv3+
# Assets: CC BY-SA 4.0
PERMIT_PACKAGE =	Yes

Собираем и патчим

К счастью, Brogue для сборки использует обычный Makefile (хоть и содержащий GNU расширения). Собрать проект будет просто. Сразу укажем несколько зависимостей, проект не соберется без установленных пакетов sdl2 и sdl2-image.

USE_GMAKE =	Yes
# в рассылке посоветовали указать, так как тестов в проекте нет
NO_TEST =	Yes

# указываем, какую цель из Makefile будем собирать
ALL_TARGET =	bin/brogue

WANTLIB =	c m SDL2 SDL2_image
LIB_DEPENDS =	devel/sdl2 devel/sdl2-image

c это стандартная библиотека языка C, m - такая же математическая библиотека. В WANTLIB мы указываем библиотеки так, как указали бы их для линковки (-lm -lSDL2_image), а в LIB_DEPENDS пишем названия портов с категориями, необходимых для работы программы.

Теперь, прямо в директории порта, набираем make build. Игра собирается и мы даже можем её запустить, набрав в директории с исходниками ./brogue. Однако, при сборке мы видим некоторое количество варнингов, которые было бы неплохо исправить.

Первым делом избавимся от предупреждения о неизвестной опции компилятора. Для этого мы создадим копию Makefile с расширением .orig.port и поправим оригинал.

cd `make show=WRKSRC`
cp Makefile Makefile.orig.port

# убираем опцию из Makefile
vi Makefile

# возвращаемся директорию порта
cd -

make update-patches

Последняя команда автоматически создаст патч с правильным именем, поместит его в директорию patches и откроет на редактирование в выбранном вами $EDITOR. Не спешите закрывать файл, хорошим тоном считается указать в начале файла что и зачем вы редактировали. Например вот так.

 - drop unknown warning -Wformat-overflow=0

Index: Makefile
--- Makefile.orig
+++ Makefile
Дальше идет текст патча...

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

Flavors и флаги сборки

Если внимательно посмотреть на Makefile brogue, можно увидеть несколько флагов сборки. Самым интересным из них будет DATADIR, позволяющий указать, в какой директории игра будет искать свои ассеты. По умолчанию это текущая директория, что неприемлемо для порта, ведь исполняемый файл должен лежать в /usr/local/bin/, а необходимые приложению ресурсы в /usr/local/share/<name>.

MAKE_FLAGS +=	DATADIR="${PREFIX}/share/brogue"

Второе, на что стоит обратить внимание, это возможность собрать brogue как в графическом, так и в консольном варианте (ещё есть web версия, но её мы оставим где лежит). Порты позволяют нам собирать несколько вариантов пакета с помощью механизма flavors, и даже несколько отдельных пакетов (например для утилиты, для GUI под нее и для документации) с помощью multi packages. В данном случае нам нужен именно flavors, который мы, аналогично пакетам со схожими вариантами, назовем no_x11.

FLAVORS =	no_x11
FLAVOR ?=

Чтобы поменять флаги сборки мы будем ориентироваться на переменную FLAVOR. Для сравнения её с no_x11 мы воспользуемся модификатором M, подробнее о котором, как и о упоминавшемся выше S, можно почитать в man make.

WANTLIB =	c m

.if ${FLAVOR:Mno_x11}
WANTLIB +=	curses
MAKE_FLAGS +=	TERMINAL=YES GRAPHICS=NO
.else
WANTLIB += SDL2 SDL2_image
LIB_DEPENDS +=	devel/sdl2 devel/sdl2-image
.endif

Обратите внимание, что мы поменяли WANTLIB, оставив там только библиотеки, общие для всех вариантов пакета, а LIB_DEPENDS перенесли внутрь условия, так как консольная версия не имеет дополнительных зависимостей.

В начало добавим отдельное описание для варианта без поддержки иксов.

COMMENT-no_x11 =	roguelike game by Brian Walker

Чтобы выполнять операции сборки и пакетирования не над основным пакетом, а над вариацией без иксов, нам нужно будет выставить переменную окружения. Например, вот так env FLAVOR=no_x11 make build. Обратите внимание, что флейвор собирается в отдельной директории и WRKSRC у неё будет другой.

Добавляем свои файлы в порт

Если собрать игру сейчас и немного в неё поиграть, вы заметите что она любит создавать самые разные файлы (сохранения, записи и т.п.) прямо в текущей рабочей директории. Нет никакой возможности указать нужную директорию при сборке, такое поведение зашито в код сразу в нескольких местах. Это крайне неудобно и поначалу я пытался пропатчить все эти места так, чтобы они использовали новый флаг сборки, введенный мною. Но большое количество патчей это сложное и хрупкое решение. К счастью, в рассылке мне подсказали более элегантный способ, спасибо Omar Polo за это и многие другие улучшения моего порта.

Мы переместим исполняемый файл игры в /usr/local/libexec, где лежат все дополнительные исполняемые файлы портов, а в /usr/local/bin мы положим небольшой скрипт, который определит директорию с сохранениями для текущего пользователя, сделает её текущей и уже там запустит игру.

Дополнительные файлы для порта должны лежать в директории files. Создадим там файл brogue со следующим содержимым.

#!/bin/sh

set -e

userdir="${XDG_DATA_HOME:-$HOME/.local/share}/Brogue"

mkdir -p "$userdir"
cd "$userdir"
exec ${TRUEPREFIX}/libexec/brogue "$@"

Установка и PLIST

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

do-install:
	${INSTALL_PROGRAM} ${WRKBUILD}/bin/brogue ${PREFIX}/libexec/
	${SUBST_PROGRAM} ${FILESDIR}/brogue ${PREFIX}/bin/brogue
	${INSTALL_DATA_DIR} ${PREFIX}/share/brogue/assets
	${INSTALL_DATA} ${WRKDIST}/bin/assets/tiles.png \
		${PREFIX}/share/brogue/assets/tiles.png
	${INSTALL_DATA} ${WRKDIST}/bin/assets/tiles.bin \
		${PREFIX}/share/brogue/assets/tiles.bin
	${INSTALL_DATA} ${WRKDIST}/bin/assets/icon.png \
		${PREFIX}/share/brogue/assets/icon.png

Мы добавили новую цель, которая будет использоваться во время сборки пакета, для проведение “фейковой” установки. Наберите в директории порта make fake, а затем cd $(make show=WRKINST). Вы окажетесь в директории fake-amd64 (если вы на amd64, а не на Байкале, конечно), в которую был установлен ваш пакет. Можете убедиться, что файлы порта лежат именно там, где вы ожидаете, на следующем шаге именно фейковая установка послужит основой для описания того, что где и с какими правами должно лежать после установки на реальной системе. Больше про фейковую установку можно почитать тут.

Вернитесь в директорию порта и наберите make update-plist. Когда скрипт отработает, он сообщит вам что создал новый файл - pkg/PLIST.

bin/brogue
@bin libexec/brogue
share/brogue/
share/brogue/assets/
share/brogue/assets/icon.png
share/brogue/assets/tiles.bin
share/brogue/assets/tiles.png

В нашем случае он выглядит достаточно просто - это перечисление файлов и директорий, с одним только @bin напротив исполняемого файла. Но PLIST может больше, указывать права на файлы, группы пользователей и даже запускать какие-то программы при установке пакета. О формате и его возможностей написано здесь.

Дело осталось за малым - добавьте описание пакета в файл pkg/DESCR и проверьте, не забыли ли вы чего, запустив /usr/ports/infrastructure/bin/portcheck и make port-lib-depends-check. Если всё хорошо, то смело набирайте make package, пора поставить пакет в вашу систему и хорошо всё потестировать.

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

export PKG_PATH=/usr/ports/packages/amd64/all/
doas pkg_add -D unsigned brogue

Если все работает хорошо и стабильно - пакет ставится и удаляется без проблем, сохранения работают и лишние файлы нигде не появляются, поздравляю - у вас есть ваш собственный пакет. Если вы хотите осчастливить им мир - обратитесь в рассылку ports@openbsd.org. Если на ваш порт не обращают внимания не расстраивайтесь, их поддержкой занимаются волонтеры и им может банально не хватать времени, чтобы уследить за всем. Я пинговал рассылку два раза, с промежутком в неделю между каждым пингом, прежде чем смог набрать два ok, требуемых для принятия нового пакета.

Последнее о чем хочется упомянуть - используйте make clean, если исправленная вами ошибка не уходит или происходит что-то странное. Эту команду можно использовать с аргументами, мне очень помогал make clean=package пока я разбирался со сборкой пакета. Ну а если ничего не помогает, делайте make clean=all.

Из полезных ресурсов можно упомянуть: