# Описание сборки С++

## Основные модули

Основными модулями для описания C++ сборки являются `PROGRAM`, `LIBRARY` и `DLL`. 

В Аркадии максимально широко распространена статическая сборка при работе с C++. Несмотря на то, что система сборки позволяет описывать сборку динамических библиотек, описать зависимость программы от динамической библиотеки довольно сложно. Специальные средства для описания подобной зависимости выдаются в случае, когда динамическая линковка требуется для соблюдения лицензионных требований. Поэтому, в отличии от cmake `LIBRARY` позволяет собрать только статическую библиотеку.

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

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

Простой пример описания сборки программы может выглядеть примерно так:
```
PROGRAM(my-prog-name)
OWNER(g:my-team-group)

SRCS(
    main.cpp
    some_src.cpp
    other_src.cpp
)
PEERDIR(
    library/cpp/http/client
    contrib/libs/fmt
)

END()
```
Такая программа будет собрана из перечисленных исходных файлов и слинкована с перечисленными библиотеками статически.

Очевидно, что при появлении функционала, который не очень логично держать в `main.cpp`, сразу же хочется писать на этот функционал тесты и бенчмарки. Тестировать отдельные функции и классы, описанные в исходном коде модуля `PROGRAM` нельзя, поэтому более реалистичной типичной структурой проекта, собирающего программу на C++, будет следующая иерархия директорий:
```
arcadia/top_level_dir/my_prog/
                      |-> bin/
                      |   |-> main.cpp
                      |   |-> ya.make
                      |-> ut/
                      |   |-> small_tests.cpp
                      |   |-> ya.make
                      |-> some_src.cpp
                      |-> other_src.cpp
                      |-> ya.make
```

Корневой ya.make при этом описывает библиотеку, в которую вынесена реализация всего функционала приложения:
```
LIBRARY()
OWNER(g:my-team-group)

SRCS(
    some_src.cpp
    other_src.cpp
)
PEERDIR(
    library/cpp/http/client
    contrib/libs/fmt
)

END()

RECURS_FOR_TEST(ut)
```

Описание сборки программы становится тривиальным и уезжает в ya.make, лежащий в папке bin:
```
PROGRAM()
OWNER(g:my-team-group)

SRCS(main.cpp)
PEERDIR(arcadia/top_level_dir/my_prog)

END()
```

Юнит тесты в данном примере размещаются в папке ut и, благодаря макросу `RECURS_FOR_TEST(ut)`, их сборка происходит только при вызове сборки с запуском тестов `ya make -t`. В случаях, когда хочется собирать юнит тесты всегда, даже когда не запрошен запуск тестов, вместо него можно использовать макрос `RECURSE`.

## Модули для тестирования

Для написания юнит тестов и бенчмарков для C++ кода в Аркадии есть несколько разных типов модулей:
 * `UNITTEST` модуль, использующий внутреннюю библиотеку юнит тестирования [unittest](https://a.yandex-team.ru/arc_vcs/library/cpp/testing/unittest)
 * `GTEST` модуль, использующий одноимённую популярную библиотеку юнит тестирования от гугла [gtest](https://github.com/google/googletest/blob/master/docs/primer.md)
 * `Y_BENCHMARK` модуль, использующий внутреннюю библиотеку написания тестов производительности [benchmark](https://a.yandex-team.ru/arc_vcs/library/cpp/testing/benchmark)
 * `G_BENCHMARK` модуль, использующий популярную библиотеку написания тестов производительности от гугл [benchmark](https://github.com/google/benchmark/blob/master/docs/AssemblyTests.md)

Все эти модули отнаследованы от модуля `PROGRAM` и позволяют делать всё, что можно сделать при описании сборки программы. Во всех случаях правильная зависимость от использующейся библиотеки тестирования уже добавлена системой сборки и писать соответствующие `PEERDIR`'ы руками не требуется.

В примере из предыдущего раздела ya.make в папке ut может иметь следующий вид:
```
UNITTEST()
OWNER(g:my-team-group)

SRCS(small_tests.cpp)
PEERDIR(arcadia/top_level_dir/my_prog)

END()
```

## Динамические библиотеки

Модуль `DLL`, позволяющий описывать сборку динамических библиотек, во многих отношениях ведёт себя подобно модулю `PROGRAM`. Так же, как и последний, он статически линкуется со всеми модулями `LIBRARY`, от которых зависит, требуя для своей сборки версию статической библиотеки с поддержкой Position Independent Code.

При этом для модуля `DLL` опционально можно указывать export script со списком символов, которые должны быть доступны из динамической библиотеки.
```
DLL()
OWNER(g:my-team-group)

SRCS(
    some_src.cpp
    other_src.cpp
)
EXPORTS_SCRIPT(myproj.exports)

END()
```

{% note info %}

В отдельных ситуациях библиотеку, живущую и используемую в Аркадии, требуется отдавать внешним потребителям, использующим систему сборки, отличную от Аркадийной. Часто это бывает удобно достичь, собрав модуль `LIBRARY` как динамическую библиотеку, и отдавать её потребителям вместе с требуемыми заголовочными файлами. Для таких целей заведён модуль `DLL_FOR`.

{% endnote %}

## Управление флагами компиляции

Для сборки C++ кода довольно часто может возникать необходимость управлять как флагами компиляции кода, собираемого в текущем модуле, так и кода, собираемого с использованием разрабатываемой в данном модуле библиотеки. Для управления флагами компиляции текущего модуля можно использовать макрос `CXXFLAGS`. При этом библиотечные модули могут перед отдельными флагами указывать ключевое слово `GLOBAL`, маркирующее следующий за ним флаг, как глобальный. Глобальные флаги используются не только в компиляции текущей библиотеки, но и в компиляции библиотек и программ, зависящих от текущей прямо или транзитивно.

Например:
```
LIBRARY()

SRCS(...)
CXXFLAGS(
    GLOBAL -DUSE_SHORT_ALIAS_NAMESPACE
    -DENABLE_SOME_INTERNAL_FEATURE
)

END()
```

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

## Глобальные исходные файлы

Ещё одним важным местом, в котором может быть использовано ключевое слово `GLOBAL`, является макрос `SRCS`.

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

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

## Зависимости по умолчанию

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

Список библиотек от которых автоматически зависит любой код в аркадии:
 * Платформенное SDK
 * Стандартная библиотека C (зависит от платформы)
 * Стандартная библиотека C++ в нашем случае это libc++ с локальными патчами от Яндекса, живущая в Аркадии.
 * Аллокатор (какой конкретно определяется с помощью макроса `ALLOCATOR`)

## Автоматическая кодогенерация

Для кодогенерации в аркадии используются два подхода. Первый, это явная кодогенерация с использование макросов, добавляющих в сборку необходимые команды, например `RUN_PROGRAM` и `GENERATE_ENUM_SERIALIZATION`. Второй подход неявный и на нём хочется остановиться подробней.

Кроме `*.cpp` файлов в макросе `SRCS` можно указывать файлы с множеством других расширений, например, `*.proto`, `*.swg`, `*.fbs`, ... (полный список регулярно пополняется, поэтому не приводится в данном документе). При этом такие файлы будут автоматически правильно обработаны. Для них в конфигурации нашей системы сборки описаны правила генерации либо `*.cpp` файлов, либо файлов с расширениями, для которых есть правила генерации `*.cpp` файлов.

Важным свойством нашей системы сборки является то, что все файлы, для которых описано правило генерации, автоматически считаются неявно добавленными в макрос `SRCS`. Поэтому если с помощью `RUN_PROGRAM` описать генерацию protobuf'а, сгенерированный файл автоматически будет подхвачен в сборку C++ модуля. Из него создадут C++ файл который будет скомпилирован и отправлен на линковку модуля.
