I am trying to create a code generation package within Flutter using the build_runner
and source_gen
packages. I've looked over numerous examples but cannot seem to find what is preventing my output files from being created. I know it's probably something stupid, but I've been trying for days and cannot seem to find the problem.
So far, I've got the following directory structure:
─┬ fletch
├── fletch_annotations
├── fletch_example
└── fletch_generator
fletch_annotations
has annotation classes like the following:
package:fletch_annotations/src/model/annotation/column.dart
import 'package:fletch_annotations/src/model/enum/enum.dart';
class Column {
final FletchDataType dataType;
final bool isNullable;
final OnConflictAction onConflictAction;
final OnDeleteAction onDeleteAction;
final OnUpdateAction onUpdateAction;
final bool isUnique;
final String? columnName;
final dynamic defaultValue;
final String? check;
const Column({
required this.dataType,
required this.isNullable,
this.onConflictAction = OnConflictAction.abort,
this.onDeleteAction = OnDeleteAction.setDefault,
this.onUpdateAction = OnUpdateAction.setDefault,
this.isUnique = false,
this.columnName,
this.defaultValue,
this.check,
});
}
In fletch_generator
, I have the following pubspec.yaml
file:
fletch_generator/pubspec.yaml
name: fletch_generator
description: A new Flutter package project.
version: 0.0.1
homepage: https://example.com #! TODO: Replace this
environment:
sdk: ">=2.18.4 <3.0.0"
dependencies:
analyzer: ^5.3.1
build: ^2.3.1
collection: ^1.17.0
fletch_annotations:
path: ../fletch_annotations
source_gen: ^1.2.6
dev_dependencies:
build_config: ^1.1.1
build_runner: ^2.3.2
build_test: ^2.1.5
flutter_lints: ^2.0.1
test: ^1.22.1
fletch_generator
has corresponding model classes:
package:fletch_generator/src/model/column.dart
import 'package:fletch_annotations/fletch_annotations.dart';
class Column {
final FletchDataType dataType;
final bool isNullable;
final OnConflictAction onConflictAction;
final OnDeleteAction onDeleteAction;
final OnUpdateAction onUpdateAction;
final bool isUnique;
final String? columnName;
final dynamic defaultValue;
final String? check;
Column({
required this.dataType,
required this.isNullable,
this.onConflictAction = OnConflictAction.abort,
this.onDeleteAction = OnDeleteAction.setDefault,
this.onUpdateAction = OnUpdateAction.setDefault,
this.isUnique = false,
this.columnName,
this.defaultValue,
this.check,
});
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Column &&
runtimeType == other.runtimeType &&
dataType == other.dataType &&
isNullable == other.isNullable &&
onConflictAction == other.onConflictAction &&
onDeleteAction == other.onDeleteAction &&
onUpdateAction == other.onUpdateAction &&
isUnique == other.isUnique &&
columnName == other.columnName &&
defaultValue == other.defaultValue &&
check == other.check;
@override
int get hashCode {
return Object.hash(dataType, columnName, defaultValue, check);
}
@override
String toString() {
return 'Column('
'dataType: $dataType, '
'isNullable: $isNullable ,'
'onConflictAction: $onConflictAction ,'
'onDeleteAction: $onDeleteAction ,'
'onUpdateAction: $onUpdateAction ,'
'isUnique: $isUnique ,'
'columnName: $columnName, '
'defaultValue: $defaultValue, '
'check: $check'
')';
}
}
My build.yaml
file in fletch_generator
:
fletch_generator/build.yaml
targets:
$default:
builders:
fletch_generator:
enabled: true
source_gen|combining_builder:
enabled: true
options:
ignore_for_file:
- lines_longer_than_80_chars
- lint_alpha
- lint_beta
build_extensions: '"{{dir}}/entity/{{file}}.dart": "{{dir}}/dao/{{file}}.g.dart"'
builders:
fletch_generator:
import: "package:fletch_generator/builder.dart"
builder_factories: ["fletchBuilder"]
build_extensions: { ".dart": [".fletch.g.part"] }
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]
build.dart
, also in fletch_generator
(in the lib
directory):
package:fletch_generator/build.dart
import 'package:build/build.dart';
import 'package:fletch_generator/src/generator/column.dart';
import 'package:source_gen/source_gen.dart';
Builder fletchBuilder(final BuilderOptions _) =>
SharedPartBuilder([ColumnGenerator()], 'fletch');
And finally, the fletch_generator
definition for ColumnGenerator
:
package:fletch_generator/src/generator/column.dart
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:fletch_annotations/fletch_annotations.dart' as annotations;
import 'package:source_gen/source_gen.dart';
class ColumnGenerator extends GeneratorForAnnotation<annotations.Column> {
@override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
return '// ColumnProcessor works!';
}
}
In fletch_example
, I have a sample_class
that utilizes the Column
annotation:
import 'package:fletch_annotations/fletch_annotations.dart';
part '../dao/sample_class.g.dart';
class SampleClass {
@Column(
dataType: FletchDataType.bigInt,
isNullable: false,
)
final int? sampleClassIndex;
SampleClass({
this.sampleClassIndex,
});
}
As well as the appropriate dependencies in the pubspec.yaml
file for fletch_example
:
name: fletch_example
description: A starting point for Dart libraries or applications.
version: 1.0.0
# homepage: https://www.example.com
environment:
sdk: ">=2.18.4 <3.0.0"
dependencies:
fletch_annotations:
path: ../fletch_annotations
dev_dependencies:
build_runner: ^2.3.2
fletch_generator:
path: ../fletch_generator
flutter_lints: ^2.0.1
lints: ^2.0.0
test: ^1.16.0
In my fletch_example
directory, I run the following commands:
flutter clean
flutter pub get
flutter packages pub run build_runner build --delete-conflicting-outputs
This generates the following console output:
[INFO] Generating build script...
[INFO] Generating build script completed, took 525ms
[INFO] Precompiling build script......
[INFO] Precompiling build script... completed, took 9.5s
[INFO] Initializing inputs
[INFO] Building new asset graph...
[INFO] Building new asset graph completed, took 1.3s
[INFO] Checking for unexpected pre-existing outputs....
[INFO] Checking for unexpected pre-existing outputs. completed, took 1ms
[INFO] Running build...
[INFO] Generating SDK summary...
[INFO] 2.8s elapsed, 0/1 actions completed.
[INFO] 5.3s elapsed, 0/1 actions completed.
[INFO] Generating SDK summary completed, took 6.2s
[INFO] Running build completed, took 6.3s
[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 40ms
[INFO] Succeeded after 6.4s with 0 outputs (2 actions)
It also creates the following fletch_example/.dart_tool/build/entrypoint/build.dart
file:
// ignore_for_file: directives_ordering
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:build_runner_core/build_runner_core.dart' as _i1;
import 'package:fletch_generator/builder.dart' as _i2;
import 'package:source_gen/builder.dart' as _i3;
import 'dart:isolate' as _i4;
import 'package:build_runner/build_runner.dart' as _i5;
import 'dart:io' as _i6;
final _builders = <_i1.BuilderApplication>[
_i1.apply(
r'fletch_generator:fletch_generator',
[_i2.fletchBuilder],
_i1.toDependentsOf(r'fletch_generator'),
hideOutput: true,
appliesBuilders: const [r'source_gen:combining_builder'],
),
_i1.apply(
r'source_gen:combining_builder',
[_i3.combiningBuilder],
_i1.toNoneByDefault(),
hideOutput: false,
appliesBuilders: const [r'source_gen:part_cleanup'],
),
_i1.applyPostProcess(
r'source_gen:part_cleanup',
_i3.partCleanup,
),
];
void main(
List<String> args, [
_i4.SendPort? sendPort,
]) async {
var result = await _i5.run(
args,
_builders,
);
sendPort?.send(result);
_i6.exitCode = result;
}
My expectation is that a fletch_example/lib/src/model/dao/sample_class.g.dart
file would be created containing the following:
// ColumnProcessor works!
If anyone could point me in the right direction, it would be so, incredibly appreciated!
A GeneratorForAnnotation
only runs for top-level elements (like classes or top-level fields or methods) in a library. In your example, the @Column
annotation is added to an instance member which does not trigger the generator.
If you can, the easiest way to fix this would be to exceptionally change your generator so that classes with @Column
annotations on their fields also need an annotation on the class declaration.
Alternatively, you could use something like this to respect column annotations in class members as well:
class ColumnGenerator extends Generator {
static const _checker = TypeChecker.fromRuntime(annotations.Column);
@override
Future<String> generate(LibraryReader library, BuildStep buildStep) async {
final values = <String>{};
for (final classMember in library.classes) {
for (final child in classMember.children) {
final annotation = _checker.firstAnnotationOf(child);
if (annotation != null) {
values.add(await generateForAnnotatedElement(
child, ConstantReader(annotation), buildStep));
}
}
}
return values.join('\n\n');
}
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
return '// ColumnProcessor works!';
}
}