На просторах интернета уже имеются всевозможные туториалы, описывающие пошагово процесс создания deb-пакета. Однако у меня возникла потребность собрать пакет из проекта с разделяемыми библиотеками, и я решил поделиться тут своим опытом

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

Описание проекта, для которого будет создаваться deb-пакет

Назовем проект poop. Он будет имитировать авторизацию в каком-нибудь сервисе, а именно проверять наличие паролей для двух пользователей и не совпадают ли они.

Начальная структура проекта

-> /poop
	|-> Makefile
	|-> /src
		|-> input_check.h
		|-> input_check.c
		|-> login.c
		|-> login.h
		|-> input_check.c

Исходники

input_check.h

#ifndef INPUT_CHECK_H
#define INPUT_CHECK_H  

int check_input(int argc);

#endif

input_check.c

#include <stdio.h>  

int check_input(int argc) {
	int ret = argc;
	switch (ret) {
		case 1:
		printf("[%s] The poop app. "
			   "Tap TAB to make app dumb\npoop [random string]\n",
			   __FUNCTION__);
			ret = 0;
			break;
		case 2:
			break;
		default:
			printf("[%s] WTF?!\npoop [random string]\n",
					__FUNCTION__);
			ret = -1;
			break;
	}
	return ret;
}

login.h

#ifndef LOGIN_H
#define LOGIN_H  

int check_login(const char* login_to_check);  

#endif

login.c

#include <stdio.h>
#include <string.h>  

#define TYPE_1 "user_1"
#define TYPE_2 "user_2"

int check_login(const char* login_to_check) {
	int ret = 0;
	if(login_to_check != NULL) {
		if ((strcmp(login_to_check, TYPE_1) == 0) || 
			(strcmp(login_to_check, TYPE_2) == 0)) {
			printf("%s\n", login_to_check);
		} else {
			printf("[%s]: Логин не определен\n", 
					__FUNCTION__);
			ret = -2;
		}
	} else {
		printf("[%s] Пусто!\n", 
				__FUNCTION__);
		ret = -3;
	}
	return ret;
}

main.c

#include <stdio.h>
#include "input_check.h"
#include "login.h"

int main(int argc, char **argv) {
	int ret = check_input(argc);
	if (0 < ret)
		ret = check_login(argv[1]);
	return ret;
}

Makefile

# target to create first shared library
libpoopl0gin.so: src/login.c
	gcc src/login.c -o libpoopl0gin.so -shared -fPIC  

# target to create second shared library
libpoopinputch3ck.so: src/input_check.c
	gcc src/input_check.c -o libpoopinputch3ck.so -shared -fPIC  

main.o: src/main.c
	gcc -c src/main.c  

all: libpoopl0gin.so libpoopinputch3ck.so main.o
	gcc main.o -I/src libpoopl0gin.so libpoopinputch3ck.so -o poop.out  

clean:
	rm -rf *.so
	rm -rf *.o*

Сборка производится командой make all

Подготовка проекта и сборка пакета

Разделим всю работу по созданию пакета на несколько этапов:

0. Обзор зависимостей;
1. Создание манифеста;
2. Перемещение файлов;
3. Создание скрипта установки;
4. Сборка и проверка пакета.

Прежде чем приступить установим некоторые необходимые пакеты:

sudo apt install dpkg-dev devscripts equivs

А вот теперь уже приступим

Когда понял, что предстоит читать этот лонгрид

0. Обзор зависимостей

Наши собранные библиотеки имеют собственные зависимости, поэтому их нужно посмотреть. Делается это командой objdump:

~/poop$ objdump -p ./libpoopl0gin.so | grep NEEDED
  NEEDED               libc.so.6
~/poop$ objdump -p ./libpoopinputch3ck.so | grep NEEDED
  NEEDED               libc.so.6

# В нашем случае программе необходима только libc. 
# Смотрим, в каком пакете она находится:
~/poop$ dpkg -S libc.so.6
libc6:amd64: /lib/x86_64-linux-gnu/libc.so.6

1. Создание манифеста

Создаем папку:

~/poop$ mkdir -p package/DEBIAN

Затем файл манифеста

~/poop$ vi package/DEBIAN/control

И наполняем следующим содержимым:

Package: poop
Version: 1.0
Section: uncrown
Priority: optional
Depends: libc6
Architecture: amd64
Essential: no
Installed-Size: 16 # значение из результата команды du -k ./poop.out
Maintainer: doob.poop <peep@doob.poop>
Description: Check random login from random user

Это минимальный набор параметров в файле манифеста. Вот их значение:

  • Package - имя пакета;

  • Version - версия программы в пакете, будет использована при обновлении пакета;

  • Section - категория пакета, позволяет определить зачем он нужен;

  • Priority - важность пакета, для новых пакетов, которые ни с чем не конфликтуют обычно прописывают optional, кроме того доступны значения required, important или standard;

  • Depends - от каких пакетов зависит ваш пакет, он не может быть установлен, пока не установлены эти пакеты;

  • Recommends - необязательные пакеты, но тем не менее они обычно устанавливаются по умолчанию в apt;

  • Conflicts - пакет не будет установлен, пока в системе присутствуют перечисленные здесь пакеты;

  • Architecture - архитектура системы, в которой можно установить этот пакет, доступные значения: i386, amd64, all, последнее означает, что архитектура не имеет значения;

  • Installed-Size - общий размер программы после установки;

  • Maintainer - указывает кто собрал этот пакет и кто отвечает за его поддержку;

  • Description - краткое описание пакета.

2. Перемещение файлов

Создаем папку usr/bin и поместить туда исполняемый файл программы:

~/poop$ mkdir -p package/usr/bin
~/poop$ cp ./poop.out package/usr/bin

Создаем папку usr/lib для наших разделяемых библиотек

~/poop$ mkdir -p package/usr/lib
~/poop$ cp ./libpoop* package/usr/lib

3. Создание скрипта установки

Несмотря на то, что система установки пакетов очень мощная и позволяет делать многое, некоторые вещи всё же сделать нельзя. Для решения этой проблемы была предусмотрена возможность выполнять скрипты перед установкой пакета и после. Аналогично это работает для удаления пакета - перед и после. Эти скрипты называются preinst, postinst, prerm и postrm. Каждый файл просто содержит набор скриптов, которые надо выполнить. Например:

~/poop$ vi package/DEBIAN/postinst

#!/bin/bash
echo "Hello from poop installed"

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

4. Сборка и проверка пакета

Осталось собрать настроенный пакет:

~/poop$ dpkg-deb --build ./package

После завершения сборки устанавливаем его с помощью apt:

~/poop$ sudo apt install ~/package.deb
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Note, selecting 'poop' instead of './package.deb'
The following NEW packages will be installed:
  poop
0 upgraded, 1 newly installed, 0 to remove and 8 not upgraded.
Need to get 0 B/4 454 B of archives.
After this operation, 16,4 kB of additional disk space will be used.
Get:1 /home/user/Projects/poop/package.deb poop amd64 1.0 [4 454 B]
Selecting previously unselected package poop.
(Reading database ... 281406 files and directories currently installed.)
Preparing to unpack .../user/Projects/poop/package.deb ...
Unpacking poop (1.0) ...
Setting up poop (1.0) ...
Hello from poop installed

Теперь проект можно запустить:

~/poop$ poop.out user
[check_login]: Логин не определен

Конечная структура проекта

-> /poop
	|-> Makefile
	|-> /src
		|-> input_check.h
		|-> input_check.c
		|-> login.c
		|-> login.h
		|-> input_check.c
	|-> /package
		|-> /DEBIAN
		|-> /usr
			|-> /bin
				|-> poop.out
			|-> /lib
				|-> libpoopinputch3ck.so
				|-> libpoopl0gin.so

Как видно из структуры выше, добавилась папка package, в которой находятся все поддиректории и файлы, необходимые для успешной сборки deb-пакета простенького проекта

Для простоты проверки всего вышего сказанного, можно клонировать с Github уже готовый настроенный проект.

Так выглядит deb-пакет по мнению Kandinsky

Буду рад ответить на вопросы и предложения.

Полезные ссылки и материалы по теме:

Создание deb пакетов

Описание процесса сборки пакета deb