The build_it
is a builder that makes publicly available third-party source code generators and runs them during the build process for rapid development.
Version 0.2.9 (BETA)
TODO:
- Add the development
build_it
JSON generator (build_it:json_dev
) that will generate a file with the.dart
extension with JSON classes (generated byjson_serializable
) without thepart
andpart of
directives (and without the files with extensions.g.dart
). This will be done by combining the parts into one file. This generator allows you to create JSON classes that will not be deleted during build process and will always be available.
The build_it
builder simplifies several development steps at once:
- Source code generators are given full (and simplified) access to build steps: build and post build
- Allows you to easily use your own generator without delving too much into the principles of the build process (generate whatever you want)
- You can use your own data description format to configure your generator in YAML format (it is recommended to use JSON models for this purpose)
- You can use third-party source code generators for rapid development, you just need to know the configuration description format (and what they end up creating)
- Includes built-in
build_it:json
JSON generator maximum compatible with packagejson_serializable
and its annotations - A built-in JSON generator is provided to simplify the creation of generators specification models, or it can be used to generate code for JSON classes that are commonly used
The build_it
builder is a common builder for the Dart build system. It is intended for building Dart projects.
It is configured and for those who use it there is no need to worry about how it works.
On the other hand, it allows you to use third-party (or your own) code generators quite simply without the need to know how it all works.
If you need to generate code based on some configuration, then you simply create a configuration file for a specific generator and build_it
builder does all the work for you (runs the corresponding generator).
Create a file named your_generator_name_build_it.dart
.
That is, add _build_it.dart
at the end of the generator file name.
Your generator will be available to anyone who adds dependencies to your package.
The generator will be available with the name your_package_name:generator_name
.
Or, if your generator has the same name as your package, then its alias (the short name) will be your_package_name
.
If necessary, you can hide it away in your package.
Eg. src/foo/bar/baz_build_it.dart
Input configuration files must be in the build_it
format.
This is a non-existent format (don't try to find a description). This format is only intended to be able to identify data in this format.
This format is very simple. The data format is YAML
.
You just need to specify the correct YAML metadata section
in a certain way.
The header format (YAML metadata section
) is shown below.
---
format:
name: build_it
generator:
name: string
language:
version: string?
---
# Configuration goes here
The build_it
builder directly executes third-party (or your own) generators for you.
The builder executes thеse generators that generates ready-to-use source code (or source code for other builders or generators).
The configurations for generators are described in the YAML
format.
Configurations are based on specifications.
That is, you describe the configuration in the YAML
format according to the specification. Because the target generator will use this configuration for its work.
First, add a dependency to the pubspec.yaml
file:
dependencies:
json_annotation: ^4.0.0
dev_dependencies:
build_it: ^0.2.3
json_serializable: ^4.0.2
Suppose you want to create a generator named example
.
Create a file named example_build_it.dart
in the lib
folder.
Add the following directive (for data exchange with the builder):
import 'package:build_it/build_it_models.dart';
Create a main
method:
Future<void> main(List<String> args, [response]) async {
//
}
And add some code to it.
import 'package:build_it/build_it_helper.dart';
import 'package:json_helpers/json_helpers.dart';
Future<void> main(List<String> args, [message]) async {
return await buildIt(args, message, _build);
}
Future<BuildResult> _build(BuildConfig config) async {
const _template = '''
void main() {
stdout.writeln('Hello, {{NAME}}');
}
''';
final data = config.data.json((e) => Data.fromMap(e));
final name = data.name;
final code = <String>[];
final template = _template.replaceAll('{{NAME}}', name);
code.add(template);
final directives = <Directive>[];
directives.add(Directive(type: 'import', url: 'dart:io'));
return BuildResult(code: code.join('\n'), directives: directives);
}
class Data {
final String name;
Data({required this.name});
factory Data.fromMap(Map<String, dynamic> json) {
return Data(name: (json['name'] as String?) ?? '');
}
}
''';
Then create your config file:
example_text.yaml
---
format:
name: build_it
generator:
name: build_it_test:example
---
name: Jack
We will assume that your package is named build_it_test
.
Accordingly, the public available name of your generator is build_it_test:example
.
Everything is ready, you can start the build process:
dart run build_runner build
Below is the build (and generation) result:
example_text.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
import 'dart:io';
// **************************************************************************
// build_it: build_it_test:example
// **************************************************************************
void main() {
stdout.writeln('Hello, Jack');
}
Yes, it’s not impressive, but we didn't put much effort into it.
It is best to use the package code_builder.
Another way is to use a template engine (for example, mustache).
A good generator should have a format specification and use JSON models to work with the configuration.
Package build_it
offers a built-in JSON generator. This generator will be improved soon. You can use it to generate JSON models for your generator.
For a generator to work well, a specification is required to describe the configuration and to work with the configuration.
JSON
classes are very well suited for this purpose. They are convenient for modeling and data processing.
It is not very pleasant to write such classes by hand, it is a common routine work.
Using the json
generator from the build_it
package can make this work a little easier.
Example of configuration for JSON
generator:
example_classes.yaml
---
format:
name: build_it
generator:
name: build_it:json
---
checkNullSafety: true
jsonSerializable:
anyMap: true
classes:
- name: Category
fields:
- { name: id, type: int? }
- { name: name, type: String? }
- { name: products, type: List<Product>, jsonKey: { defaultValue: [] } }
- name: Product
fields:
- { name: id, type: int? }
- { name: name, type: String? }
- { name: type, type: ProductType, jsonKey: { defaultValue: ProductType.product } }
enums:
- name: ProductType
values:
- { name: product, jsonValue: { value: 0 } }
- { name: service, jsonValue: { value: 1 } }
code: |
int _foo(String s) {
return 41;
}
The result of work:
example_classes.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
import 'package:json_annotation/json_annotation.dart';
part 'example_classes.g.g.dart';
// **************************************************************************
// build_it: build_it:json
// **************************************************************************
@JsonSerializable(anyMap: true)
class Category {
Category({this.id, this.name, required this.products});
/// Creates an instance of 'Category' from a JSON representation
factory Category.fromJson(Map json) => _$CategoryFromJson(json);
int? id;
String? name;
@JsonKey(defaultValue: [])
List<Product> products;
/// Returns a JSON representation of the 'Category' instance.
Map<String, dynamic> toJson() => _$CategoryToJson(this);
}
@JsonSerializable(anyMap: true)
class Product {
Product({this.id, this.name, required this.type});
/// Creates an instance of 'Product' from a JSON representation
factory Product.fromJson(Map json) => _$ProductFromJson(json);
int? id;
String? name;
@JsonKey(defaultValue: ProductType.product)
ProductType type;
/// Returns a JSON representation of the 'Product' instance.
Map<String, dynamic> toJson() => _$ProductToJson(this);
}
enum ProductType {
@JsonValue(0)
product,
@JsonValue(1)
service
}
int _foo(String s) {
return 41;
}
This generator is an example of how and what kind of public generators can be written and used with the build_it
builder.
If you add the following dependencies to your project, then this generator will be available in your project as well.
dependencies:
json_annotation: ^4.0.0
dev_dependencies:
build_it: ^0.2.3
json_serializable: ^4.0.2
Now everyone who adds dependencies will have access to this generator.
This way everyone can easily create useful public generators for everyone.
This applies to all generators intended to use with build_it
builder.
The only way to avoid build conflicts is to not create a Dart file with the same name as the configuration file.
For example, if you are using a configuration file named my_models.yaml
, then do not create a file called my_models.dart
.
The reason for the possible conflict may be that if the file my_models.dart
will generate the file my_models.g.dart
, then there will be a build conflict.
If you need to debug the process of work 'build_it', then you can read the answer here:
[https://github.com/dart-lang/build/blob/master/docs/builder_author_faq.md#how-can-i-debug-my-builder](How can I debug my builder?)
But if you need to debug your code generator, then it is easier to do so:
Move the generator functionality into a separate class and debug it in the usual way.
The builder 'build_it' runs the generator in a separate isolate, this debugging is not very convenient.
To be continued...