# Как писать ya.make

Сборка в ya.make-файлах описывается *макросами*. Использование макроса выглядит как `<ИМЯ>([опциональные параметры через пробел])`.
Никаких других конструкций в языке ya.make нет, [`ELSE()`](#if), [`ENDIF()`](#if) и [`END()`](#if) — тоже макросы. Кроме макросов есть только комментарии.
В языке ya.make символом начала комментария является `#` и комментарий длится до конца строки.

Макросы `ya.make` зачитываются последовательно, но не все из них [интерпретируются](./index.md#interp) непосредственно при зачитывании.

Сразу интерпретируются:

- Макросы макросы выставления переменных [`SET`/`SET_APPEND` и подобные](#var_macros). Значения переменных доступы в параметрах других макросов ниже по тексту ya.make. Макросы, внутри которых
  выставляются переменные ведут себя также.
- [Условные конструкции](#if). Они видят сложившиеся значения переменных и выбирают ветку для интерпретации в соответствии со значением условия в конкретной точке ya.make
- [INLCUDE](#include) выполняют текстовую подстановку сразу при чтении ya.make и в позицию, где встречен макрос.
- Макросы, формирующие списки ([`PEERDIR`](./common/macros.md#peerdir), [`ADDINCL`](./common/macros.md#addincl) и т.п.) формируют их в текстуальном порядке. Однако, обрабатываться эти списки могут в произвольном порядке (например, запросто может быть,
  что модуль, написанный в [`PEERDIR`](./common/macros.md#peerdir) позже будет сконфигурирован раньше).

[Макросы модулей](#module_macros), устанавливающие свойства и формирующие команды, макросы задающие зависимости и связи применяются в тот момент, когда соответствующая информация нужна и в порядке,
определяемом самой системой сборки. Полагаться на определённый порядок исполнения таких макросов не стоит.


## Скоупы в ya.make

Некоторые макросы называются *модульными макросами*. Такие макросы определяют начало описания модуля, а также его базовые свойства. Заканчивается описание модуля макросом `END()`.
Между этими макросами содержится собственно всё описание сборки модуля. Таким образом в ya.make-файле выделяется два скоупа:

- **Глобальный** — до начала описания модуля и после него, если модуль есть. Весь файл, если модуля нет.
- **Модульный** — от макроса начала модуля (например [`PROGRAM`](./cpp/modules.md#program)) до макроса `END()`.

### Глобальный скоуп { #global_scope }

Глобальный скоуп — это то, что относится ко всему ya.make-файлу, а не к модулю, который в нём собирается.

Только в глобальном скоупе пишутся:

- **Связи** — макросы [`RECURSE`](./common/macros.md#recurse) и т.п. Этот макрос говорит, что при сборке данного ya.make как цели надо собирать ещё и другие цели — аргументы макроса. Могут существовать ya.make состоящие только из
  [`RECURSE`](./common/macros.md#recurse) и служащие для агрегации сборочных целей. Также [`RECURSE`](./common/macros.md#recurse) и особенно [`RECURSE_FOR_TESTS`](./common/macros.md#recurse) может соседствовать с модулем, например библиотека может добавлять в сборку свой тест, чтобы проверить
  не только сборку, но и линковку.

- **Модули** и END(). Начало и конец модуля могут быть только в глобальном скоупе. Вложенные модули запрещены.

Также в глобальном скоупе могут быть использованы:

- *Информационные* макросы [`OWNER`](./common/macros.md#owner) и [`SUBSCRIBER`](./common/macros.md#subscriber). Они не интерпретируются системой сборки и нужны другим системам.
- [`INCLUDE`](#include) — макрос текстового включения.
- Макросы выставления переменных [`SET`/`SET_APPEND` и подобные](#var_macros). Такие переменные доступны для использования в макросах всего ya.make ниже по тексту, но не влияют на модульные свойства.
  Например, [`SET_APPEND(CFLAGS -fno-lto)`](common/macros.md#set_append) не повлияет на флаги компиляции в модуле, описанном в таком ya.make.
- [Условные конструкции](#if)


В глобальном скоупе можно использовать только [*встроенные*](./index.md#conf) макросы и макросы начала модуля. Все остальные [*конфигурируемые*](./index.md#conf) макросы,
могут быть использованы только в модульном скоупе.

Конструкции, указанные в глобальном скоупе, интерпретируются ровно один раз в любом ya.make, даже в описывающем [*мультимодуль*](./index.md#multi).
Исключение составляет собственно макрос начала [мультимодуля](./index.md#multi):
он интерпретируется как макрос начала модуля, соответствующего варианту. Например, [`PY23_LIBRARY()`](./python/modules.md#py23_library) будет интерпретирован сначала как
[`PY2_LIBRARY`](./python/modules.md#py2_library), а потом как [`PY3_LIBRARY()`](./python/modules.md#py3_library).

__Пример ya.make состоящего только из глобального скоупа:__

```
OWNER(g:owners)

INCLUDE(my_vars.inc)

RECURSE(lib)
IF (WITH_PROGRAM)
    RECURSE(program)
ENDIF()
```


### Модульный скоуп { #mod_scope }

В модульном скоупе располагается всё [описание сборки модуля](./index.md#mod)

В нём размещаются все [макросы, описывающие как собрать этот модуль](./index.md#module_macros), а также какими свойствами этот модуль обладает и какие
[глобальные свойства, переменные и команды](./index.md#globals) он определяет для тех модулей, которые от него зависят.

Там же могут размещаться макросы [выбора вариантов мультимодуля](#multi_macro), доступных для сборки этого конкретного модуля.

Также как и в глобальном, в модульном скоупе могут быть использованы:

- *Информационные* макросы [`OWNER`](./common/macros.md#owner) и [`SUBSCRIBER`](./common/macros.md#subscriber). Они не интерпретируются системой сборки и нужны другим системам.
- [`INCLUDE`](#include) — макрос текстового включения.
- Макросы выставления переменных [`SET`/`SET_APPEND` и подобные](#var_macros). Такие переменные доступны для использования в макросах в пределах модуля ниже по тексту, и могут влиять на модульные свойства.
  Например, [`SET_APPEND(CFLAGS -fno-lto)`](common/macros.md#set_append) поменяет флаги компиляции во всех командах компиляции модуля. Даже если [`SRCS`](./common/macros.md#srcs) с перечислением исходных файлов написано выше по тексту.
- [Условные конструкции](#if)

## Макросы

Сборка в ya.make-файлах описывается *макросами*. Использование макроса выглядит как `<ИМЯ>(параметры)`. Исторически для их именования используется
(SCREAMING_SNAKE_CASE)[https://ru.wikipedia.org/wiki/Snake_case].

Система сборки ya make предоставляет огромное количество макросов для различных языков и применений. Они описаны в соответствующих разделах:

- [Макросы для C++](cpp/macros.md)
- [Макросы для Python](python/macros.md)
- [Макросы для Java](java/macros.md)
- [Макросы для Go](go/macros.md)
- [Макросы для Protocol Buffers](proto/macros.md)
- [Макросы для Flat Buffers](flatbuf/macros.md)
- [Макросы для агрегации](package/macros.md)
- [Макросы для тестов](tests/common.md)
- [Полный автогенерируемый набор макросов в Аркадии](https://a.yandex-team.ru/arc_vcs/build/docs/readme.md)
- [Откуда берутся модули и макросы](./index.md#conf)

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


### Передача параметров

Параметры в макросах ya.make разделяются пробелами или переводами строк соответственно в большинстве случаев каждое отдельное слово — это отдельный параметр.
Макросы с длинным набором параметров можно переносить на несколько строк, разделяя по параметрам. Множественные пробелы в отступах считаются одним разделителем.

Поэтому можно так:

```
SET(FILE_LIST x y z)
```

Или так
```
SET(FILE_LIST
    x y z)
```
Или так
```
SET(
   FILE_LIST
   x
   y
   z
)
```

### Типы и виды параметров

Параметры в ya.make бывают всего трёх типов: строки, списки строк и булевы (по признаку наличия, см. ниже).

Параметры в ya.make бывают *именованные* и *неименованные*.

**Неименованные параметры** участвуют в вызове своим значением, они бывают:

- **Скалярные** — значением параметра является строка (одно слово). Иногда такой параметр может быть опциональным. Например, имя модуля 
   (параметр макроса заголовка модуля) — это обычно опциональный скалярный параметр.  Если таких параметров несколько, они принимают свои
   значения в соответствии с позицией

  - В документации обычно параметр описывается как `<значение>` (обязательный) или `[<значение>]` опциональный. Иногда угловые скобки опущены для читаемости.
    Важно, что слово, обозначающее значение, в таком случае одно и оно не целиком заглавными буквами.

  __Примеры:__
  ```
  ### @usage: PY_NAMESPACE(<name.space>)
  PY_NAMESPACE(my.namespace)
  .
  ### @usage: KV(<Key> <Value>)
  KV(the_key, the_value) # Key=the_key, Value=the_value
  ```

- **Свободно-списочные** — значением параметра является список всех строк (слов), не отнесённых ни к какому другому параметру. Если таких не находится, список остаётся пустым.
  - В документации такой параметр описывается как `Значение...`. 

  __Примеры:__
  ```
  ### @usage: ALL_SRCS([GLOBAL] Filenames...)
  .
  ALL_SRCS(x.cpp GLOBAL y.cpp)       # Filenames=x.cpp y.cpp, GLOBAL=True
  .
  ### @usage: SRC(<File>, Flags...)
  SRCS(x.cpp -Wno-error -std=c++17)  # Flags=-Wno-error -std=c++17
  SRCS(y.cpp)                        # Flags=<empty>
  ```

**Именованные параметры** участвуют в вызове своим именем и, возможно, значением. Исторически для именования параметров макросов, как и самих макросов, используется
(SCREAMING_SNAKE_CASE)[https://ru.wikipedia.org/wiki/Snake_case].

Именованные параметры бывают:

- **Булевы** — значение параметра определяется в описании самого макроса, а в вызове различается факт присутствия параметра в списке (*истина*) или отсутствия (*ложь*).
  - В документации такой параметр описывается как `[PARM_NAME]`. Важно, что в данном случае имя параметра написано целиком большими буквами и без угловых скобок.

  __Пример:__
  ```
  ### @usage: ALL_SRCS([GLOBAL] Filenames...)
  .
  ALL_SRCS(GLOBAL x.cpp y.cpp)  # GLOBAL is True
  ALL_SRCS(a.cpp b.cpp)         # GLOBAL is False
  ```

- **Скалярные** — значением параметра является строка (слово), идущее в вызове сразу после имени параметра. Такой параметр может быть *опциональным*, если он не указан,
  то параметр получит значение по умолчанию.
  - В документации такой параметр выглядит как `NAME <value>` или `[NAME <value>]`. Имя параметра целиком большими буквами, а значение — одно слово в угловых скобках.

  __Примеры:__
  ```
  ### @usage: COMPILE_LUA(Src, [NAME <import_name>])
  .
  COMPILE_LUA(func.lua NAME my.func)  # NAME=my.func
  COMPILE_LUA(the_func.lua)           # NAME=<default>
  ```

- **Списочные** — значением параметра является список строк (слов), идущих после имени параметра и до следующего имени параметра или закрывающей скобки.
  Имя параметра может встречаться в строке несколько раз, давая в результате конкатенацию всех списков. Если имя параметра не встречается, список остаётся пустым.
  - В документации такой параметр выглядит как `NAME <list of values>` или `[NAME <list of values>]`. Имя параметра целиком большими буквами, а
    значение — несколько слов в угловых скобках. 

  __Пример:__
  ```  
  ### @usage: FROM_ARCHIVE(Src [RENAME <resource files>] OUT <output files> [PREFIX <prefix>] [EXECUTABLE])
  # Src    - обязательный неименованный скалярный
  # RENAME - опциональный именованный списочный
  # OUT    - обязательный именованный списочный
  # PREFIX - опциональный именованный скалярный
  # EXECUTABLE - булев
  .
  FROM_ARCHIVE(
      resource.tar.gz
      PREFIX y
      RENAME y/a.txt OUT y/1.txt
      RENAME y/b/c.txt OUT y/2.txt
      RENAME RESOURCE OUT 3.tar.gz
      OUT y/d.txt
  )
  # Src=resource.tar.gz 
  # PREFIX=y
  # RENAME=y/a.txt y/b/c.txt RESOURCE  ## RESOURCE в данном случае значение, а не имя параметра
  # OUT=y/1.txt y/2.txt 3.tar.gz y/d.txt
  ```

### Экранирование значений параметров

Для объединения нескольких слов в параметре в одно можно использовать `"..."` (двойные кавычки) или, если что-то идёт не так `"'...'"` одинарные кавычки внутри двойных.

{% note warning %}

Экранирование кавычками может работать только на весь список параметров. Например:

Значением переменной будет одна неразрывная строка:
```
SET(FILE_LIST "a.x b.x c.x")
```

Значением переменной станет список из 4-х элементов, а не из 3-х:
```
SET(FILE_LIST a.x "with space.x" c.x)
```
{% endnote %}


{% note warning %}

Внутри ya make в качестве разделителя также используется запятая (`,`) и в некоторых случаях её невозможно надёжно экранировать. Единственным надёжным решением на данный
момент является использование специальной переменной `${__COMMA__}`

{% endnote %}

Пример:

```
SET(
  OPENAPI_GENERATOR_GENERATE_MODELS_VALUE
  models${__COMMA__}apis=false${__COMMA__}modelTests=false${__COMMA__}apiTests=false${__COMMA__}modelDocs=false${__COMMA__}apiDocs=false
)
```

### Интерпретация значений параметров

Параметрами макросов может быть практически что угодно и интерпретацией параметров могут заниматься разные системы в разные моменты времени:

- Часть значений интерпретирует сама система сборки. При чём это касается не только *встроенных* макросов, но и [*конфигурируемых*](./index.md#conf).
  Встроенные макросы могут полностью разбирать свои параметры, например [логические выражения в `IF`](#if), а также могут [интерпретировать имена директорий](#dirs).
  Для [имён файлов есть общая интерпретация](#files). Они могут обрабатываться, формируя входы, результаты, текстовые параметры команд.
  Система сборки также поддерживает ограниченный набор строковых и списочных операций для превращения значений параметров в параметры сборочных команд. Но как-то глубоко
  интерпретировать строковые значения она не умеет.

- Часть значений с помощью системы сборки могут интерпретировать [плагины для макросов](./index.md#plugins). Поскольку они пишутся на развитых языках программирования,
  их возможности в интерпретации значений практически неограниченны. Единственное ограничение к плагинам — это скорость их работы.

- Многие значения параметров макросов долетают до сборочных команд и интерпретируются уже самими командами во время исполнения. При чём это могут быть как оригинальные
  сборочные инструменты, так и их обёртки (обычно на языке Python). Большая часть таких обёрток описана в [build/scripts](https://a.yandex-team.ru/arc_vcs/build/scripts).
  Сама система сборки (своей исполнительной подсистемой), тоже может кое-что интерпретировать на этом этапе. Она поддерживает:
  - Свёртку списков параметров в файлы аргументов
  - Подстановку ссылок на глобальные ресурсы (переменные вроде `$(PYTHON)`)
  - Подстановку путей до дерева исходного кода и дерева сборки (новое на каждую сборочную команду) — переменные `$(ARCADIA_ROOT)` и `$(BUILD_ROOT)`.

#### Имена директорий { #dirs }

Макросы, такие как [`PEERDIR`](./common/macros.md#peerdir), [`ADDINCL`](./common/macros.md#addincl), [`RECURSE`](./common/macros.md#recurse) и [`DATA`](common/data.md#data) принимают имена директорий. Однако в этих конкретных 4-х макросах 4 различных интерпретации этих путей:

- [`ADDINCL`](./common/macros.md#addincl), а также [`SRCDIR`](./common/macros.md#srcdir) интерпретируют директории просто как пути. Далее они используются для [отображения имён на файлы](../general/how_it_works.md#resolving),
  но, по сути, это просто строки с дополнительной проверкой их наличия в дереве исходных файлов.
  В такие макросы можно передать как директорию в дереве исходных файлов, так и в дереве результатов сборки. Путь требуется всегда от корня репозитория и без [явного указания](common/vars.md#dir_vars)
  он считается в дереве исходных файлов. 

- [`PEERDIR`](./common/macros.md#peerdir) интерпретирует имена директорий, как ссылки на модули, которые будут там собраны. Это же справедливо для других макросов [межмодульных зависимостей](./index.md#mod_deps) --
  [`DEPENDS`](./tests/common.md#depends), [`BUNDLE`](./common/macros.md#bundle) и [`RUN_PROGRAM`](./common/macros.md#run_program), где директория указывается первым параметром как путь до инструмента. Путь в данном случае интерпретируется достаточно сложным образом:
  - Путь всегда интерпретируется как путь от корня репозитория. Корень указывать нельзя и не имеет смысла: генерированных ya.make не бывает (ну то есть сгенерировать можно,
    но интерпретироваться они не будут). 
  - В дереве исходных файлов должен лежать ya.make описывающий сборку.
  - Результатом этой сборки будет артефакт в аналогичном месте дерева результатов сборки. Зависимость получается от него.
  - В случае, если в ya.make описан [мультимодуль](./index.md#multi), то будет выбран подходящий для данной зависимости.

- [`RECURSE`](./common/macros.md#recurse) и подобные макросы интерпретируют имена директорий, как пути до ya.make в этих директориях. Все макросы, кроме [`RECURSE_ROOT_RELATIVE`](./common/macros.md#recurse) требуют пути относительно текущей директории.
  [`RECURSE_ROOT_RELATIVE`](./common/macros.md#recurse) требует пути от корня в дереве исходного кода. Корень указывать нельзя и не имеет смысла: генерированных ya.make не бывает (ну то есть сгенерировать можно,
   но интерпретироваться они не будут). 
   
- [`DATA`](common/data.md#data) интерпретирует директории, как зависимость от всех файлов в этой директории. Аналогично работают макросы, собирающие файлы по шаблону, например [`ALL_PY_SRCS`](python/macros.md#all_py_srcs).
  В [`DATA`](common/data.md#data) по историческим причинам путь внутри репозитория указывается как `arcadia/<path_from_root>`. При чём путём может быть как директория, так и файл. В любом случае зависимость допустима только
  на исходные файлы. Директории не могут быть результатами сборки и даже для файлов [резолвинг](../general/how_it_works.md#resolving) для [`DATA`](common/data.md#data) не работает.

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

#### Имена файлов { #files }

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

Некоторые макросы, такие как [`INCLUDE`](#include) и [`INDUCED_DEPS`](common/macros.md#induced_deps) требуют указания *полных имён файла* включая корень, к которому этот файл относится
(корень репозитория исходных файлов или корень результатов сборки). Корни указываются с помощью [специальных встроенных переменных](common/vars.md#dir_vars).

Основная часть макросов этого не требует и имена файлов [отображаются на реальные файлы (*резолвятся*)](../general/how_it_works.md#resolving). Реальное имя потом может быть
непосредственно интерпретировано как вход команды и/или обработано чтобы сформировать имя результата команды, а также текстуальное представление этого имени в строке запуска команды.
Кроме того, обработка, может, например включать получение директории по файлу и помещение этой директории в [`ADDINCL`](./common/macros.md#addincl) для модуля, где написан вызов макроса на этом файле.

{% note tip %}

Если в макросе имя файла встречается в *текстовой позиции* (там, где оно не должно по идее интерпретироваться) и в позиции входа (там, где применяется [резолвинг](../general/how_it_works.md#resolving))
Имя файла заменится на *реальное* в обеих позициях. Если команда к такому не готова необходимо каким-то образом сделать имя в команде отличающимся от реального. 

__Пример:__

```
    RUN_PROGRAM(tools/x2y a.x IN a.x OUT_NOAUTO a.y)   # Здесь a.x в первой позиции заменится на полное
# vs
    RUN_PROGRAM(tools/x2y a.x- IN a.x OUT_NOAUTO a.y)  # Здесь a.x- останется как есть и будет
                                                       # интерпретироваться инструментом
```

{% endnote %}

Поскольку [отображение имён на файлы](../general/how_it_works.md#resolving) может получить ссылку на результат исполнения другой команды. Через этот механизм команды связываются между собой
зависимостями. Так можно написать цепочку зависимых макросов [`RUN_PROGRAM`](./common/macros.md#run_program) обрабатывающих один файл в несколько этапов.

__Пример:__

```
OWNER(g:generators)

LIBRARY(mylib)
    # Ниже $S — это ссылка на корень репозитория исходного кода
    #      $B — это ссылка на директорию с результатами сборки
    # $B будет разный у каждой команды, но совпадение на этом уровне обеспечивает связывание между командами.
    # Перенос данных между командами осуществляется системой сборки в процессе исполнения

    # Сконвертируем a.x в a.y
    # a.x _отобразится_ на $S/libs/my/a.x как вход (IN)
    # a.y _сгенерируется_ как $B/libs/my/a.y инструментом в tool/x2y и будет _предсказан_ таким в OUT_NOAUTO
    RUN_PROGRAM(tools/x2y a IN a.x OUT_NOAUTO a.y)


    # Теперь сконвертируем a.y в a.z
    # a.y _отобразится_ на $B/libs/my/a.y как вход (IN) в силу отсутствия $S/libs/my/a.y
    # Это приведёт к зависимости от предыдущей команды (порядок команд на самом деле не важен)   
    # a.z _сгенерируется_ как $B/libs/my/a.z инструментом в tool/y2z и будет _предсказан_ таким в OUT_NOAUTO
    RUN_PROGRAM(tools/y2z a IN a.y OUT_NOAUTO a.z)

    # Теперь сконвертируем a.z в a.cpp
    # a.z _отобразится_ на $B/libs/my/a.z как вход (IN) в силу отсутствия $S/libs/my/a.z
    # Это приведёт к зависимости от предыдущей команды (порядок команд на самом деле не важен)   
    # a.cpp _сгенерируется_ как $B/libs/my/a.cpp инструментом в tool/y2z и будет _предсказан_ таким в OUT
    RUN_PROGRAM(tools/z2cpp a IN a.z OUT a.cpp)

    # Дальше в силу OUT для $B/libs/my/a.cpp и наличия автоматической обработки для .cpp 
    # $B/libs/my/a.cpp будет скомпилирован в $B/libs/my/a.cpp.o
    
    # Ещё дальше в силу автоматической обработки .o на уровне модуля
    # $B/libs/my/a.cpp.o поступит на вход модульной команды и попадёт в библиотеку mylib.a
END()


```

Более того, [отображение имён на файлы](../general/how_it_works.md#resolving) работает через границы модулей, поэтому поставив зависимость на библиотеку из примера выше
можно получить генерированный файл как вход в макросе в другом модуле:

__Ещё один пример:__

```
OWNER(g:generators)

LIBRARY(deplib)
    # Ниже $S — это ссылка на корень репозитория исходного кода
    #      $B — это ссылка на директорию с результатами сборки
    # $B будет разный у каждой команды, но совпадение на этом уровне обеспечивает связывание между командами
    # Перенос данных между командами осуществляется системой сборки в процессе исполнения

    PEERDIR(libs/my)             # Директория, где живёт mylib в репозитории

    # Сконвертируем a.y в a.сpp
    # a.y _отобразится_ на $B/libs/my/a.y, который найдётся в силу PEERDIR
    # Он будет засчитан как зависимость и одновременно подставится в команду (в параметр --input)
    # Это приведёт к зависимости от команды из другого модуля
    # a.cpp _сгенерируется_ как $B/libs/dep/a.cpp инструментом в tool/y2cpp и будет _предсказан_ таким в OUT_NOAUTO
    # Заметим, что результат будет в директории нашего модуля и потому не совпадёт по имени с таковым у mylib
    RUN_PROGRAM(tools/y2cpp a --input a.y IN ${ARCADIA_BUILD_ROOT}/libs/my/a.y OUT_NOAUTO a.cpp)

    # Дальше всё отработает как в примере выше
END()

```


## Переменные { #vars }

В ya.make активно используются переменные. Переменные содержат значения, которые можно передавать в макросы, включая [условные](#if). По историческим причинам переменные 
именуются в (SCREAMING_SNAKE_CASE)[https://ru.wikipedia.org/wiki/Snake_case], но пользовательские переменные могут использовать и другие схемы именования.


Значения переменных — это всегда строки или списки строк (слов), разделённые пробелом. 

__Пример:__

```
LIBRARY()

SET(MSG "Hello wor!d")
SET(SOURCES greet.cpp colors.cpp)

SSRCS($SOURCES)  # Here will be list
MESSAGE($MSG)    # Here will be just string

END()
```

Семантически значения переменных могут быть очень [разными](#var_sources). Они могут приходить извне как свойства [платформы/конфигурации](../general/base_concepts.md#configs),
а могут быть [заданы](#var_macros) прямо в этом же ya.make-файле.

Использование переменной обычно выглядит как `$VAR_NAME` или `${VAR_NAME}` и обычно заменяется на значения в момент использования. Однако,
часть переменных вычисляется позже. Такие переменные нельзя использовать в [условных конструкциях](#if) и их значение может остаться нераскрытым
при использовании в значении макроса [`SET` и подобных](#var_macros).

К таким переменным относятся:

- Переменные [`ARCADIA_ROOT`, `ARCADIA_BUILD_ROOT`, `CURDIR` и `BINDIR`](common/vars.md#dir_vars): в ссылках на файлы они интерпретируются прямо по именам (без подстановки),
  но, например, при использовании их как части значения в макросе (`SET`)[#var_macros] они останутся не подставленными.

- Глобальные переменные вроде `LDFLAGS`, `PEERS` и т.п. определяются очень поздно, когда полностью построен [граф зависимостей](../general/how_it_works.md#build_deps).
  Поэтому никаких внятных значений в них быть не может. На них можно сослаться с тем, чтобы они попали в текст команд и позже раскрылись, но никакие проверки
  относительно них сделать нельзя.

### Откуда берутся переменные { #var_sources }

Есть 5 источников переменных:

1. Встроенные переменные. Эти переменные система сборки предоставляет сама. К ним относятся переменные [`ARCADIA_ROOT` и подобные](common/vars.md#dir_vars), перемененные `PEERS`. `INCLUDE`, `TARGET` и т.п.,
   которые позволяют получить свойства модуля такие как его зависимости, пути поиска инклудов, имя главного результата его сборки и т.п.

2. Переменные переданные через [командную строку](../usage/ya_make/index.md#D).

3. Переменные, описанные в [конфигурации](./index.md#conf). Эти переменные бывают двух видов:
   - Общие, [описывающие](common/vars.md#platform_vars) текущую [сборочную конфигурацию](../general/base_concepts.md#configs), например `OS_LINUX` или `SANITIZER_TYPE`. Обычно менять такие переменные не имеет смысла.
     Однако, [часть таких переменных](common/vars.md#D) имеет смысл значений по умолчанию и для конкретного модуля они могут быть изменены. Например, по умолчанию в сборке выставлено `STRP=no` для сохранения
     отладочной информации, но модуль, где это не нужно может [переопределить](#var_macros) эту переменную в значение `yes` макросом `STRIP()`.
   - Модульные, относящиеся к свойствам модуля или его команд (например, `CFLAGS`). Обычно такие переменные имеют значения по умолчанию, но могут быть [переопределены](#var_macros) в модуле.

4. [Глобальные переменные](./index.md#global), пришедшие по зависимостям. Такие переменные подставляются поздно, поэтому прочитать их значение в ya.make не получится. Однако, они могут быть
   отданы в макросы, создающие команды, чтобы значение подставилось в текст команды. Однако, обычно такие переменные уже используются в командах и передавать их явно не нужно. Модуль может
   изменить значение такой переменной и другие, зависящие от него, модули его получат. Так, например, макрос `LDFLAGS` собирает флаги нужные библиотекам в переменную `LDFLAGS`, которая
   используется в команде модуля  [`PROGRAM`](./cpp/modules.md#program), которая отвечают за линковку программ.

5. Переменные, [установленные](#var_macros) пользователем в этом ya.make или в тех, которые этот ya.make [включает](#onclude).


### Макросы установки переменных { #var_macros }

Для работы с переменными предусмотрены макросы [`SET`](./common/macros.md#set), [`SET_APPEND`](./common/macros.md#set_append), [`DEFAULT`](./common/macros.md#default), 
[`ENABLE`](./common/macros.md#enable) и [`DISABLE`](./common/macros.md#enable).

Все они позволяют так или иначе задать значения переменных. Их можно использовать как в [глобальном скоупе](#global_scope), так и в [модульном](#mod_scope). 

- В глобальном скоупе можно определить новые переменные, которые не описаны в [конфигурации](./index.md#conf). Действовать такие переменные будут во всём ya.make.

- В модульном скоупе также можно определить новые переменные, которые не описаны в [конфигурации](./index.md#conf). Но кроме того, можно переопределить
  переменные, описанные там, как общие, так и специфичные данному типу модуля. В любом случае такие переменные выставляются на модуль и, например, в разных вариантах
  [мультимодуля](./index.md#multi) с помощью макроса [`IF`](#if) им можно задать разные значения. Также можно изменить значения [глобальных переменных](./index.md#global)
  и они распространяться в модули, зависящие от данного. 


Макросы, принимающие значения ([`SET`](./common/macros.md#set), [`SET_APPEND`](./common/macros.md#set_append), [`DEFAULT`](./common/macros.md#default)) могут иметь ссылки на другие переменные как часть значения. Например:

```
SET(SUFFIX res)
SET(INP xx.scheme)
SET(OUTP $(MODDIR)/${INP}.${SUFFIX})
```

При этом все переменные, кроме тех, что вычисляются поздно будут сразу подставлены, *поздние* переменные останутся как есть. Если такое значение, с не подставленной переменной
окажется в контексте, где значение не подставленной переменной станет известно (например, в тексте команды), то оно подставится.


Макросы установки переменных создают новые переменные, если переменная с таким именем не описана в конфигурации. В этом случае её надо использовать ниже по тексту непосредственно в макросах
модуля или всего ya.make (если переменная установлена вне модуля). Однако, если переменные описаны в [конфигурации](./index.md#conf) модуля или макросов модуля, то макрос переопределит значение
такой переменной.

__Пример:__

```
SRCS(x.cpp)               # Здесь внутри используется CFLAGS

SET(LLVM_VER 11)          # LLVM_VER создаётся здесь

PEERDIR(contrib/libs/llvm${LLVM_VER})       # LLVM_VER здесь подставится по сложившемуся значению

# SET(LLVM_VER 8)         # Это та же LLVM_VER, но это бы повлияло на IF ниже, но не на PEERDIR выше.

IF (LLVM_VER == 11)
  SET(CFLAGS -Wno-error)  # Это влияет на SRCS выше
ENDIF()

```

### Полезные ссылки

- Макросы установки переменных [`SET`](./common/macros.md#set), [`SET_APPEND`](./common/macros.md#set_append), [`DEFAULT`](./common/macros.md#default).
[`ENABLE`](./common/macros.md#enable) и [`DISABLE`](./common/macros.md#enable).
- Общие [встроенные переменные](common/vars.md#dir_vars).
- Общие [переменные платформ и конфигураций](common/vars.md#platform_vars).
- Общие [переопределяемые переменные](common/vars.md#D).
- Переменные сконфигурированные для [C++](cpp/vars.md), [Python](python/vars.md), [Java](java/vars.md), [Go](go/vars.md), [Protobuf](proto/vars.md), [Flatbuf](flatbuf/vars.md).

## Специальные макросы

### INCLUDE { #include }

Система сборки ya make поддерживает текстовые включения макросом `INCLUDE(<full_file_name>)`. Текст из файла, переданного параметром будет подставлен вместо `INCLUDE` и интерпретирован.

Соответственно этот текст:

- Будет относиться к тому же скоупу, что и макрос `INCLUDE`.
- [Переменные](#vars), определённые в тексте будут определять модульные переменные, если `INCLUDE` размещён в модуле и будут видны в [условных конструкциях](#if) и параметрах других макросов ниже этого макроса.
- [Переменные](#vars), определённые выше `INCLUDE` будут доступны в параметрах макросов включённого файла, включая [условных конструкциях](#if)
- Сейчас по умолчанию включения файлов обрабатываются каждый раз, когда встречаются при интерпретации ya.make и включенных в него файлов. В частности макросы [`SET_APPEND`](common/macros#set_append) во включенном файле будут вызваны столько раз, сколько встрется файл внутри одного ya.make.
  Это может привести к неоправданному росту значений переменных. Изменить поведение на однократную обработку для каждого начального `ya.make` можно макросом [`INCLUDE_ONCE`](#include_once).
- `INCLUDE` внутри [мультимодуля](./index.md#multi), независимо от реальной реализации, действует так, словно все текстовые ключения сделаны до порождения вариантов [мультимодуля](./index.md#multi). Т.е. конструкции из включенного файла будут интерптетированы во всех вариантах [мультимодуля](./index.md#multi).

Параметром макросом должно быть полное имя, с указанием [`${ARCADIA_ROOT}/`](common/vars.md#dir_vars) как принадлежность к дереву исходных файлов. 

{% note warning %}

Циклические текстовые включения запрещены и вызывают ошибку конфигурирования.

{% endnote %}

{% note alert %}

Включайте файлы так, чтобы границы модуля (макрос начала модуля и `END`) и части конструкции [`IF/ELSEIF/ELSE/ENDIF`](common/macros#if) находились в одном файле. Даже если вынос начала модуля в отдельный файл сейчас работает, не гарантируется, что это продолжит работать.

**Хорошо:**
```
PY23_TEST()

INCLUDE(${ARCADIA_ROOT}/project/tests_body.inc)

END()
```
**или**

mod.inc

```
PY23_TEST()

TEST_SRCS($FILES)

END()
```

ya.make
```
SET(FILE test_my.py)
INCLUDE(mod.inc)
```

**Плохо:**

mod.inc
```
PY23_TEST()

SIZE(MEDIUM)
```

ya.make
```
INCLUDE(mod.inc)

TEST_SRCS(test_my.py)
END()
```

{% endnote %}

### INCLUDE_ONCE

```
INCLUDE_ONCE([yes|no])
```

Тестовые включения могут приводить к тому, что один и тот же файл будет по разным путям от основного ya.make включен несколько раз.
Это будет приводить к замедлению работы а также, возможно, к дублированию значений при вызове макроса [`SET_APPEND`](common/macros#set_append).

Чтобы избежать этого рекомендуется использовать макрос `INCLUDE_ONCE()`. Файл, в котором написан такой макрос будет обработан только один раз для
каждого `ya.make`-файла. Остальные его включения при обработке этого `ya.make` будут проигнорированы.

- Макрос без параметров или с параметром `yes` включает однократную обработку файла.
- Макрос с параметром `no` выключает однократную обработку файла. Это может быть полезно чтобы зафиксировать явно, что файл
  предназначен для многократного включения. И особенно это будет полезно при смене поведения [`INCLUDE`](#include) по-умолчанию.

{% note tip %}

Где бы макрос `INCLUDE_ONCE` не исполнился в файле, этот файл не будет интерпретирован второй раз. Однако рекомендуется писать 
макрос в самом начале файла и настоятельно не рекомендуется ставить его под [`IF`](common/macros#if).

{% endnote %}


### Условные конструкции { #if }

Система сборки позволяет чтобы сделать часть макросов в ya.make обрабатывалась только при определённых условиях. Для этого служат
[условные конструкции IF/ELSEIF/ELSE/ENDIF](common/macros.md#if)

```
IF (Expr)
    ...
ELSEIF (Expr)
    ...
ELSE()
    ...
ENDIF()
```

Условия могут быть наложены на значения любых доступных [переменных](#vars), как определённых выше по тексту макросами [`SET`](#var_macros), так и [пришедших извне](#var_sources).

Синтаксис макроса и, в частности, доступные условия описаны в [соответствующем разделе](common/macros#if).

Условные конструкции исполняются непосредственно во время интерпретации ya.make и оперируют значениями переменных, сложившимися к тому моменту, когда
до `IF` дошла очередь. Это имеет два следствия:

1. Непосредственная интерпретация не гарантирует даже валидации конструкций в ветви, которая не была выполнена.
2. Переменные могут отражать не окончательную конфигурацию модуля. Конструкции ниже по тексту могут напрямую (перезаписав значения переменных) или косвенно (через правила конфигурации)
   изменить значения переменной, на которую наложены условия. А некоторые переменные вносят вклад в конфигурацию, но не определяют свойство самостоятельно.

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


### Работа с мультимодулями

Поскольку [*мультимодуль*](./index.md#multi) описывает сборку сразу нескольких разных модулей, то есть конструкции, позволяющие:

- Отключить какие-то варианты мультимодуля, например, потому что код с ними несовместим, или наоборот включить отключенные по умолчанию варианты.
- Сделать разное поведение для разных вариантов в мультимодуле
- Переопределить варианты мультимодулей, доступные по [`PEERDIR`](./common/macros.md#peerdir) из данного.

У каждого варианта в мультимодуле есть тэг (`Tag`) — имя варианта. Имена уникальны только внутри конкретного типа мультимодуля, но могут переиспользоваться между разными типами
для упрощения настройки. Так тэг `PY3` стоит на варианте для Python 3 в большинстве мультимодулей. 

#### `EXCLUDE_TAGS`,`INCLUDE_TAGS`,`ONLY_TAGS` { #multi_macro }

Макросы `EXCLUDE_TAGS`,`INCLUDE_TAGS`,`ONLY_TAGS` позволяют управлять доступным набором вариантов в каждом конкретном мультимодуле.

- `EXCLUDE_TAGS(Tags...)` позволяет исключить полностью варианты из мультимодуля. Эти варианты не будут конфигурироваться и не будут доступны по [`PEERDIR`](./common/macros.md#peerdir)
   из совместимых с ними модулей, вместо этого будет выдаваться ошибка.

- `ICLUDE_TAGS(Tags...)` позволяет включить варианты в мультимодуль если они существуют, но отключены для него по умолчанию.

- `ONLY_TAGS(Tags...)` позволяет оставить только перечисленные набор вариантов в мультимодуле.

{% note warning %}

Рекомендуется писать эти макросы ближе к началу. Макросы интерпретируются на обработке первого варианта мультимодуля. Если первый вариант исключается макросом, то после макроса
интерпретация этого варианта прекращается, но, если до его достижения у интерпретации были внешние эффекты (например, ошибки), они могут быть выданы.

{% endnote %}

#### `MODULE_TAG` и `PEERDIR_TAGS`

Работа мультимодулей в существенной части построена на двух свойствах:

- Тэг варианта модуля. Он позволяет идентифицировать вариант и потому используется в случаях, когда надо на вариант сослаться.
- Набор тэгов для зависимостей — список тэгов вариантов, которые будут выбираться при PEERDIR из модуля (или варианта мультимодуля) на мультимодуль.

Тэг варианта модуля доступен на чтение через переменную `MODULE_TAG`. В частности, на `MODULE_TAG` можно писать условия, чтобы изменить поведение для отдельного варианта

__Пример:__

```
OWNER(user)

PY23_LIBRARY()

   PY_SRCS(soource.py)

   IF ($MODULE_TAG == "PY2")
       PEERDIR(contrib/python/six)
   ENDIF()
END()
```

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

```
OWNER(user)

PY23_LIBRARY()

   PY_SRCS(soource.py)

   IF (PYTHON2) 
       PEERDIR(contrib/python/six)
   ENDIF()
END()
```
Переменная [`PYTHON2`](./python/vars.md#py_ver) определена во всех модулях на Python 2 включая соответствующие варианты мультимодулей.


{% note alert %}

Технически переменную `MODULE_TAG` можно переопределить макросом [`SET`](#var_macros), но делать этого не рекомендуется: поведение может быть непредсказуемым.

{% endnote %}

Список тэгов для зависимостей определяется переменной `PEERDIR_TAGS`. Эта переменная содержит список имён вариантов, при чём для каждого мультимодуля там должен быть не более чем 1 вариант
(зависеть от нескольких вариантов одного мультимодуля запрещено). Список задан на каждый тип модуля и его можно модифицировать макросом [`SET`](#var_macros). Поскольку такие макросы отрабатывают
раньше, чем [`PEERDIR`](./common/macros.md#peerdir), то изменения будут применены и зависимости будут настроены правильно.

## Откуда берутся модули, макросы и общие переменные { #conf }

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

Набор доступных в ya.make макросов и модулей постоянно расширяется. Никакая ручная документация не в состоянии поддерживать этот список актуальным,
поэтому каждую ночь по комментариям и описаниям генерируется [автоматическая документация](../generated.md), содержащая описания всех модулей и макросов
доступных в данный момент.

Ниже приведён краткий путеводитель по модулям, макросам их источникам и особенностям описания.

### Встроенные макросы

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

К таким макросам относятся:

- Макрос текстового включения [`INCLUDE`](#include)

- Базовые макросы работы с переменными ([`SET`/`SET_APPEND` и т.п.](#var_macros)). Сюда же относятся макросы получения списков файлов по маскам (`_GLOB`), но они
  являются внутренними и могут быть использованы в ya.make, они могут использоваться для создания пользовательских макросов, таких как [`ALL_PY_SRCS`](python/macros.md#all_py_srcs).

- Базовые макросы зависимостей и связей: [`PEERDIR`](./common/macros.md#peerdir), [`DEPENDS`](./tests/common.md#depends), [`RECURSE`](./common/macros.md#recurse).

- Макросы путей для поиска файлов [`SRCDIR`](./common/macros.md#srcdir), [`ADDINCL`](./common/macros.md#addincl), [`INDICED_DEPS`](./common/macros.md#induced_deps) и т.п.

- Макросы управления модулями и мультимодулями: `END`, `DLL_FOR`, [`EXCLUDE_TAGS`/`ONLY_TAGS`/`ONLY_TAGS`](#exclude_tag).

- Условные конструкции [`IF`/`ELSE`/...](#if)

- Описательные макросы, которые система сборки не интерпретирует, но может выдать как дополнительную информацию о модулях, например [`OWNER`](./common/macros.md#owner).

Встроенные макросы [описаны в коде ядра системы сборки](https://a.yandex-team.ru/arc_vcs/devtools/ymake/yndex/builtin.cpp)


### Конфигурируемые макросы

Буквально все модули и мультимодули, а также подавляющее большинство макросов описаны в конфигурационных файлах [build/ymake.core.conf](https://a.yandex-team.ru/arc_vcs/build/ymake.core.conf) и
файлах в директории [build/conf](https://a.yandex-team.ru/arc_vcs/build/conf). Там же определено множество переменных, определяющих свойства платформ, конфигураций и отдельных типов модулей.

Описание конфигурации делается на достаточно сложном декларативном DSL, позволяющем выразить:

- Правила настройки (значения переменных) в зависимости от платформы и других параметров (тоже переменных);
- Описание макросов — их параметризацию и поведение;
- Шаблоны формирования сборочных команд: что, где и как надо вызывать, а также дополнительные свойства команд такие как, например, отображаемое имя или требуемые ресурсы;
- Входы и результаты для связывания команд в граф;
- Применение макросов по расширениям;
- Свойства модулей для связывания их с командами и между собой;
- Правила, относящиеся к конкретным модулям: свойства модулей и команд в них в зависимости от платформы и других параметров;
- Варианты мультимодулей и их связывание по зависимостям.

Подробная информация по конфигурированию системы сборки и описанию своих модулей и макросов будет в [разделе для разработчиков (TBD)](../development/index.md). Ниже лишь краткая
информация, необходимая для более полного понимания того, что происходит в ya.make.

#### Описание макросов

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

1. Макросы, задающие свойства
   Такие макросы вызывают другие макросы и в итоге формируют [переменные](#vars), которые будут использованы в командах других макросов или модуля. Кроме того,
   такие макросы могут добавлять зависимости или устанавливать свойства модуля.

   Например, макрос [`PROTO_NAMESPACE`](./proto/macros.md#proto_namespace) описан примерно так (это упрощённый вариант):

   ```
   PROTO_NAMESPACE=

   ### @usage: PROTO_NAMESPACE([GLOBAL] Namespace)
   macro PROTO_NAMESPACE(GLOBAL?"GLOBAL":"", Namespace) {
       SET(PROTO_NAMESPACE $Namespace)
       ADDINCL($GLOBAL FOR proto $Namespace)
       ADDINCL($GLOBAL ${ARCADIA_BUILD_ROOT}/$Namespace)
   }
   ```

   Делает следующее:

   - Устанавливает переменную `PROTO_NAMESPACE`, которая используется в вызовах `protoc` для формирования пути для результатов его работы.
   - Устанавливает путь поиска .proto-файлов в импорте с помощью [`ADDINCL(FOR proto ...)`](./common/macros.md#addincl). Это нужно и системе сборки для [разрешения имён](../general/how_it_works.md#resolving) напарсенных импортов и
     передаётся в `protoc` как путь для поиска импортов.
   - Устанавливает путь поиска инклудов для генерированных `.pb.h` в дереве результатов сборки макросом [`ADDINCL`](./common/macros.md#addincl) (C++ —  дефолт и его указывать не нужно). Это нужно и системе сборки для
     [разрешения имён](../general/how_it_works.md#resolving) зависимостей, [наведённых импортами](#induced) на генерированные `.pb.h` из других генерированных
     `pb.cc` и `pb.h`. Это же передаётся компилятору C++ как путь для поиска инклудов при компиляции генерированных файлов.
   - В присутствии параметра `GLOBAL` оба макроса [`ADDINCL`](./common/macros.md#addincl) становятся *глобальными*, т. е. их действие распространяется не только на сам модуль, но и на всех, кто от него зависит. Иначе в таких
     зависящих модулях импорты и инклуды просто не будут работать: [имена не смогут найти себе файлы](../general/how_it_works.md#resolving)

   По сути, такой макрос работает также, как если бы его содержимое было написано непосредственно в ya.make.


2. Макросы, описывающие команды
   Такие макросы описывают команды, которые должны быть исполнены в процессе сборки. Макрос формирует команду, которая встраивается в сборочный граф своего модуля за счёт
   [связывания](../general/how_it_works.md#resolving) своих результатов с входами других команд (потребителей) и, возможно, своих входов с результатами других команд (источников).

   Например, макрос `GENERATE_ENUM_SERIALIZATION` описан примерно так (это упрощённый вариант):

   ```
   ### @usage: GENERATE_ENUM_SERIALIZATION(File.h)
   macro GENERATE_ENUM_SERIALIZATION(File) {
      .CMD=${tool:"tools/enum_parser/enum_parser"} ${input:File} --output ${output;suf=_serialized.cpp:File} ${output_include;hide:File} ${output_include;hide:"util/generic/serialized_enum.h"} ${kv;hide:"p EN"}
      PEERDIR(tools/enum_parser/enum_serialization_runtime)
   }
   ```

   Означает это примерно следующее:

   - Команда будет запускать результат сборки [tools/enum_parser/enum_parser](https://a.yandex-team.ru/arc_vcs/tools/enum_parser/enum_parser). Это инструментальная зависимость,
     которая приведёт к сборке enum_parser под сборочную платформу.
   - Входом команды будет файл, переданный параметром. Его имя будет [превращено в файл](../general/how_it_works.md#resolving) по правилам для входных файлов. В том числе оно может указывать
     и на генерированный файл, тогда эта команда будет зависеть от генерирующей команды.
   - Выходом команды будет файл, сформированный добавлением `_serialized.cpp` к переданному имени. Выход будет размещён по внутреннему правилу размещения результатов сборки. Поскольку
     файл имеет расширение `.cpp`, то для него существует правило [обработки по расширению](./extensions.md). Соответственно, будет сгенерирована команда компиляции этого файла,
     которая станет зависеть от команды генерации.
   - Сам переданный файл, а также файл [util/generic/serialized_enum.h](https://a.yandex-team.ru/arc_vcs/util/generic/serialized_enum.h) станут зависимостями для генерированного файла.
     Это означает, что файлы будут доступны команде его компиляции, а при их изменении она будет перезапущена. В текст команды для исполнения эти части подставлены не будут благодаря *модификатору* `hide`.
   - При выводе прогресса сборки, исполнение команды будет помечено как `EN`.
   - Дополнительно модуль с этим макросом получит зависимость на библиотеку [tools/enum_parser/enum_serialization_runtime](https://a.yandex-team.ru/arc_vcs/tools/enum_parser/enum_serialization_runtime)
     которая используется в генерированном коде.


   Такого рода макросы и формируют [сборочный граф](../general/how_it_works.md), который исполняется в процессе сборки.


#### Описание модулей

Модуль — это, по сути, макрос, описывающий команду своей сборки, а также ряд дополнительных свойств:

- Что является [автоматическими входами](./extensions.md#auto) его команд.
- Как он [связывается с другими модулями](#mod_deps)
- Какие макросы допустимы, а какие нет в [описании его сборки](#mod)
- Финальный ли он и т.п.

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

У модуля есть параметры, но они не описываются. Система сборки сама обрабатывает их специальными образом для формирования имени модуля.

У модуля бывают локальные переменные, которые могут изменять поведение как его собственной команды, так и других команд [описания его сборки](#mod),
т. е. не только макросов в его описании, но и макросов, написанных в его [скоупе](#mod_scope) в ya.make. На основе переменных могут быть также составлены
условные правила поведения модуля.

Модули могут наследоваться друг от друга, чтобы не дублировать свойства общие для нескольких модулей. Так модули [`PROGRAM`](./cpp/modules.md#program) и [`DLL`](./cpp/modules.md#dll) наследуются от общего предка
— модуля `_LINK_UNIT`, задающего общие свойства, связанные с тем, что модуль может линковать библиотеки.

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

Вот как, например, описан модуль `FAT_OBJECT` (сборка всех объектных файлов из всех зависимостей в один большой файл):

```
module FAT_OBJECT: LIBRARY {
    .CMD=LINK_FAT_OBJECT_CMD
    .PEERDIR_POLICY=as_build_from
    .ALLOWED=PACK_GLOBALS_IN_LIBRARY

    when ($MSVC == "yes" || $CYGWIN == "yes") {
        MODULE_SUFFIX=.global.lib
    }
    elsewhen ($OS_ANDROID == "yes") {
        MODULE_SUFFIX=.a
    }
    otherwise {
        MODULE_SUFFIX=.o
    }

    _USE_LINKER()
}
```

Здесь можно увидеть, что:

- Большинство свойств наследуется от библиотеки ([`LIBRARY`](./cpp/modules.md#library))
- Модульная команда (`.CMD`) описана в отдельной переменной, которая зависит от платформы. Команда на структуре похожа на команду в макросе, но может ссылаться на специальные переменные
  `PEERS`, `GLOBAL_SRCS` и `AUTO_INPUT` чтобы получить доступ к [списочным входам модуля](../general/how_it_works.md#mod_resolve).
- `.PEERDIR_POLICY` определяет, будут ли [зависимости на другие модули](#mod_deps) *сборочными* (`as_build_from`, как здесь) или *логическими* (`as_include`, как у предка-[`LIBRARY`](./cpp/modules.md#library)).
- Разрешён один дополнительный макрос — `PACK_GLOBALS_IN_LIBRARY` определяющий, что делать с глобальными входами (собирать их в объектный файл или библиотеку).
- Правила описывают как формируется суффикс (расширение) имени в зависимости от платформы.
- Вызовом макроса настраивается linker который будет использован для сборки.

Примерно так, но иногда существенно сложнее, описаны все модули доступные в системе сборки ya.make.

#### Описание мультимодулей

[Мультимодуль](./index.md#multi) описывается как совокупность модулей, каждый из которых определяет *тэг* (совпадает с его именем в описании ли задаётся переменной `MODULE_TAG`), который отвечает за
связывания варианта по зависимостям из других модулей. Собственных общих свойств у мультимодуля нет, равно как нет и наследования. Однако каждый отдельный вариант описывается как полноценный
модуль и может быть унаследован.

Как было сказано [ранее](./index.md#multi) каждая зависимость выбирает ровно один вариант из мультимодуля, однако сами варианты могут зависеть друг от друга.
Так собственно программный вариант в [`PY3_PROGRAM`](./python/modules.md#py3_program) зависит от библиотечного.

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

```
### @usage: PY3_PROGRAM([progname])
multimodule PY3_PROGRAM {
    module PY3_BIN: PY3_PROGRAM_BIN {
        .PEERDIRSELF=PY3_BIN_LIB
        .IGNORED=RESOURCE RESOURCE_FILES PEERDIR TASKLET_REG
        _PY_PROGRAM()
    }

    module PY3_BIN_LIB: PY3_LIBRARY {
        .IGNORED=RESTRICT_LICENSES
        _REQUIRE_EXPLICIT_LICENSE()
    }
}

```

Видно, что

- Есть два варианта, которые наследуются от бинарной программы и библиотеки соответственно
- Программный вариант зависит от библиотечного (свойство `.PEERDIRSELF`)
- Каждый вариант модифицирует поведение относительно нормального для предков в части обработки макроса [`PY_SRCS`](./python/macros.md#py_srcs) (описан в [плагине](#plugins) pybild).
- Каждый вариант вызывает дополнительные макросы как часть своей логики.


Не видно, но

- Модули, у которых в переменной `PEERDIR_TAG` есть `PY3_BIN_LIB` смогут иметь PEERDIR на такой мультимодуль и по зависимости выберется библиотечный вариант.


Примерно так, но иногда существенно сложнее, описаны все мультимодули доступные в системе сборки ya.make.


### Плагины { #plugins }

Ядро системы сборки предоставляет возможность расширения с использования кода на Python. Код на питон работает медленнее, чем на встроенном DSL,
но позволяет преодолеть часть ограничений встроенного языка.

На Python можно

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

Код макросов размещён в директории [build/plugins](https://a.yandex-team.ru/arc_vcs/build/plugins)
Реализации макросов — это функции Python вида `on<имя_макроса>`, например [реализация PY_SRSC](https://a.yandex-team.ru/arc_vcs/build/plugins/pybuild.py?rev=5a0dcc85a59614afaf286a2a22760db38c4cb56a#L170)

Плагины на питоне берутся из репозитория в момент исполнения сборки и непосредственно интерпретируются ядром системы сборки (в неё влинкован интерпретатор Python). Таким образом любые
изменения в плагинах, также как и в конфигурации, не требуют релиза системы сборки.

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

Плюсом плагинов на C++ является их заметно большая скорость, минусом — релизный цикл: поскольку плагины влинкованы в ядро они релизятся вместе с ним, при локальной разработке и тестировании
таких плагинов требуется пересборка ядра системы сборки (программы [devtools/ymake/bin](https://a.yandex-team.ru/arc_vcs/devtools/ymake/bin)).

Таким плагином [реализован макрос RESOURCE](https://a.yandex-team.ru/arc_vcs/devtools/ymake/plugins/plugin_resource_handler.cpp).
