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, то почему бы не собрать собственный препроцессор всего на свете. А потом внезапно вспомнил, что зависимости у нас "иммутабельные", кэш общий, и вообще никто не обещал, что хук = полноценная фаза компиляции. Так что результат получился закономерный, сам придумал себе проблему и героически побежал ее решать 😂