Вопросы, связанные с разработкой и сборкой пакетов

Я хочу создать пакет для Fedora. Что мне следует знать?

См. здесь и здесь.

Как собрать RPM пакет в mock?

См. здесь.

Как добавить свой пакет в репозиторий Fedora и стать мейнтейнером?

См. здесь.

Что такое Koji?

Fedora Koji - это автоматизированная среда для сборки пакетов для Fedora.

Хочу внести свои правки в пакет и пересобрать его для личных нужд. Как проще это сделать?

Установим утилиты fedpkg и mock:

sudo dnf install fedpkg mock

Скачаем исходники необходимого пакета foo-bar:

fedpkg clone -a foo-bar

Перейдём в каталог с загруженными исходниками и переключимся на ветку для конкретной версии Fedora (если нужна версия из Rawhide - следует использовать master):

cd foo-bar
fedpkg switch-branch f29

Внесём свои правки, сделаем коммит в репозиторий:

git add -A
git commit -m "Description of our changes."

Запустим автоматическую сборку в mock:

fedpkg mockbuild

Как создать tarball с исходниками из Git репозитория?

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

Клонируем репозиторий источника:

git clone https://example.org/foo-bar.git

Создадим архив с исходниками:

git archive --format=tar --prefix=foo-bar-1.0.0/ HEAD | gzip > ~/rpmbuild/SOURCES/foo-bar-1.0.0.tar.gz

Здесь HEAD - указатель на актуальный коммит (вместо этого можно использовать SHA1 хеш любого коммита, а также имя тега или ветки), foo-bar - название проекта, а 1.0.0 - его версия.

Как переопределить пакет в Koji репозитория RPM Fusion?

Создание build override для репозитория f29-free:

koji-rpmfusion tag f29-free-override foo-bar-1.0-1.fc29

Удаление build override для репозитория f29-free:

koji-rpmfusion untag f29-free-override foo-bar-1.0-1.fc29

Как настроить Git для работы с почтовым сервисом Gmail?

Для того, чтобы использовать функцию git send-mail с почтовым сервисом Gmail, необходимо:

  1. включить двухфакторную аутентификацию в настройках Google аккаунта;
  2. в настройках безопасности почтового ящика Gmail разрешить использование «небезопасных приложений» (под небезопасными Google понимает любые, не поддерживающие OAuth2);
  3. там же включить доступ к почте посредством POP3 или IMAP (это активирует также и необходимый для нас протокол SMTP);
  4. в настройках безопасности сгенерировать новый пароль для приложения;
  5. указать в файле ~/.gitconfig параметры почтового сервиса;
  6. когда будет запрошен пароль, ввести созданный ранее пароль приложения.

Пример файла ~/.gitconfig для работы с почтовым сервисом Gmail:

[sendemail]
    smtpEncryption = tls
    smtpServer = smtp.gmail.com
    smtpUser = yourname@gmail.com
    smtpServerPort = 587

Правильно ли использовать dlopen для загрузки динамических библиотек в приложении?

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

  1. в каждом дистрибутиве GNU/Linux именование библиотек, особенно если у них нет чётко установленной апстримом SOVERSION константы, ложится на плечи мейнтейнеров. К примеру есть популярная libcurl. Во всех дистрибутивах она линкуется с openssl и называется libcurl.so, а в Debian и Ubuntu была переименована в libcurl-gnutls.so из-за линковки с gnutls;
  2. нет никакой гарантии, что загрузится именно необходимая версия библиотеки, имеющая необходимую функцию, а отсутствии оной приложение будет аварийно завершено с ошибкой сегментирования;
  3. если существует несколько версий библиотеки с разными SOVERSION, необходимо самостоятельно их искать на диске и подгружать с рядом хаков ибо имя libfoo.so без указанной SOVERSION в большинстве дистрибутивов представляет собой символическую ссылку и доступен лишь после установки соответствующего development пакета. Соответственно на машинах обычных пользователей он отсутствует;
  4. о библиотеках, подгружаемых динамически, не в курсе LD, а следовательно он не сможет при загрузке образа приложения подгрузить их в память;
  5. в случае корректной линковки LD перед запуском приложения осуществит автоматический поиск необходимых экспортируемых функций во всех указанных библиотеках. При их отсутствии приложение не будет запущено;
  6. при сборке пакета динамически подгружаемые через dlopen библиотеки не будут определены и прописаны в качестве зависимостей пакета, что может вызвать проблемы у пользователей и падение приложения;

Как получить полный список установленных переменных окружения в текущем терминале?

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

env

Как получить полный список установленных переменных для запущенного процесса?

Получение списка установленных переменных окружения для запущенных процессов:

cat /proc/$PID/environ

Здесь $PID - PID процесса, информацию о котором необходимо получить.

Как задать переменную окружения?

Вариант 1. Запуск процесса с заданной переменной окружения:

FOO=BAR /usr/bin/foo-bar

Вариант 2. Экспорт переменной окружения в запущенном терминале и дальнейший запуск приложения:

export FOO=BAR
/usr/bin/foo-bar

Вариант 3. Модификация директивы Exec= в ярлыке запуска приложения:

env FOO=BAR /usr/bin/foo-bar

Как правильно настроить Git для работы?

Сначала укажем своё имя и адрес электронной почты:

git config --global user.name "Your Name"
git config --global user.email email@example.org

Установим предпочитаемый текстовый редактор для работы с коммитами:

git config --global core.editor vim

Я хочу внести правки в проект. Как правильно отправить их в апстрим?

Если проект хостится на одном из популярных сервисов (GitHub, BitBucket или GitLab), сначала войдём в свой аккаунт (при осутствии создадим) и сделаем форк репозитория.

Осуществим базовую настройку Git клиента если это ещё не было сделано ранее.

Клонируем наш форк:

git clone git@github.com:YOURNAME/foo-bar.git

Создадим ветку new_feature для наших изменений (для каждого крупного изменения следует создавать отдельную ветку и ни в коем случае не коммитить в master):

git checkout -b new_feature

Внесём свои правки в проект, затем осуществим их фиксацию:

git add -A
git commit -s

В появившемся текстовом редакторе укажем подробное описание всех наших изменений на английском языке. Несмотря на то, что параметр -s является опциональным, большинство проектов требуют его использования для автоматического создания подписи вида:

Signed-off-by: Your Name <email@example.org>

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

git remote add upstream https://github.com/foo/foo-bar.git

Скачаем актуальные изменения и выполним rebase основной ветки нашего форка с апстримом:

git fetch upstream
git checkout master
git merge upstream/master

Осуществим rebase ветки с нашими изменениями с основной:

git checkout new_feature
git rebase master

Отправим наши изменения на сервер:

git push -u origin new_feature

Создадим новый Pull Request.

Безопасно ли использовать LD_PRELOAD для загрузки сторонних библиотек?

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

Создадим и скомпилируем простой пример example.c:

#include <stdio.h>

static __attribute__((constructor (200))) void bar()
{
    printf("%s", "Method bar() was called.\n");
}

static __attribute__((constructor (150))) void foo()
{
    printf("%s", "Method foo() was called.\n");
}

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

Скомпилируем и слинкуем наш пример:

gcc -shared $(rpm -E %{optflags}) -fPIC example.c -o example.so $(rpm -E %{__global_ldflags}) -lc

Внедрим нашу библиотеку в известный доверенный процесс, например whoami:

LD_PRELOAD=./example.so whoami

Оба суперглобальных метода будут немедленно исполнены с правами запускаемого приложения и изменят его вывод:

Method foo() was called.
Method bar() was called.
user1

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

Как можно активировать LTO оптимизации при сборке пакета?

Для активации LTO оптимизаций необходимо и достаточно передать параметр -flto как для компилятора (CFLAGS и/или CXXFLAGS), так и для компоновщика.

Самый простой способ сделать это - переопределение значений стандартных макросов внутри SPEC файла:

%global optflags %{optflags} -flto
%global __global_ldflags %{__global_ldflags} -flto

Если в проекте применяются статические библиотеки (в т.ч. для внутренних целей), то также необходимо переопределить ряд переменных окружения внутри секции %build:

export AR=%{_bindir}/gcc-ar
export RANLIB=%{_bindir}/gcc-ranlib
export NM=%{_bindir}/gcc-nm

Если используется система сборки cmake, то помимо этого придётся патчить манифест CMakeLists.txt, т.к. он в настоящее время не поддерживает загрузку переопределённых значений:

set(CMAKE_AR "/usr/bin/gcc-ar")
set(CMAKE_RANLIB "/usr/bin/gcc-ranlib")
set(CMAKE_NM "/usr/bin/gcc-nm")

В противном случае появится ошибка plugin needed to handle lto object.

Как вывести список установленных пакетов, от которых никто не зависит?

В настоящее время данная функциональность отсутствует в dnf «из коробки», поэтому напишем и скомпилируем небольшую программу на языке C, реализующую это средствами библиотеки libsolv.

Установим компилятор и необходимые для сборки библиотеки:

sudo dnf install gcc libsolv-devel

Создадим файл rpm-unneeded.c с исходным текстом программы:

#include <solv/pool.h>
#include <solv/poolarch.h>
#include <solv/repo_rpmdb.h>
#include <solv/solver.h>

int main(void)
{
    Pool *pool;
    Repo *rpmdb;
    Solver *solver;
    Queue q;

    pool = pool_create();
    pool_setarch(pool, NULL);
    pool_set_flag(pool, POOL_FLAG_IMPLICITOBSOLETEUSESCOLORS, 1);

    rpmdb = repo_create(pool, "@system");
    repo_add_rpmdb(rpmdb, NULL, 0);
    pool->installed = rpmdb;

    solver = solver_create(pool);
    solver_set_flag(solver, SOLVER_FLAG_KEEP_EXPLICIT_OBSOLETES, 1);
    solver_set_flag(solver, SOLVER_FLAG_BEST_OBEY_POLICY, 1);
    solver_set_flag(solver, SOLVER_FLAG_YUM_OBSOLETES, 1);

    queue_init(&q);
    solver_solve(solver, &q);
    solver_get_unneeded(solver, &q, 1);

    for (int i = 0; i < q.count; i++)
    {
        printf("%s\n", pool_solvid2str(pool, q.elements[i]));
    }

    queue_free(&q);
    pool_free(pool);

    return 0;
}

Скомпилируем и слинкуем приложение:

gcc $(rpm -E %{optflags}) -fPIC rpm-unneeded.c -o rpm-unneeded -lsolv -lsolvext

Запустим приложение ./rpm-unneeded и получим список установленных пакетов, от которых никто не зависит.