Dart 3.10 и hooks

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

С появлением относительно стабильной версии пакета hooks c Dart 3.10 во мне зажглась надежда. А что если мы получили в руки нечто большее, чем просто запускалку нативного тулчейна при сборке проекта? Что если станет возможен полноценный препроцессинг исходного кода на Dart?

Постановка задачи

Дано: код стандартной библиотеки языка (вымышленного), Dart 3.10 и немного кофе. Задача: в зависимости от флагов компиляции исключать из итогового байт-кода определенные фичи.

Как я хотел это реализовать

Создать хук, который при запуске сборки проекта компилирует исходиники ЯП и запихивает их в Dart-файл в виде списка байт (Uint8List). Импортировать получившийся "ассет" в код и использовать его.

Что получилось

Все хорошо, да не очень-то. На последнем шаге потерпел фиаско — сгенерированный таким образом ассет сохраняется в .dart-tool и по сути является лишь неким артефактом сборки, но не частью исходников. Поэтому такие файлы недоступны для импорта в исходном коде библиотеки. С другой стороны, если генерить файлы прямо в lib, то хук просто не будет модифицировать исходный код библиотеки, если она подключена через pub.dev или git 🙂

Почему так вышло

Видимо, я слишком много себе нафантазировал и пытался натянуть сову на глобус. Сама по себе концепция того, что можно автоматизировать некие операции на этапе запуска сборки супер крутая. И именно поэтому мне не нравится кодоген - как бы там ни было, он работает на "ручном" приводе. Тут — полноценное встраивание в пайплайн сборки. Но по правде говоря, мне не следовало ожидать другого поведения)

Зависимости в Dart имеют иммутабельное поведение, как и большинство экосистем других языков: Cargo (Rust), Go modules, npm (с lockfile), Maven/Gradle (с локальными кэшами). Предполагается, что зависимость не меняет своё поведение в ходе сборки, кроме допустимой кодогенерации внутри самого проекта, но не внутри зависимостей. Dart не копирует зависимости в проект, а использует единый кэш, поэтому модификации физически опасны.

Итоги эпопеи

В итоге я, конечно, сам себя перехитрил: решил, что раз в Dart завезли hooks, то почему бы не собрать собственный препроцессор всего на свете. А потом внезапно вспомнил, что зависимости у нас "иммутабельные", кэш общий, и вообще никто не обещал, что хук = полноценная фаза компиляции. Так что результат получился закономерный, сам придумал себе проблему и героически побежал ее решать 😂