I am working on a dart package with includes over 200 models and at the moment i have to write manually one line of "export" for each model, to make the models available for everyone who uses this package.
I want the build runner to generate one dart file which contains every export definition.
Therefore I would create an annotation "ExportModel". The builder should search for each class annotated with this annotation.
I tried creating some Builders, but they will generate a *.g.dart file for each class that is annotated. I just want to have one file.
Is where a way to create a builder that runs only once and creates a file at the end ?
The short answer to your question of a builder that only runs once and creates a single file in the package is to use r'$lib$'
as the input extension. The long answer is that to find the classes that are annotated you probably want an intermediate output to track them.
I'd write this with 2 builders, one to search for the ExportModel
annotation, and another to write the exports file. Here is a rough sketch with details omitted - I haven't tested any of the code here but it should get you started on the right path.
@ExportModel()
.Could write with some utilities from package:source_gen
, but can't use LibraryBuilder
since it's not outputting Dart code...
Goal is to write a .exports
file next to each .dart
file which as the name of all the classes that are annotated with @ExportModel()
.
class ExportLocatingBuilder implements Builder {
@override
final buildExtensions = const {
'.dart': ['.exports']
};
@override
Future<void> build(BuildStep buildStep) async {
final resolver = buildStep.resolver;
if (!await resolver.isLibrary(buildStep.inputId)) return;
final lib = LibraryReader(await buildStep.inputLibrary);
final exportAnnotation = TypeChecker.fromRuntime(ExportModel);
final annotated = [
for (var member in lib.annotatedWith(exportAnnotation)) element.name,
];
if (annotated.isNotEmpty) {
buildStep.writeAsString(
buildStep.inputId.changeExtension('.exports'), annotated.join(','));
}
}
}
This builder should be build_to: cache
and you may want to have a PostProcessBuilder
that cleans up all the outputs it produces which would be specified with applies_builder
. You can use the FileDeletingBuilder
to cheaply implement the cleanup. See the FAQ on temporary outputs and the angular cleanup for example.
.exports
files and generate a Dart fileUse findAssets
to track down all those .exports
files, and write an export
statement for each one. Use a show
with the content of the file which should contain the names of the members that were annotated.
class ExportsBuilder implements Builder {
@override
final buildExtensions = const {
r'$lib$': ['exports.dart']
};
@override
Future<void> build(BuildStep buildStep) async {
final exports = buildStep.findAssets(Glob('**/*.exports'));
final content = [
await for (var exportLibrary in exports)
'export \'${exportLibrary.changeExtension('.dart').uri}\' '
'show ${await buildStep.readAsString(exportLibrary)};',
];
if (content.isNotEmpty) {
buildStep.writeAsString(
AssetId(buildStep.inputId.package, 'lib/exports.dart'),
content.join('\n'));
}
}
}
This builder should likely be build_to: source
if you want to publish this file on pub. It should have a required_inputs: [".exports"]
to ensure it runs after the previous builder.
You could implement this as a single builder which uses findAssets
to find all the Dart files. The downside is that rebuilds would be much slower because it would be invalidated by any content change in any Dart file and you'd end up parsing all Dart code for a change in any Dart code. With the 2 builder approach then only the individual .exports
which come from a changed Dart file need to be resolved and rebuilt on a change, and then only if the exports change will the exports.dart
file be invalidated.
Older versions of build_runner
also didn't support using the Resolver
to resolve code that isn't transitively imported from the input library. Recent version of build_runner
have relaxed this constraint.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With