Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Build Runnerでコードを生成する

Build Runnerでコードを生成する

MAO YUFENG

April 27, 2021
Tweet

More Decks by MAO YUFENG

Other Decks in Programming

Transcript

  1. Packages for code generation - build: - build_runnerの処理対象Builderを提供する。 Builderを継承して、コード生成ロジックが定義できる。 -

    https://pub.dev/packages/build - source_gen (optional) - Builderのwrapper、Dartファイルをより生成しやすくする。 - https://pub.dev/packages/source_gen - code_builder (optional) - Dartのソースコードを生成する。 (JavaPoet,KotlinPoetと似ている。) - https://pub.dev/packages/code_builder - build_config, analyzer, et al. (transive dependencies)
  2. Defination: Builder /// The basic builder class, used to build

    new files from existing ones. abstract class Builder { /// Mapping from input file extension to output file extensions. Map<String, List<String>> get buildExtensions; /// Generates the outputs for a given [BuildStep]. FutureOr<void> build(BuildStep buildStep); }
  3. A builder that copy dart files (1 → 1) class

    CopyBuilder implements Builder { @override final buildExtensions = const { '.dart': ['.dart.copy'] }; @override Future<void> build(BuildStep buildStep) async { final inputId = buildStep.inputId; final copy = inputId.addExtension('.copy'); final contents = await buildStep.readAsString(inputId); await buildStep.writeAsString(copy, contents); } } --lib |--foo.dart +--bar.dart ↓ --lib |--foo.dart |--foo.dart.copy |--bar.dart +--bar.dart.copy
  4. Static analysis of dart source code @override Future<void> build(BuildStep buildStep)

    async { final LibraryElement inputLibrary = await buildStep.inputLibrary; // Get all imports final List<ImportElement> imports = inputLibrary.imports; // Find element with name of foo final List<Element> topElements = inputLibrary.topLevelElements; // Generate content ... }
  5. A bulder that export all files (n → 1) class

    ExportAllBuilder implements Builder { @override Map<String, List<String>> get buildExtensions { return const { r'$lib$': ['export.dart'], }; } @override Future<void> build(BuildStep buildStep) async { await for (final input in buildStep.findAssets(Glob('lib/*.dart'))) { // do something } } }
  6. Export custom builder to build runner // my_builder/lib/builder.dart Builder copyBuilder(BuilderOptions

    options) => CopyBuilder(); # my_builder/build.yaml builders: copy: import: "package:my_builder/builder.dart" builder_factories: ["copyBuilder"] build_extensions: {".dart": [".copy.dart"]} # Will automatically run on any package that depends on it auto_apply: dependents # Generate the output directly into the package, not to a hidden cache dir build_to: source /// Creates a [Builder] honoring the configuation in [options]. typedef BuilderFactory = Builder Function(BuilderOptions options);
  7. Use LibraryBuilder with Genreator class MyGenerator extends Generator { @override

    String generate(LibraryReader library, BuildStep buildStep) { final LibraryElement libraryElement = library.element; return '// generated content ...'; } } Builder myLibraryBuilder(BuilderOptions options) => LibraryBuilder( MyGenerator(), generatedExtension: '.copy.dart', );
  8. A Generator that read annotated value class MyAnnotation { const

    MyAnnotation(this.value); final String value; } class MyGenerator extends GeneratorForAnnotation<MyAnnotation> { @override FutureOr<String> generateForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep) { final value = annotation.read('value').stringValue; return 'const ${element.displayName} = \'$value\';'; } }
  9. Define a class named Animal final animal = Class((b) =>

    b ..name = 'Animal' ..methods.add(Method.returnsVoid((b) => b ..name = 'eat' ..body = const Code("print('Yum');")))); class Animal { void eat() => print('Yum!'); } ↓
  10. Definition: Reference /// A reference to [symbol], such as a

    class, or top-level method or field. Reference refer(String symbol, [String url]) => Reference(symbol, url);
  11. Use Reference final Reference streamControllerRefer = refer('StreamController()', 'dart:async'); final library

    = Library( (b) => b..body.add(streamControllerRefer.assignFinal('controller').statement), ); // Collects references and automatically allocates imports. final emitter = DartEmitter(Allocator()); final content = library.accept(emitter); import 'dart:async'; final controller = StreamController<int>(); content:
  12. Riverpod final someProvider = Provider((ref) { // something }); //

    In test ProviderScope( overrides: [ provider.overrideWithValue(someValue), ], child: SomeWidget(), );
  13. AppThemeModeState final appThemeModeStateProvider = StateNotifierProvider( (_) => AppThemeModeState(), ); @FakeState(ThemeMode.dark)

    class AppThemeModeState extends StateNotifier<ThemeMode?> { AppThemeModeState() : super(null); void toggle() { state = state == ThemeMode.light ? ThemeMode.dark : ThemeMode.light; } }
  14. Definition: FakeStateNotifier class FakeStateNotifier<T> extends StateNotifier<T> { FakeStateNotifier(T state) :

    super(state); @override dynamic noSuchMethod(Invocation invocation) { if (invocation.isMethod) { return null; } return super.noSuchMethod(invocation); } }
  15. Generate fake state and ProviderScope overrides // theme_mode_state.fake.dart class FakeAppThemeModeState

    extends FakeStateNotifier<ThemeMode?> implements AppThemeModeState { FakeAppThemeModeState([ThemeMode? state = ThemeMode.dark]) : super(state); } // generated_state_overrides.dart final defaultStateOverrides = <Override>[ appThemeModeStateProvider.overrideWithValue(FakeAppThemeModeState()), // ... ]; // In test ProviderScope( overrides: [ ...defaultStateOverrides, ], child: SomeWidget(), );
  16. 宣伝 The Flutter code generator for your assets, fonts, colors,

    … — Get rid of all String-based APIs. https://github.com/FlutterGen/flutter_gen
  17. PS

  18. An annotation that has an object value // package:some_builder class

    ObjectAnnotation { const ObjectAnnotation(this.value); final Object value; } // package:my_flutter_project enum Platform { android, ios } @ObjectAnnotation(Platform.android) final foo = 123;
  19. Read the source of annotation class ObjectGenerator extends GeneratorForAnnotation<MyAnnotation> {

    @override FutureOr<String> generateForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep) { final ElementAnnotation elementAnnotation = element.metadata.first; // Read the source directly instead of using `ConstantReader annoation` final source = elementAnnotation.toSource(); final value = source.substring( '@ObjectAnnotation('.length, source.length - 1, ); return 'const ${element.displayName} = $value;'; } } @ObjectAnnotation(Platform.android) ⬇ Platform.android