build_runnerから実行できるミニマムなBuilder

2024-02-11#dart

Dartを使った開発では自動生成されたコードを利用することが多い。freezedjson_serializableなどのパッケージでは、以下のようにbuild_runnerを使ってコードを自動生成する。

$ dart run build_runner build

build_runnerはbuild.yamlによって定義されたbuilderを使ってビルドを行い、Dartのコードを生成する。ビルドに関わるいくつかのパッケージがDart開発チームによって提供されている。

なので、buildが提供するBuilderインターフェイスを実装すれば、build_runnerからコード生成を行うパッケージが自分でも作れるようになる。

ミニマムなBuilder

最小構成のBuilderを実装するため、まずはパッケージを作る。

$ dart create minimum_builder
$ cd minimum_builder

dart createで作られたダミーのコードを消す。

$ code . # remove dummy codes

buildなどの依存するパッケージをインストールする。buildのみが必要な依存関係で、build_runnerとbuild_configは開発のみに必要な依存関係となる。

$ dart pub add build dev:build_runner dev:build_config

build.yaml

build.yamlにビルドのための設定を書く。

$ code build.yaml

それぞれの設定項目の詳細については公式ドキュメントに書かれている。

targets:
  $default:
    builders:
      minimum_builder|todo_builder:
        generate_for:
          - example/*
        enabled: True
 
builders:
  todo_builder:
    import: 'package:minimum_builder/minimum_builder.dart'
    builder_factories: ['todoBuilder']
    build_extensions:
      .dart:
        - .g.dart
    build_to: source
    auto_apply: dependents

builderの実装

Builderインターフェイスを実装するクラスにコードの生成処理を実装する。

$ code lib/src/todo_builder.dart
import 'dart:async';
 
import 'package:build/build.dart';
 
class TodoBuilder implements Builder {
  @override
  FutureOr<void> build(BuildStep buildStep) async {
    final newInputId = buildStep.inputId.changeExtension('.g.dart');
    await buildStep.writeAsString(newInputId, '// TODO: implement');
  }
 
  @override
  Map<String, List<String>> get buildExtensions => {
      '.dart': ['.g.dart'],
    };
}

Builderインターフェイスが実装を要求するメソッドが2つある。

build_factoriesの実装

build.yamlのbuild_factoriesで指定した関数で実装したビルダーを返すようにする。

$ code lib/minimum_builder.dart
import 'package:build/build.dart';
import 'src/todo_builder.dart';
 
Builder todoBuilder(BuilderOptions options) => TodoBuilder();

試してみる

build.yamlでgenerate_forexample/*と指定したので、ダミーのコードを置いて試してみる。

$ mkdir example
$ touch example/example.dart
$ dart run build_runner run

example/example.g.dartが生成されていることがわかる。

// TODO: implement

まとめ

ほぼなにもしないビルダーを作ってみて、build_runnerによって実行可能なビルダーを実装する方法を理解できた。あとは、Builderを実装するクラスで入力として受け取ったファイルを元にコードを生成する実装を書いていけば、自分オリジナルのビルダーを実装できるだろう。

今回は触れなかったけど、source_genという別のパッケージを使うことでビルダーをより簡単に実装できるようになるため、機会があれば別の記事に使い方をまとめてみたいと思う。