# Базовые понятия системы сборки ya make

## Что и как строится {#what}

**Сборочная цель** или **модуль**. Для того, чтобы запустить сборку надо указать какую программу, библиотеку или пакет надо собрать. Эта программа/библиотека/пакет и есть *сборочная цель*.
Понимать *сборочную цель* можно двояко — это и *артефакт* — реальный результат сборки (собственно файл программы или библиотеки, файл или директория с файлами пакета), и *описание сборки*
— ya.make с описанием сборки.

{% note info %}

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

{% endnote %}

В любом случае *сборочная цель* указывается своей директорией. В этой директории в файле `ya.make` описано что будет собираться и как это надо собирать. Туда же по умолчанию будут сложены символьными ссылками результаты сборки. В системе сборки ya make
принято общее правило *одна директория — одна сборочная цель*. В каждой директории сборка описывается только в файле `ya.make` и каждый файл `ya.make` содержит описание сборки не более чем одного модуля. Это правило неявно нарушается только в двух случаях:

- Часть сборочных целей является [*мультимодулями*](#multimodules) — специальными целями, которые могут быть разными модулями в зависимости от контекста использования.
- Часть сборочных целей могут неявно добавлять для себя проверки, они не описываются явно, но будут построены как тесты вместе с модулем, если запрошен запуск тестов.


При **непосредственной сборке** сборочные цели указываются параметрами команды `ya make <target1> <target2>`. Если цель не указана явно, то ею является текущая директория.

{% note warning %}

Не запускайте `ya make` в корневой директории репозитория. Это приведёт к попытке построить всю Аркадию, что вряд ли получится и вряд ли нужно.

{% endnote %}

Кроме *непосредственной сборки* явным перечислением целей, несколько целей могут быть собраны *вместе*. Это достигается указанием целей в макросе семейства `RECURSE` в `ya.make`. Макрос не делает цели зависимыми,
он лишь позволяет собрать несколько целей одновременно, например, все бинарные файлы сервиса или тесты вместе с библиотекой.  Один `ya.make` может содержать и описание сборки модуля и
`RECURSE` на другие сборочные цели, которые надо собрать вместе с этим модулем. Чтобы ограничить сборку только *непосредственной* служит ключ `--ignore-recurses` команды `ya make`.

*Модули* могут быть **финальными** и **промежуточными**. *Финальные* модули — программы, динамические библиотеки, тесты и т.п. могут исполняться и независимо распространяться. *Промежуточные* модули —
библиотеки используются для сборки *финальных* модулей. В `ya make` используется статическая линковка и потому *Финальные* модули всегда **замкнуты**, т.е. их сборка вызывает сборку всех модулей,
от которых они зависят по `PEERDIR` и их результаты самодостаточны. Даже в Java, где связывание всегда динамическое, `JAVA_PROGRAM` эмулирует *замкнутый* артефакт — в результаты сборки попадают все
необходимые зависимости, а также скрипт для запуска java-программы.

*Промежуточные* модули могут вызывать сборку своих `PEERDIR`-зависимостей, но только если это необходимо для их собственной сборки. Так в Java и Go сборка пакета требует на вход собранных зависимостей. Однако в С++ и Python сборка модуля не использует
результаты сборки зависимостей (хотя может использовать их свойства), и потому при сборке библиотеки в С++ и Python зависимые библиотеки не будут собираться. Они соберутся только при сборке
программы или теста, которые (транзитивно) зависят от библиотеки. Все зависимости на *промежуточные* модули **транзитивно замыкаются** по `PEERDIR` в *финальных* модулях, независимо от языка этих модулей.

Подробней про значимости можно прочитать [ниже](#deps)


## Что такое конфигурирование {#conf}
Первое, что вы увидите, запустив сборку в Аркадии, будет что-то вроде
```
Configuring dependencies for platform default-linux-x86_64-debug
```
потом будет ещё
```
Configuring dependencies for platform tools
```
и только потом побежит прогресс собственно сборки. Что же такое *конфигурирование* в этом сообщении и что такое *платформы*?

**Конфигурирование** в нашей системе сборки — это процесс *анализа зависимостей* и построения *графа сборочных команд*. Подробнее про это можно почитать в разделе [как работает система сборки](how_it_works.md),
но вкратце система сборки во время *конфигурирования* решает 3 основные задачи:

1. **Обнаружение изменений**. Первое что надо понять системе сборки — это что изменилось со времени последней пересборки. Система сборки `ya make` делает это двумя разными способами и в два разных момента времени.
   - Сначала она в соотвествии с описаниями сборки ищет что поменялось на файловой системе. Простой и частый случай  — это изменение исходного файла, более сложные случаи — это изменение `ya.make` или изменение
   состава файлов для указанного в сборке шаблона (*glob*-выражения). Эта информация помогает ей не делать полностью *анализ зависимостей*, а использовать *кэш зависимостей*.
   - В самом конце, когда уже построен *граф сборочных команд*, каждая команда получает уникальный идентификатор (*UID*), который, в частности, является ключом в кэше результатов. Таким образом даже если
   *кэш зависимостей* недоступен или явно запрошено конфигурирование без кэша, сборка всё равно будет инкрементальной — всё, что уже строилось и есть в кэше результатов перестроено не будет.

2. **Анализ зависимостей**. Наша система сборки устроена таким образом, что каждая команда ещё до своего исполнения знает всё, от чего она зависит. Это знание конденсируется в её *UID*. И это не только текст команды и содержимое её входных файлов.
   Зависимости между файлами тоже учитываются. Поэтому анализ зависимостей — это не только считывание всех нужных `ya.make`-файлов. Это ещё и
   - Анализ (**парсинг**) исходных файлов в поисках межфайловых зависимостей.
   - Хэширование всех файлов для фиксации их содержимого и обнаружения изменений при следующих сборках.
   - Распространение и уточнение свойств по межмодульным зависимостям (например, dependency management в Java), формирование транзитивных списков зависимостей (например, списка библиотек для линковки программ).
   - Расчёт влияния зависимостей на сборочные команды и вычисление **UID**. Уникальный идентификатор — это хэш от всего, что может повлиять на *функциональный* результат команды (т.е. изменить результат так, что он будет работать по-другому).
     Мы не стремимся к *бинарной воспроизводимости* результатов исполнения команд. В *UID* засчитывается текст команды со всеми её аргументами, имена и содержимое всех файлов, которые нужны команде, включая
     зависимые по `include`/`import`. Если на вход команде `A` подаётся результат исполнения команды `B`, то в `UID(A)` засчитывается `UID(B)`.

3. Построение **Графа сборочных команд** или **сборочного графа**. Результатом процесса *конфигурирования* является *граф сборочных команд* — это граф, в узлах которого расположена информация о сборочных командах. Она включает собственно текст команд,
   имена результатов их работы, требования, служебную информация для отображения в процессе сборки и в CI и т.п. Связи в графе —  это отношение *производитель-потребитель*. Узел команды-потребителя связан исходящими дугами с узлами команд-производителей, результаты
   которых потребитель принимает на вход. Ключом в графе является *UID*, поэтому зависимости в узле — это просто список UID-ов. В графе команд выделены входы — результаты исполнения этого графа (собственно сборки). Относительно этих входов граф всегда *замкнут* т.е. содержит всю необходимую информацию для построения результатов. Это включает как
   команды собственно сборки для *целевой платформы*, так и команды сборки инструментов для *сборочной платформы*, которые после будут использованы прямо в этой же сборки как части команд. Граф может быть
   сериализован и исполнен не там, где его строили, важно лишь, чтобы часть графа относящаяся к *платформе сборки* соответствовала *реальной платформе*, на которой его будут исполнять.

## Платформы и конфигурации {#configs}
Важным понятием при конфигурировании является **платформа** или, более точно, **сборочная конфигурация**.

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

- *реальная платформа конфигурирования* — это та платформа, где запускается собственно конфигурирование (команда `ya make`). Там исполняется сама система сборки как минимум при *конфигурировании*.
  Мы поддерживаем в качестве таких платформ Intel x86-64 Linux, IBM PowerPC LInux, Intel x86-64 macOS, Apple m1 macOS через Rosetta 2, Apple m1 macOS (в экспериментальном режиме), Intel x86-64 Windows.
- *реальная сборочная (или host) платформа* — это та платформа, где исполняется сборка. На этой платформе работает часть системы сборки, отвечающая за исполнение *сборочного графа* а также сборочные
  инструменты (как загружаемые в сборку бинарно, так и строящиеся в ней же). Поскольку наш *сборочный граф* можно отдельно построить и отдельно исполнить, то эта платформа не обязана совпадать с предыдущей.
  Набор поддерживаемых платформ здесь тот же, что и выше, но поскольку инструменты загружаются или строятся в сборочном графе, при конфигурировании можно указать альтернативную
  *предписанную сборочную платформу* флагом `--host-platform`.
- *реальная целевая платформа* — это та платформа, где будет исполняться собранный код. Система сборки `ya make` поддерживает кросс-сборку и набор *целевых платформ* очень широк. Более того,
  в рамках одной сборки можно построить код сразу под несколько *предписанных целевых платформ* указав несколько раз параметр `--target-platform` или добавив платформы в [ya.conf](https://docs.yandex-team.ru/yatool/commands/gen_config#target-platform).

  {% note alert %}

  Система сборки ya make поддерживает *одновременную сборку* под несколько платформ (`--target-platform` или `[[target_platform]]` можно указать несколько раз).
  Более того, для каждой целевой платформы доступны разные дополнительные настройки (`--target-platform-flags` или `[target_platform.flags]` позволяет задать [`-D`-флаги](#D),
  а `--target-platform-build-type` -- [тип сборки](#build-type) для платформы, указанной перед этими опциями). Однако

  - Для каждой платформы будет получен отдельный артефакт. ya make не поддерживает *мультиплатформенную сборку* -- результаты не будут запакованы в fat binary для macOS или Android APK.
  - Артефакты для разных платформ могут иметь одинаковые имена, при складывании результатов они могут наложиться друг на друга и результат будет непредсказуем.

  Используйте [ya package](../ya_package) для предсказуемой сборки под несколько платформ.

  {% endnote %}


Однако, часто при обсуждении системы сборки под *платформой* понимают *сборочную конфигурацию*, то есть весь набор параметров, который влияет на формирование результатов сборки. Так для Java частью платформы
может быть версия JDK, а для C++, например, сборка с address sanitizer. Даже тип сборки `debug`/`release` включают в такое расширенное понятие платформы. *Сборочная конфигурация* может меняться большим количеством
разных флагов включая произвольные *конфигурационные переменные* или *сборочные флаги*, передаваемые в сборку опцией `-D`, `--target-platform-flags` или `[target_platform.flags]`.  Вся эта информация называется **конфигурационными параметрами**. Она учитывается в *конфигурировании* и
определяет то, каким будет *сборочный граф* - какие в нём будут команды, входные файлы и результаты *предписанная сборочная платформа* и *предписанная целевая платформа* по умолчанию определяются на основе
*реальной платформы конфигурирования*, т.е. в основном они такие же как платформа, где, собственно, запустили систему сборки. Однако отдельные *конфигурационные параметры* могут отличаться.

Поскольку сборка `ya make`  герметичная и все зависимости известны при *конфигурировании*, то наборы системных библиотек и инструментов используются не из *реальной сборочной платформы*.
Конфигурирование про неё ничего не знает. Они определяются *предписанной сборочной платформой* и *предписанной целевой платформой*. Такие наборы (toolchains) настроены для сборки `ya make` как для случая
сборки в рамках одной платформы (сборочная и целевая платформы совпадают), так и для многих вариантов кросс-сборки. Системные библиотеки и инструменты скачиваются из Sandbox в соответствии с параметрами
и обеспечивают одинаковую на разных вариантах *реальной сборочной платформы*. Сборка не зависит не только от локально установленных пакетов, но и будет одинаковой, например, между Ubuntu 12 и Ubuntu 18.
Более того, сборка для macOS (*целевая платформа*) будет сконфигурирована так, что даст одинаковый результат будучи исполненной на macOS и Linux (*сборочные платформы*).

## Исполнение сборки {#exec}
После того, как *конфигурирование* построило *сборочный граф*, можно приступать к его **исполнению**. Это может быть сделано **локально**, т.е. на машине, где собственно сборка была запущена, или на
**кластере распределённой сборки**. Оба метода исполнения имеют ряд общих черт:

- Исполнение начинается от *результатов* — целей, которые были запрошены при сборке в командной строке или по `RECURSE`. Они зафиксированы в *сборочном графе* и являются его входами.
- *UID* является ключом в кэше. Если некий *UID* присутствует в кэше, то команду не будут исполнять, а сразу вернут закэшированные результаты. Если нет, то сначала будет сделана попытка рекурсивно собрать
  Зависимости (также с использованием кэша) и только потом будет исполнена команда.
- Каждая команда исполняется в своей рабочей директории, называемой **BUILD_ROOT**. Важно понимать, что *BUILD_ROOT* — это не одна директория на всю сборку. Она разная для каждой отдельной команды. Система
  сборки обеспечивает наполнение её результатами сборки зависимых команд, но только в том случае если зависимость задана (известна системе сборки).
- Команды имеют доступ к Аркадии через директорию **SOURCE_ROOT**. Обычно это одна директория на все команды, и она указывает на корень Аркадии. Однако в специальном режиме *строгого контроля зависимостей* (задаётся ключом `--sandboxing`) система
  сборки даёт в ней доступ только до файлов, зависимость от которых известна системе сборки. Это позволяет проверять *герметичность* сборки.


**Герметичность** — это зависимость сборки только от данных, которые известны системы сборки.  Мы стараемся, чтобы наша сборка была *герметичной* — это обеспечивает корректность сборки с кэшированием и воспроизводимость для определённой ревизии и конфигурации. По идее все зависимости от исходного кода должны быть известны системе сборки,
в командах не должно быть зависимостей от файлов на локальной системе, а также от внешних систем. Все необходимые бинарные данные берутся из хранилища Sandbox по ID зафиксированному в репозитории, что делает
невозможным изменение данных для конкретной ревизии. По умолчанию вся Аркадия доступна любой команде, поэтому герметичность может быть нарушена, например, если команда будет читать что-то в Аркадии без указания
на это в системе сборки. В этом случае система сборки не может гарантировать даже корректность результатов: поскольку *UID* в этом случае не будет отражать все зависимости, закэшированные данные будут не в полной мере отражать состояние репозитория, что может привести к ошибкам времени сборки или времени исполнения.
Чтобы избегать таких проблем в систему сборки внедряется **строгий контроль зависимостей**, запрещающий во время исполнения сборки доступ к файлам, не известным системе сборки при *конфигурировании*. Режим включается флагом `--sandboxing` в локальной сборке. Однако,

{% note alert %}

Далеко не все проекты в Аркадии могут быть собраны в таком режиме. Сборка в нём на регулярной основе тестируется для небольшого числа проектов devtools.

{% endnote %}

Если сборка *герметична*, то при фиксированном состоянии *рабочей копии* репозитория — базовая ревизия + набор локальных изменений и фиксированном наборе *конфигурационных параметров* для целевой платформы результаты будут
**воспроизводимы**. Т.е. не будут зависеть от конкретной системы, на которой запущена сборка и метода её исполнения. Это обеспечивает ряд полезных свойств:

- Любые проблемы из CI можно воспроизвести локально. Проблемы у одного разработчика могут быть воспроизведены другим разработчиком у себя.
- Бинарный код может быть собран заново (в случае утери, необходимости отката и т.п.) просто на основе состояния репозитория.
- Результаты сборки на *кластере распределённой сборки* могут быть использованы как кэш для локальной сборки. Это работает через общий *распределённый кэш*, а также после явной заказанной *распределённой сборки* (--dist).
- Нет необходимости держать в *кластере распределённой сборки* сервера под Windows и macOS, сборка на Linux под эти платформы даст такие же результаты, как сборка на "родной" платформе.  Здесь есть инструментальные ограничения: некоторые инструменты сборки под Windows нормально работают только на Windows, но если сборка доступна, то результаты будут функционально эквивалентны.


## Описание сборки {#yamakes}

Как было сказано ранее, сборка для ya make описывается в файлах с именами `ya.make`, которые размещаются рядом с кодом.

### Макросы

Описание сборки состоит из набора **макросов**, которые могут

- Описывать собственно сборку кода (например, `SRCS`  —  исходный код или `PEERDIR`  —  сборочная зависимость)
- Описывать то, как будет интерпретироваться `ya.make` (например, макросы `IF` или `INCLUDE`)
- Описывать свойства сборки и влиять на работу других макросов через переменные (например, SET() или GRPC())
- Определять, что надо собирать (например, `RECURSE`)
- Фиксировать свойства для внешних систем (например, `OWNER`)

Описание сборки в `ya.make` очень высокоуровневое. Большинство *макросов*, не требуют указания команд для сборки. Команды — это скрытые свойства макросов, которые могут меняться в зависимости от *конфигурационных параметров*. Указывать в макросах обычно надо лишь аргументы для этих команд.
Такие макросы как `SRCS` могут определить какую команду использовать для сборки по расширению файла, для каждого файла отдельно. И даже более того, если результат какого-то макроса — это файл с известным расширением,
то команда обработки файла по расширению будет использована автоматически (как если бы этот файл написали в `SRCS`).


### Модули

Есть специальный вид *макросов*, определяющий сборочную цель, мы называем такой макрос **модуль**. Примеры таких макросов — это `LIBRARY`, `GO_PROGRAM`, `JTEST` и т.п.

{% note alert %}

В каждом `ya.make` может быть указано не более одного модуля. Это позволяет ссылать на модули как на директории.

{% endnote %}


*Модуль* имеет следующие свойства:

- Макросы размещённые между *макросом типа модуля* и `END()` определяют сборку для этого модуля.
- Макросы, описывающие сборку писать вне *модуля* нельзя.
- Если директория указана как *цель сборки* (явно, или по `RECURSE`), то будет собираться модуль, описанный в её `ya.make` если он там есть.
- Если директория указана как зависимость (`PEERDIR`, `DEPENDS` и т.п.), то эта зависимость именно от модуля в этой директории. Если модуль в `ya.make` отсутствует, будет выдана ошибка.
- Свойства сборки (переменные, например `CFLAGS`, пути поиска инклудов и импортов `ADDINCL` и т.п.) вычисляются для модуля и распространяются на весь модуль. В этом смысле набор свойств
  декларативен на уровне модуля. Вычисление этих свойств, однако, может быть императивно: макросы `SET`, `SET_APPEND` и `IF` выполняются в порядке написания и читают значения переменных, сложившиеся
  к моменту их исполнения. Макросы, формирующие списки (`ADDINCL`, `PEERDIR`) работают в порядке их написания, но игнорируют дублирующиеся элементы.

{% note warning %}

Макросы, вычисляющие свойства могут иметь неявные связи через переменные. Так, например, макрос `GRPC` неявно зависит от `PROTO_NAMESPACE` поскольку первый формирует пути для
дополнительных выходных файлов на основе второго. В определении макросов мы стараемся избегать таких зависимостей, но иногда они всё-таки случаются. Если вам кажется, что что-то работает не так, как вы ожидаете
- обратитесь в [поддержку devtools](https://st.yandex-team.ru/createTicket?queue=DEVTOOLSSUPPORT&_form=65090) и вам обязательно помогут.

{% endnote %}

Имя порождаемого модулем артефакта можно определить с помощью первого опционального аргумента модуля. Этим аргументом задаётся только базовая часть имени, к которой в зависимости от конкретного модуля добавляется зависящие от платформы префикс и суффикс. Например, статическая библиотека на C++ на Linux получит префикс `lib` и суффикс `.a`, на Windows пустой префикс и суффикс `.lib`, а Java библиотека на всех платформах получит суффикс `.jar` и пустой префикс. Если пользователь не задал базовое имя, то оно будет сконструировано из пути, по которому модуль расположен в Аркадии.

Таким образом интерпретацию `ya.make` можно примерно представить так:

- `ya.make` зачитывается и формируется набор свойств модуля.
- То же делается для зависимостей модуля рекурсивно.
- Свойства для модуля уточняются расширяются **глобальными свойствами** — такими, которые распространяются не только на модуль, но и на всех, кто от него зависит. Например, `ADDINCL(GLOBAL )`
  задаёт пути поиска инклудов для модуля и всех, кто от него зависит. Это нужно для корректной обработки `#include` в заголовочных файлах модуля, включенных из других модулей.
- Только после этого формируется *граф сборки модуля* - команды сборки всего, что для него нужно, включая сборку результата самого модуля - библиотеки, программы или теста.


### Мультимодули {#multimodules}

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

- `PROTO_LIBRARY` — библиотека для protobuf имеет 6 разных вариантов сборки в зависимости от того, для какого языка её используют. Так при использовании по `PEERDIR` из `LIBRARY` будет выбран C++-вариант её сборки,
  а при использовании из `GO_LIBRARY` — go-вариант. А ещё есть варианты для Python 2, Python 3, Java и пакетирования исходных файлов.

- `PY23_LIBRARY` — библиотека совместимая с Python 2 и Python 3. Собирается в зависимости от того, из модуля для какого Python её используют.

- `YQL_UDF` — статическая библиотека, если её используют по зависимостям, и динамическая библиотека-UDF, если её собирают *непосредственно*.

{% note tip %}

При *непосредственной сборке* и сборке по `RECURSE` *мультимодуля* будут построены вcе его варианты. При сборке по `PEERDIR` только те варианты, которые требуются этими `PEERIR`. В одну сборку по разным `PEERDIR` транзитивно может входить несколько вариантов, но каждый отдельный `PEERDIR` на *мультимодуль* выбирает из него только один вариант.

Например, в `PY2_PROGRAM` может иметь непосредственный `PEERDIR` на `PROTO_LIBRARY` и транзитивную зависимость через `LIBRARY`. В этом случае сборка `PY2_PROGRAM` потребует вариантов `PROTO_LIBRARY` для Python и С++.

{% endnote %}

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

**Пример:**

```
PY23_LIBRARY()
  IF (PY2)
    # use compatibility library for Python 2
    PEERDIR(contrib/library/six)
  ENDIF()
END()
```


### Зависимости {#deps}

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

- **Межмодульные зависимости**. Межмодульные сборочные зависимости — это `PEERDIR`, `BUNDLE` и *инструментальные зависимости*. Межмодульные тестовые зависимости — это `DEPENDS` - зависимость от собираемого модуля. В зависмости от языка сборочные межмодульные зависимости могут
  учитываться только при линковке финального артефакта (программы) для C++ и Python или приводить к пересборке самого модуля при изменении зависимостей для Java и Go.

  Основные зависимости между модулями — это:

  - `PEERDIR` - цель нужна для сборки другой цели. Самая распространённая межмодульная зависимость.
  - `DEPENDS` - цель нужна тесту во время его исполнения (она не является частью теста, а именно исполняется тестом во время его работы). `DEPENDS`-зависимости собираются только если был запрошен запуск соответствующего теста, либо по ключу `--force-build-depends`.
  - `BUNDLE` - результат сборки цели нужен для сборки другой цели (например, так можно сложить одну программу внутрь другой). `BUNDLE`-зависимости всегда собираются вместе с модулем, в описании которого они написаны.
  - **инструментальная** - результат сборки цели используется как инструмент при сборке другой цели. Такая зависимость возникает, например, при использовании макроса `RUN_PROGRAM`.
    Она похожа по смыслу на зависимость `BUNDLE`, но в отличие от всех прочих, она меняет *платформу* для зависимой цели: поскольку зависимая цель будет исполняться во время сборки, то сама она будет
    построена для *сборочной платформы*, в то время как её результат будет строиться для *целевой платформы*.

  {% note info %}

  В большинстве случаев межмодульные зависимости должны указываться явно, но есть два исключения:

  1. Система сборки обычно выводит *инструментальные зависимости* на сборочные инструменты сама при использовании соответствующих файлов в сборке.
  2. Система сборки умеет выводить зависимости между пакетами в Go на основании `import` в коде. Это возможно благодаря простому отображению имён пакетов в `import` на файловую систему и правилу *одна директория - один модуль*.

  {% endnote %}


  {% note info %}

  В большинстве случаев в сборке ya make межмодульные зависимости — это зависимости по исходному коду в trunk, поэтому их не нужно версионировать. Исключением являются зависимости от общих компонент в Java.
  Такие зависимости используются версионированными бинарными артефактами, загружаемыми из Sandbox. Для фиксирования своих зависимостей модули на Java используют макросы `DEPENDENCY_MANAGEMENT` и подобные.

  {% endnote %}


- **Файловые зависимости**. Помимо *модульных* система сборки анализирует и использует зависимости между файлами. Такие зависимости делятся на *явные* и *индуцированные* или *наведённые*.
  Допустим в библиотеке `l` файл `a.cpp` инклудит `b.h`, а тот в свою очередь `c.h`. Теперь если меняется `c.h` то при сборке `l` надо перезапустить команду компиляции `a.cpp` в `a.cpp.o`, а также персборку `l.a` с изменившимся `a.cpp.o`.
  Зависимости `a.cpp` от `b.h` и `b.h` от `c.h` называются **явными** в том смысле, что эта зависимость явно указана в коде. Соответственно, на сборку `a.cpp.o` влияют все *явные* зависимости для `a.cpp` рекурсивно.
  Чтобы упростить описание таких зависимостей система сборки `ya make` умеет выводить их анализом файлов по расширениям.

  {% note alert %}

  Система сборки анализирует зависимости именно так, как написано выше. Для неё `b.h` зависит от `c.h`. В реальности все такого рода зависимости — это зависимости для `a.cpp.o`. Команда её сборки может определять
  какие из написанных в коде зависимостей сейчас актуальны, а какие нет (через условные `#include`). Однако система сборки игнорирует контекст и условные директивы, получая зависимости более агрессивно.
  Это может приводить к лишним пересборкам, а также к сложностям в разрешении зависимостей (отображения имени из `#include` на реальный файл). Однако, это существенно экономит время при анализе зависимостей,
  а также память на их представление.

  {% endnote %}

  Допустим теперь, что в файле `a.proto` написано `import b`. Система сборки выведет из этого *явную* зависимость, что трансляция `a.proto` зависит от `b.proto`. Однако, кроме того, она выведет из этого
  факта **наведённую** (или **индуцированную**) зависимость `a.pb.h` от `b.pb.h` и распространит её на всех, кто инклудит `a.pb.h`. Эта зависимость не может быть получена как *явная* анализом `b.pb.h`
  поскольку в момент *конфигурирования* его просто не существует - он будет создан во время *исполнения* сборки.

  Безусловно, система сборки не всесильна. Она умеет анализировать только известные ей типы файлов. Если исходный файл порождается неизвестным ей генератором, то надо явно указать какие зависимости у такого генерированного файла могут появиться.

  - Если набор зависимостей фиксированный, их можно перечислить в макросе `INDUCED_DEPS` в модуле самого генератора.
  - Если набор зависимостей переменный, то их надо указать параметром `OUTPUT_INCLUDES` макроса `RUN_PROGRAM` или аналогичного в месте вызова генератора.

- **Зависимости модулей от файлов**. Модули естественным образом зависят файлов. В большинстве случаев эта зависимость вида *файл нужен для сборки модуля*. В большинстве макросов, таких как `SRCS`, `FILES` и даже
  `RUN_PROGRAM` упоминания файлов порождают такую зависимость. Однако есть 3 других варианта:
   - `DATA()` — зависимость исполнения тестов от файлов в Аркадии. Файлы не попадают в сборку тестов, а предоставляются непосредственно исполнению теста в исходном виде.
   - `SRCS(GLOBAL file.cpp)` — зависимость сборки от файла, но не того, модуля, где такой макрос написан. Если такой макрос написан в библиотеке, то от компиляции файла зависит не библиотека, а любая программа, использующая библиотеку.
     Такое бывает нужно, чтобы линкер не удалил код, присутствующий в файле, например статическую инициализацию с регистрацией каких-нибудь объектов.
   - `SRCS(header.h)` или `PY_SRCS(my.pyi)` — файл приписан модулю, но не используется в сборке. Такое нужно для работы `ya ide` чтобы дополнительные файлы могли попадать в проекты для модулей. Система сборки
     может использовать такие зависимости для своих целей (например, так или иначе учитывать принадлежность файлов модулям).
