# Ограничения зависимостей

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

Для ограничения зависимостей предусмотрено три механизма:

1.  [Автоматический запрет зависимостей на детали реализации](#internal)
2.  [Правила ограничения PEERDIR](#policy)
3.  [Исключённый код](#blacklist)

{% note alert %}

Первые два способа ограничивают [только PEERDIR-зависимости](#limitations)

{% endnote %}


## Автоматический запрет PEERDIR { #internal }


При разработке приложений иногда возникает необходимость отделить часть кода в *детали реализации* — код, который нужен только вашему приложению и не должен быть использован кем-то еще. 
Для решения данной задачи в Аркадии реализован автоматический механизм запрета [`PEERDIR`](./macros.md) на детали реализации. Он основан на [механизме](https://golang.org/cmd/go/#hdr-Internal_Directories) из языка Go, который выполняет ту же функцию.

Основное применение данного механизма — сокрытие деталей реализации конкретного сервиса или библиотеки.

Детали реализации можно скрывать как от "внешних пользователей" вашего сервиса (например, детали реализации сервера YT от всех пользователей YT), так и внутри проекта YT можно скрывать
детали реализации одного сервиса от другого. Поэтому данный механизм стоит рассматривать как инструмент для разделения зон ответственности и инкапсуляции внутри проекта,
а не только для ограничения "наружу".

### Как это работает
Для автоматического запрета зависимостей используется директория со специальным названием - `internal`. Это не флаг, не атрибут, не параметр в `ya.make`, это именно название директории.
Всё, что находится в этой директории и её поддиректориях, разрешено для использования ([`PEERDIR`](./macros.md)) только тому коду, у которого одна из родительских директорий является
непосредственной родительской директорией для директории `internal`.

Например, для приведённого ниже файлового дерева любой код внутри директории `foo` и её поддиректориях может использовать код из `foo/internal` и её поддиректорий,
а любой код вне директории `foo` (например, код в директории `bar` или код в "корне") не может использовать код из `foo/internal` и её поддиректорий.

```
foo/
├── internal/
│   ├── main.go
│   └── a/
bar/
```

Данный механизм включен только для пользовательского кода. Он не работает для внешнего кода (`contrib/`), кроме Go, в Go он работает везде, включая `vendor/` и `contrib/go/`. Это связано с тем, что во всех
языках кроме Go во "внешнем" мире нет такого механизма. Соответственно, может встречаться внешний код, который не будет удовлетворять правилу *internal/*.

При срабатывании механизма в лог выводится сообщения следующего вида:
```
Error[-WBadDir]: in $B/foo/foo.a: PEERDIR from $S/foo to $S/bar/internal/foobar is prohibited since latter is ``internal`` and former is not its sibling
```

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

Для решения данного вопроса используется несколько правил и обязанностей, которые определяют порядок действий. Проект использующий автоматический запрет [`PEERDIR`](./macros.md) берёт данные обязанности автоматически в момент начала использования механизма.

1. Если кому-то нужно что-то из чужих деталей реализации, то именно этот кто-то должен приложить усилия для решения данной задачи.
2. Решение не может идти в ущерб проекту, чьи детали реализации предполагается сделать публичными, и должно обеспечить необходимый уровень инкапсуляции для оригинального проекта. В общем случае решение должно точечно вынести нужные детали реализации в публичные библиотеки и предоставить необходимый уровень абстракции для оригинального проекта.
3. Проект, использующий автоматические запреты на PEERDIR'ы деталей реализации, должен обеспечить как минимум оперативную консультационную помощь, а желательно непосредственно участвовать в решении задачи. Без выполнения этого пункта проект не может высказывать замечаний по качеству производимой работы (по сути, теряет права, предоставляемые пунктом 2).

При возникновении разногласий, которые невозможно решить самостоятельно, вопрос эскалируется до руководителей участвующих сторон с постановкой [arcadia-wg@yandex-team.ru](mailto:arcadia-wg@yandex-team.ru) в копию разбора проблемы.

## Ограничения зависимостей через Peerdir Policy { #policy} 

Общим правилом работы в Arcadia является отсутствие заборов. Мы хотим, чтобы код был переиспользован по максимуму вместо локальных велосипедов.
Однако, иногда всё же хочется иметь возможность ограничить использование какого-то кода. Например, если библиотека имеет аналог в Аркадии и принесена на время переезда проекта. 
Мы бы не хотели давать другим проектам этой библиотекой пользоваться. Есть и другие случаи. 
Все такие случаи регулируются соответствующими комитетами ([языковыми комитетами](https://wiki.yandex-team.ru/devrules/) и [комитетом contrib](https://wiki.yandex-team.ru/devrules/#contrib)).

Для закрытия кода у нас есть механизм **Peerdir Policy**, однако его применение ограничено. Все изменения в правилах должны производиться через комитеты. Для закрытия кода нужно создать
задачу в очереди [st/PEERDIR](https://st.yandex-team.ru/PEERDIR) с подробным описанием ситуации. Тикет уйдёт на рассмотрение в [contrib комитет](https://wiki.yandex-team.ru/devrules/#contrib).
После получения разрешения от комитета, можно создавать PR на добавление *Peerdir Policy*.

{% note info %}

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

{% endnote %}

### Правила Peerdir Policy

Для упрощения отслеживания все такие ограничения хранятся централизованно в директории [build/rules](https://a.yandex-team.ru/arc/trunk/arcadia/build/rules/).
При попытке сделать запрещённый `PEERDIR` система сборки выдаст configure-ошибку 
```
PEERDIR from $S/alice/gamma/sdk/golang to $S/vendor/github.com/sirupsen/logrus is prohibited, ignored`
```

При этом `PEERDIR` будет полностью проигнорирован, что в большинстве случаев приведёт к ошибкам сборки и/или исполнения тестов. Актуальный список файлов с правилами задаётся переменной `PEERDIRS_RULES_PATH` в
[ymake.core.conf](https://a.yandex-team.ru/arc/trunk/arcadia/build/ymake.core.conf?rev=4503001#L22)

{% note alert %}

Ограничения используются системой сборки для построения сборочного графа и потому должны быть доступны всегда, т. е. входить в каркас Аркадии.
Попытка положить ограничения за пределы директории `build/` приведёт к невозможности селективного чекаута и поломкам на этапе конфигурирования.

{% endnote %} 

Каждый файл содержит правила следующего вида:
```
[ALLOW|DENY] <regexp_from> -> <regexp_to>
```
и полнострочные комментарии начинающиеся с `#`.
Правила интерпретируются так:
- Если **ALLOW** или **DENY** не задано, считается **DENY**
- `<regexp_from>` и `<regexp_to>` — это регулярные выражения в формате PCRE без пробелов, пробел является разделителем
- Регулярные выражения применяются к директориям и по умолчанию задают префикс (если не начинаются с `.*`, то подразумевается `^` в начале).
- `<regexp_from>` проверяет директорию, в которой написан `ya.make` c [`PEERDIR`](./macros.md), `<regexp_to>` проверяет аргумент [`PEERDIR`](./macros.md).
- Если правило **DENY**, то [`PEERDIR`](./macros.md) запрещён, если **ALLOW**, то разрешён.
- Поиск идёт до первого совпадения обоих regexp'ов сверху вниз (правила в начале файла имеют более высокий приоритет). Между файлами приоритет определяется порядком наполнения переменной `PEERDIRS_RULES_PATH`.
- Если ничего не подошло - [`PEERDIR`](./macros.md) разрешён.

{% note tip %}

Обычно в правилах последний слог пути — это директория, чтобы избежать лишних совпадений рекомендуется использовать `/`в конце.

{% endnote %} 


### Примеры использования
```
# Из vendor разрешены любые PEERDIRы
ALLOW vendor/ -> .*
```

```
# Разрешён PEERDIR на Google RPC
ALLOW .* -> vendor/google.golang.org/grpc
# Разрешён PEERDIR на Job Scheduling Package
ALLOW .* -> vendor/github.com/jasonlvhit/gocron
# Все остальные PEERDIRы в vendor запрещены
DENY .* -> vendor/
```

```
# Разрешён PEERDIR на boost только выделенным проектам
ALLOW (contrib/|maps/|library/testing/ya_boost_test/|statbox/libstatbox/) -> contrib/deprecated/boost
# Остальным - запрещён (DENY по умолчанию)
.* -> contrib/deprecated/boost
```

## Исключение кода { #blacklist }

Некоторые деревья Аркадии исключены из использования в системе сборки ya make. Любое использование файлов в этих директориях запрещается системой сборки, как если бы директорий не было вовсе.
При попытки использовать файл ошибка выглядит примерно так:

```
Error[-WBlckLst]: in $B/build/docs/empty/build-docs-empty.pkg.fake: Trying to use $S/frontend/README.md from the prohibited directory `frontend`
```

Простой список префиксов таких деревьев от корня Аркадии хранится в файлах 

- [local.blacklist] — исключено из использования в локальной сборке. Эти директории нельзя использовать совсем, даже для локальных экспериментов.
- [autocheck.blacklist](https://a.yandex-team.ru/arc/trunk/arcadia/build/rules/autocheck.blacklist) — исключено из использования в автоматической сборке CI. Эти файлы можно использовать локально,
  но их использования нельзя коммитить в Аркадию.

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


## Ограничения ограничений { #limitations }
Механизмы *internal/* и **Peerdir Policy** сейчас работают только для зависимостей между модулями через [`PEERDIR`](./macros.md).
Но следует учитывать, что также возможны другие типы зависимостей: 
1. Прямые #include от корня Аркадии.
2. Использование (*воровство*) файлов из одних модулей в других (перечисление в `SRCS`, `RESOURCE` и других макросах), а также `PY_PROTOS_FOR`, `DLL_FOR` и похожие конструкции, *ворующие* всё содержимое модуля.
3. Использование файлов из несвязанных директорий (через SRCDIR или прямым указанием пути от корня Аркадии)
4. Использование просто файлов из модуля в тестах через макрос `DATA`.
5. Использование собранного артефакта (через `RUN_PROGRAM` как инструмента, через `BUNDLE` для пакетирования, через `DEPENDS` для тестирования).

Все эти зависимости не ограничиваются через [*internal*](#internal) и [*Peerdir Policy*](#policy). Нам бы хотелось, чтобы первые два варианта без [`PEERDIR`](./macros.md) были бы невозможны,
но даже это сделать непросто (есть несколько причин по которым сейчас в таких случаях [`PEERDIR`](./macros.md) поставить нельзя). Завязываться на содержимое других проектов/директорий через `SRCS` и `DATA` 
(3.) и (4.) кажется неудачной практикой, однако это может быть необходимо, в частности, при использовании артефактов в тестах (через `DEPENDS`).
Соответственно ограничения на 3. и 4. в большинстве случаев не будут разумными. Запрет на использование инструментов (5.) может иметь смысл, например, по причинам устаревания и
запрета некоторых из них. Кроме того, инструмент тоже может быть деталью реализации, однако на данным момент способов их выборочного запрета не существует.

Для полного исключения кода из сборки у нас есть [список исключённых директорий](#blacklist)

