= Программный проект и иерархия каталогов
:title-separator: {sp}|
:category: Программирование
:tags: Linux, файлы, каталоги, программирование, cmake
:toc:
include::{l10ndir}/{lang}.adoc[]

Для операционных систем типа Linux принят стандарт
https://ru.wikipedia.org/wiki/FHS[FHS] («стандарт иерархии файловой
системы»), унифицирующий местонахождение файлов и каталогов с общим
назначением в файловой системе. Полная текущая версия стандарта
находится http://refspecs.linuxfoundation.org/fhs.shtml[здесь].

== Типы расположения проекта

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

* системная иерархия в каталоге `/usr` используется для установки
бинарных пакетов для данного дистрибутива;

* системная иерархия в каталоге `/usr/local` используется для установки
программного обеспечения системным администратором без использования
пакетов (не рекомендуется для использования из-за проблем поддержки
в актуальном состоянии);

* системная иерархия в каталоге `/opt` используется для установки
стороннего программного обеспечения. В рамках данной иерархии
предполагается, что каждый программный продукт располагается в
собственном каталоге. При таком типе сборки обычно используются
дополнительные методы (статическая компоновка, включение в состав
пакета своего набора динамических библиотек) для обеспечения работы
пакета в операционных системам с отличающимся составом библиотек
и другим циклом обновления;

* системная иерархия в домашнем каталоге пользователя не имеет
определённого стандарта, обычно производители дистрибутивов
предлагают использовать для исполняемых файлов каталоги
`$HOME/bin` или `$HOME/.local/bin`.

Система автоматизации сборки программного обеспечения https://cmake.org[CMake]
позволяет организовать окружение подобное перечисленным выше.
На этапе сборки проекта можно создать структуру каталогов,
которая будет отвечать требованиям по логическому разделению
файлов на исполняемые, заголовочные, библиотеки, файлы настроек и т.д.

== Автоматическая адаптация к текущему окружению

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

В библиотеке https://git.246060.ru/f1x1t/myxlib[myxlib] реализован
класс, который анализирует расположение и окружение исполняемого
файла и предоставляет методы для получения имён каталогов,
соответствующих текущему окружению. Названия методов и описания
возвращаемых значений приведены в таблице.

.Имена методов и описания
[cols="2m,4",options="header"]
|===
| Метод                      | Описание
| homeDirectory()            | Полный путь к домашнему каталогу текущего пользователя
| tempDirectory()            | Полный путь к каталогу с временными файлами
| userConfigDirectory()      | Полный путь к пользовательскому каталогу с файлами настройки
| userConstDataDirectory()   | Полный путь к пользовательскому каталогу с неизменяемыми файлами
| userVarDataDirectory()     | Полный путь к пользовательскому каталогу с изменяемыми файлами
| userLogDirectory()         | Полный путь к пользовательскому каталогу с журналами работы
| executableFilePath()       | Полный путь к исполняемому файлу
| systemConfigDirectory()    | Полный путь к системному каталогу с файлами настройки
| systemConstDataDirectory() | Полный путь к системному каталогу с неизменяемыми файлами
| systemVarDataDirectory()   | Полный путь к системному каталогу с изменяемыми файлами
| systemLogDirectory()       | Полный путь к системному каталогу с журналами работы
| executableFileDirectory()  | Полный путь к каталогу с исполняемым файлом
| executableFileName()       | Имя исполняемого файла
| configFilePath()           | Полный путь к файлу настройки
| configFileName()           | Имя файла настройки
| projectName()              | Имя подкаталога для проекта
|===

Пример использования:

[source,cpp]
----
#include <myx/filesystem/paths.hpp>
namespace MF = myx::filesystem;

MF::Paths& paths = MF::Paths::instance();
paths.init( QStringLiteral( "project_name" ), QStringLiteral( "conf" ) );
qDebug() << paths.systemConstDataDirectory().path();
----

== Правила выбора типа окружения

Класс `myx::filesystem::Paths` реализован в виде синглтона,
чтобы повторно не выполнять проверку окружения в разных частях программы.
Сначала определяются имена пользовательского и временного каталогов с
помощью вызовов функций https://doc.qt.io/qt-5/qdir.html#homePath[`QDir::homePath`]
и https://doc.qt.io/qt-5/qdir.html#tempPath[`QDir::tempPath`], затем
имена пользовательских каталогов для настроек, постоянных и изменяемых
данных и журналов. Эти значения не зависят от расположения исполняемого файла,
а определяются в соответствии со значениям переменных окружения `HOME`,
`TMPDIR`, `XDG_CONFIG_HOME` и `XDG_DATA_HOME`, либо устанавливаются значения,
принятые в стандартах. Пример имён каталогов для пользователя `user`
и проекта `project` приведён в таблице.

.Стандартные каталоги для текущего пользователя
[cols="4,4m,6m",options="header"]
|===
| Назначение каталога | Метод                    | Значение
| Домашний каталог    | homeDirectory()          | /home/user
| Временные файлы     | tempDirectory()          | /tmp
| Файлы настройки     | userConfigDirectory()    | /home/user/.config/project
| Неизменяемые файлы  | userConstDataDirectory() | /home/user/.local/share/project/data
| Изменяемые файлы    | userVarDataDirectory()   | /home/user/.local/share/project/lib
| Журналы работы      | userLogDirectory()       | /home/user/.local/share/project/log
|===

=== Общая проверка

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

IMPORTANT: При проверке типов иерархии всегда проверяется наличие
всех необходимых каталогов, при отсутствии хотя бы одного будет
принято решение, что файлы всех типов находятся в одном каталоге
с исполняемым.


=== Проверка на работу в иерархии `/opt`

Если полный путь к исполняемому файлу начинается с `/opt` и
содержит в себе название текущего проекта, например
`/opt/org/project/bin/application`, то выполняется проверка
на наличие сопутствующих системных каталогов. Если они присутствуют,
то принимается решение, что окружение в иерархии `/opt` сформировано правильно,
иначе делается заключение о том, что файлы всех типов находятся
в одном каталоге с исполняемым и дальнейшие проверки не выполняются.
Пример правильной структуры каталогов для данной иерархии приведён в таблице.


.Каталоги в иерархии `/opt`
[cols="4,4m,5m",options="header"]
|===
| Назначение файла / каталога | Метод                      | Значение
| Исполняемый файл            | executableFilePath()       | /opt/org/project/bin/application
| Файлы настройки             | systemConfigDirectory()    | /opt/org/project/etc
| Неизменяемые файлы          | systemConstDataDirectory() | /opt/org/project/files/data
| Изменяемые файлы            | systemVarDataDirectory()   | /opt/org/project/files/lib
| Журналы работы              | systemLogDirectory()       | /opt/org/project/files/log
|===


=== Проверка на работу в иерархии `/usr/local`

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


.Каталоги в иерархии `/usr/local`
[cols="4,4m,5m",options="header"]
|===
| Назначение файла / каталога | Метод                      | Значение
| Исполняемый файл            | executableFilePath()       | /usr/local/bin/application
| Файлы настройки             | systemConfigDirectory()    | /usr/local/etc/project
| Неизменяемые файлы          | systemConstDataDirectory() | /usr/local/share/project
| Изменяемые файлы            | systemVarDataDirectory()   | /var/lib/project
| Журналы работы              | systemLogDirectory()       | /var/log/project
|===


=== Проверка на работу в иерархии `/usr`

Если полный путь к исполняемому файлу начинается с `/usr`,
например `/usr/bin/application`, то выполняется проверка
на наличие сопутствующих системных каталогов. Если они присутствуют,
то принимается решение, что окружение в иерархии `/usr` сформировано правильно,
иначе делается заключение о том, что файлы всех типов находятся
в одном каталоге с исполняемым и дальнейшие проверки не выполняются.
Пример правильной структуры каталогов для данной иерархии приведён в таблице.


.Каталоги в иерархии `/usr`
[cols="4,4m,5m",options="header"]
|===
| Назначение файла / каталога | Метод                      | Значение
| Исполняемый файл            | executableFilePath()       | /usr/bin/application
| Файлы настройки             | systemConfigDirectory()    | /etc/project
| Неизменяемые файлы          | systemConstDataDirectory() | /usr/share/project
| Изменяемые файлы            | systemVarDataDirectory()   | /var/lib/project
| Журналы работы              | systemLogDirectory()       | /var/log/project
|===


=== Проверка на работу в домашнем каталоге

Если полный путь к исполняемому файлу начинается с `/home/user/bin` или
`/home/user/.local/bin`, например `/home/user/bin/application`, то выполняется
проверка на наличие сопутствующих системных каталогов. Если они присутствуют,
то принимается решение, что окружение в домашнем каталоге сформировано правильно,
иначе делается заключение о том, что файлы всех типов находятся
в одном каталоге с исполняемым и дальнейшие проверки не выполняются.
Пример правильной структуры каталогов для данной иерархии приведён в таблице.


.Каталоги при работе в домашнем каталоге
[cols="4,4m,6m",options="header"]
|===
| Назначение файла / каталога | Метод                      | Значение
| Исполняемый файл            | executableFilePath()       | /home/user/bin/application
| Файлы настройки             | systemConfigDirectory()    | /home/user/.config/project
| Неизменяемые файлы          | systemConstDataDirectory() | /home/user/.local/share/project/data
| Изменяемые файлы            | systemVarDataDirectory()   | /home/user/.local/share/project/lib
| Журналы работы              | systemLogDirectory()       | /home/user/.local/share/project/log
|===


=== Проверка на работу в окружении для разработки

Если исполняемый файл находится в каталоге `bin` и при этом окружение не совпадает
ни с одним из перечисленных выше, то делается предположение, что исполняемый файл
запускается из окружения, сформированного системой управления проектом, и в данный
момент идёт разработка (отладка) приложения. В этом случае целесообразно считать
системными каталогами те, которые находятся внутри иерархии каталогов программного
проекта. Если присутствуют каталоги, созданные системой управления проекта,
то принимается решение, что окружение сформировано правильно,
иначе делается заключение о том, что файлы всех типов находятся
в одном каталоге с исполняемым и на этом проверки заканчиваются.

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

.Каталоги при работе в окружении для разработки
[cols="4,4m,6m",options="header"]
|===
| Назначение файла / каталога | Метод                      | Значение
| Исполняемый файл            | executableFilePath()       | /home/user/work/project/_build/debug/bin/application
| Файлы настройки             | systemConfigDirectory()    | /home/user/work/project/_build/debug/etc/
| Неизменяемые файлы          | systemConstDataDirectory() | /home/user/work/project/_build/debug/files/data
| Изменяемые файлы            | systemVarDataDirectory()   | /home/user/work/project/_build/debug/files/lib
| Журналы работы              | systemLogDirectory()       | /home/user/work/project/_build/debug/files/log
|===


=== Расположение в одном каталоге

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


.Каталоги в неопределённой иерархии
[cols="4,4m,6m",options="header"]
|===
| Назначение файла / каталога | Метод                      | Значение
| Исполняемый файл            | executableFilePath()       | /home/user/work/project/application
| Файлы настройки             | systemConfigDirectory()    | /home/user/work/project
| Неизменяемые файлы          | systemConstDataDirectory() | /home/user/work/project
| Изменяемые файлы            | systemVarDataDirectory()   | /home/user/work/project
| Журналы работы              | systemLogDirectory()       | /home/user/work/project
|===
