# #include в Аркадии


## Общие соображения

Наша сборочная система не требует явного перечисления зависимостей по заголовкам, а сама парсит их.

* Без запуска препроцессора.
* Каждый файл парсится однократно. **Каждый хедер тоже парсится ровно один раз.**
* `#ifdef` игнорируются.
* Каждый встреченный include *резолвится* (превращается в файловую зависимость) тоже однократно.

В то же время:
* Препроцессор парсит и резолвит инклуды для каждой отдельной компиляции каждого .cpp-файла. **Рекурсивно для всех включённых хедеров**.
* Полноценный резолвинг (как у препроцессора) зависит от набора [`ADDINCL`](../common/macros.md#addincl) (`-I`).
* [`ADDINCL`](../common/macros.md#addincl) определяются на уровне модулей.
* **Мы не поддерживаем неоднозначность резолвинга между модулями.**

Для нас [`ADDINCL`](../common/macros.md#addincl) порождают неоднозначность (можно получить разный резолвинг одного и того же `#include` в контексте разных модулей), которая выливается в потерянные зависимости и *мигание уидов*.

Настраивая инклуды нам надо добиться двух целей:
1. При сборке должен браться тот инклуд, в который мы целимся (нельзя допускать, чтобы в сборке зацепился неправильный инклуд).
2. В построении зависимостей ymake надо чтобы не потерялась реально существующая зависимость. Мы допускаем избыточность зависимостей, но недостача - это всегда проблема.

{% note alert %} 

В построении зависимостей ymake однозначность (в пределах одной сборки) важнее точности: лишние зависимости, если они обеспечивают однозначность, лучше точных, но мигающих зависимостей.

{% endnote %}

## Проблемы и решения

Если `#include` не резолвится (ya make сообщает о -WBadIncl), есть следующие пути исправления:

1. **[direct]** Указать в `#include` путь от корня Аркадии. Самый надежный и правильный способ. Остальные нужно применять только если:
   - Заголовка нет в Аркадии. 
   - Модификация исходного кода нежелательна, т.е. это либо contrib, либо проект переезжает в Аркадию.  После переезда в разумные сроки нужно избавиться от таких заголовков.
   - Есть несколько вариантов резолвинга в Аркадии. 
  
2. **[error]** Если заголовок не используется (например, он под `#if 0`), то отключить его с ошибкой: `#error #include <file>`. Даже если он есть или будет в Аркадии.

3. **[addincl]** Добавить в ya.make [`ADDINCL`](../common/macros.md#addincl) на директорию с заголовком. 
   - Если такой `#include` встречается в публичном заголовке (предназначенном для использования извне текущего проекта), то использовать [`ADDINCL(GLOBAL ...)`](../common/macros.md#addincl).
   - Надо стараться не делать [`ADDINCL`](../common/macros.md#addincl) за пределы своего проекта. Вместо [`ADDINCL`](../common/macros.md#addincl) на чужие заголовки надо делать [`PEERDIR`](../common/macros.md#peerdir) на этот проект, а в нем самом прописывать [`ADDINCL(GLOBAL ...)`](../common/macros.md#addincl). [Стандартизация ADDINCL в contrib](https://st.yandex-team.ru/IGNIETFERRO-952). Контрибы являются исключением из этого правила, т.к. там цель минимизации [`ADDINCL(GLOBAL ...)`](../common/macros.md#addincl) конфликтует с желанием не менять код контрибов для облегчения обновления. Про include в контрибах см. [раздел ниже](#contrib). 
   - [`ADDINCL`](../common/macros.md#addincl) могут быть разными в различных платформах, но в рамках одной платформы должны быть одинаковыми, чтобы не порождать мигающих уидов. Поэтому условные [`ADDINCL`](../common/macros.md#addincl) для `OS_LINUX` или `OS_WINDOWS` разрешены, а для `PYTHON2` или `PYTHON3` - нет. 

4. **[sysincl]** Добавить новое правило резолвинга в один из конфигов build/sysincl/*.yml и ymake.core.conf. Нужно применять, если заголовок используется не из Аркадии, а из `OS SDK`, `CUDA Toolkit`, `Intel FPGA SDK`, etc. В этом случае не создается зависимости от файлов Аркадии. Также может использоваться для указания одновременно нескольких целей резолвинга. Подробнее в [разделе ниже](#sysincl).

5. **[proxy]** Если в Аркадии есть более одного заголовка, которые могут быть использованы в `#include` под разными условиями в рамках одной платформы, то надо делать прокси-заголовок с условными `#include` всех вариантов и настраивать [`ADDINCL`](../common/macros.md#addincl) на него. Пример: python 2/3. 

   {% note warning %}

   Любая такая ситуация требует пристального внимания с тем, чтобы условная компиляция распространялась на всё дерево до конечной цели (программы/сошки/теста/пакета) иначе результат сборки может быть некорректным.
   
   {% endnote %}

6. **[ignore]**

   {% note alert "DEPRECATED: использование этого механизма запрещено!" %}

   Полностью спрятать заголовок от системы сборки можно с помощью комментария `#include <file> // Y_IGNORE`. Использование этого способа крайне не рекомендуется. Это прямой путь к потерянным зависимостям. Вместо него нужно использовать **2. [error]** или **3. [sysincl]**.

   {% endnote %}

### #include через #define

В настоящий момент парсер ymake такие `#include` игнорирует. Для того чтобы не потерять зависимости можно написать вот такой блок
```
#if 0
#include <path/to/target1.h>
#include <path/to/target1.h>
#endif
```

Когда мы научим наш парсер не игнорить такие `#include`, то можно будет пользоваться механизмом **[sysincl]**.


## Аркадийный код

Для родного аркадийного кода рекомендуется указание путей от корня Аркадии в `#include`. [`ADDINCL`](../common/macros.md#addincl) в аркадийном коде быть не должно. Избавиться от них вам поможет утилита `ya fix-includes` с опцией `--resolve`, которая приводит инклуды к аркадийному стилю.

Допустимы так же локальные `#include "file.h"`. Про них стоит помнить:
* Относительные пути плохо сочетаются с `#pragma once`, например https://st.yandex-team.ru/IGNIETFERRO-1177
* Генерируемые при сборке файлы (например `.pb.h`) складываются в отдельный каталог, а не под ноги. Для их использования нужно писать путь от корня Аркадии. 

Системные заголовки и stl можно включать по их обычным путям. Но скорее всего в util есть обертка или замена для них со всякими полезностями. Смотрите в [Arcadia C++ library guide](https://wiki.yandex-team.ru/arcadia/development/cpplibraryguide/) и в серию постов [Arcadia How To](https://kostik.at.yandex-team.ru/2229).




## Контрибы { #contrib }

Контрибы - это модули, разработка которых ведется за пределами Аркадии. 

В контрибах мы хотим по возможности не менять код для облегчения синхронизации с апстримом. Если же без изменений не обойтись, то рекомендуется их автоматизировать. Рекомендованный способ укладки C++ контрибов в Аркадию - утилита [yamaker](https://a.yandex-team.ru/arc/trunk/arcadia/devtools/yamaker/). Из желания не изменять код возникает необходимость использования [`ADDINCL`](../common/macros.md#addincl) в контрибах. Лучше всего доверить их генерацию yamaker. В остальном правила и рекомендации следующие:

* Контрибы должы быть сконфигурированы так, чтобы для их использования не требовалось добавлять [`ADDINCL`](../common/macros.md#addincl) в аркадийный код, а было достаточно только [`PEERDIR`](../common/macros.md#peerdir). Это значит, что иногда не обойтись без [`ADDINCL(GLOBAL ...)`](../common/macros.md#addincl). Если в публичный заголовок библиотеки включаются другие файлы по [`ADDINCL`](../common/macros.md#addincl), то этот [`ADDINCL`](../common/macros.md#addincl) должен быть глобальным (параметр [`GLOBAL`](../common/macros.md#addincl)). В остальных случаях [`ADDINCL(GLOBAL...)`](../common/macros.md#addincl) использовать не нужно. Это не средство для сокращения размера ya.make.

* При необходимости сделать [`ADDINCL(GLOBAL ...)`](../common/macros.md#addincl) в контрибе полезно вынести его публичные заголовки в отдельный каталог, если этого уже не сделано в оригинальном коде контриба. Для этого можно создать каталог с прокси-заголовками, которые будут инклудить публичные заголовки контриба по полному пути от корня Аркадии или по относительному пути. В yamaker для этого есть специальный параметр `inclink`.
`inclink={'mysql': {'include/mysql.h', 'include/mysqld_error.h'}}` создаст каталог mysql, в котором будут прокси-заголовки для `include/mysql.h` и `include/mysqld_error.h`

* Неиспользуемые заголовки (под `#if 0`) нужно предварять `#error` (`disable_includes` в yamaker). Это одновременно и отключает определение зависимости для него и гарантирует, что заголовок действительно не используется в сборке. Для заголовков, которые будут использоваться только в некоторых платформах нужно обеспечить корректный резолвинг во всех. Иногда для этого требуется добавить новое правило для механизма системных заголовков. 

* Если при укладывании контриба возникает ошибка "sysincl/addincl mismatch", то значит его заголовки пересекаются по именам с какими-то системными и требуется модификация конфигов [правил системных заголовков](#sysincl).

## Правила системных заголовков [sysincl] { #sysincl }

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

Первая проблема с такими заголовками в том, что в зависимости от платформы они могут браться и из Аркадии. Например, при сборке с системной libc ее заголовки привозятся в сборку вместе c os sdk, а при сборке с musl [лежат в Аркадии](https://a.yandex-team.ru/arc/trunk/arcadia/contrib/libs/musl-1.1.20/include?rev=4009210). Аналогично со стандартной библиотекой c++. Для поддержки такого системные заголовки перечислены в конфигах в [build/sysincl](https://a.yandex-team.ru/arc/trunk/arcadia/build/sysincl). Эти конфиги включаются в зависимости от сборочных переменных платформы в [build/ymake.core.conf](https://a.yandex-team.ru/arc/trunk/arcadia/build/ymake.core.conf).

Пример такого конфига:
```yaml
- includes:
  - sys/queue.h
  - errno.h:
    - contrib/libs/cxxsupp/libcxx/include/errno.h
    - contrib/libs/musl-1.1.20/include/errno.h

- source_filter: "^contrib/libs/libevent"
  - sys/queue.h: contrib/libs/libevent/compat/sys/queue.h

- case_sensitive: false
  includes:
  - winsock.h
```

Конфиг определяет как должна резолвится строка из #include при определённых условиях.
Возможные варианты: 
- `#include` должен игнорироваться (соответствующего файла нет в Аркадии).
- `#include` означает зависимость на один или несколько файлов в Аркадии (путь указывается от корня дерева исходного кода). Зависимость от нескольких файлах отражает неоднозначность резолвинга: в разных случаях `#include` означает один из перечисленных вариантов.
- `source_filter` позволяет задать регулярное выражение на имя файла, в котором написан `#include`. Если он указан, то правило будет действовать только на `#include`, написанные в файлах, удовлетворяющих выражению.
- `case_sensitive: false` означает, что правила (строки для `#include`) должны трактоваться без учёта регистра. Такое бывает нужно для стандартных хедеров Windows.


Вторая проблема, что в даже рамках одной платформы они могут резолвиться в несколько мест в Аркадии. Например `stdint.h` есть как [в musl](https://a.yandex-team.ru/arc/trunk/arcadia/contrib/libs/musl-1.1.20/include/stdint.h?rev=4009025), так и [в libc++](https://a.yandex-team.ru/arc/trunk/arcadia/contrib/libs/cxxsupp/libcxx/include/stdint.h?rev=4408235). Чтобы избежать неоднозначности (резолвим мы однократно) мы явно выписываем все возможные варианты в конфигах системных заголовков и консервативно считаем, что зависимость есть от всех. Чтобы такие размноженные зависимости не расползались, применение правил для системных заголовков можно ограничить регулярным выражением в source_filter. Если для инклуда возможно применение нескольких правил, то их результаты суммируются.

{% note warning %}

Для предотвращения мигания уидов из-за второй проблемы мы проверяем, что для системного заголовка перечисленны все возможные варианты в конфиге. Если в процессе определения зависимостей обнаруживается новый, то выдается ошибка конфигурации **BadIncl** "sysincl/addincl mismatch".

Пример:
```
Error[-WBadIncl]: in $B/devtools/examples/diag/ymake/bad_incl/bad_incl: sysincl/addincl mismatch for include stddef.h from $S/devtools/examples/diag/ymake/bad_incl/main.cpp addincl: $S/contrib/libs/musl/include/stddef.h sysincls: ${ARCADIA_ROOT}/contrib/libs/cxxsupp/libcxx/include/stddef.h
```

{% endnote %}
