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

Build Runnerでコードを生成する

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Build Runnerでコードを生成する

Avatar for MAO YUFENG

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