lib/$lib$
as input and manifest.dart
as output. It could then use buildStep.findAssets
to find the Dart files emitted by your first builder. You also need to add a runs_before
to the first builder to ensure all Dart files are ready when the aggregate builder runs.
I have another question: how do I force a build_runner to process nested files. I have the following build.yaml
:
targets:
$default:
builders:
dirk_code_builder:
enabled: True
builders:
dirk_code_builder:
import: "package:dirk/builder.dart"
builder_factories: ["dirkCodeBuilder"]
build_extensions:
'$lib$': [".dirk.dart"]
auto_apply: dependents
build_to: source
defaults:
options:
input_folder: lib/views/
output_folder: lib/views/code/
start_file: _start.dirk.html
layout_file: layout.dirk.html
My builder source code is the following:
const String _inputFileExtension = '.dirk.html';
const String _outputFileExtension = '.dirk.dart';
class DirkCodeGenerator implements Builder {
Map<String, List<String>> _buildExtensionsCopy;
@override
Map<String, List<String>> get buildExtensions =>
_buildExtensionsCopy ??= generateExtensions(_options);
...
}
Map<String, List<String>> generateExtensions(Map<String, dynamic> options) {
final filesList = Directory(options['input_folder'])
.listSync(recursive: true)
.where(
(FileSystemEntity file) => file.path.endsWith(_inputFileExtension));
final result = <String, List<String>>{};
filesList.forEach((FileSystemEntity element) {
final name = split(element.path).last.split('.').first.replaceAll('-', '_');
result[element.path] = <String>[
'${options["output_folder"]}$name$_outputFileExtension',
];
});
print(result);
return result;
}
The problem is: generateExtensions
sees nested files in /lib/views/
, for example /lib/views/shared/menu.dirk.html
, but in the build
method they are absent, only direct children of /lib/views/
are present. Thank you.
There are a bunch of things that are looking off here: First, buildExtensions
should almost always be a constant. There are a few exceptions for builders re-usable across projects, but that's not what you have here. Also, don't ever use File
or Directory
inside a builder. The build
package generalizes over the way assets are stored (there could be build implementations fetching assets over a network, and build implementations might write some assets to a hidden folder in .dart_tool
). It also breaks mechanisms like incremental rebuilds.
I'm not sure what exactly your builder is doing. Does it create one .dirk.dart
file from a .dirk.html
input, but in another folder? If you tell us a bit more about what your builder is doing we can find a way to express that without using raw io or dynamic build extensions.
generateExtensions
part was taken from a swagger-dart-code-generator, and comes from insufficient understanding of the build process, I will read the docs to find a better way, since you pointed that build
abstractions are more powerful regarding the source of the files. I uploaded the project to github dirk repo for future reference. Simply put, I'm developing a view engine with code interpolation, and it, yes, creates a .dirk.dart
file for each .dirk.html
. For now, the source folder is /lib/views and the output is /lib/views/code. I'm still learning dart and its ecosystem, so my work may be far from a masterpiece :)
Ah, I see. It sounds like your builder essentially operates on each file independently, right? In that case, I would probably ditch the aggregate builder in favor of a regular builder - take .dirk.html
as input and .dirk.dart
as output. To only run on files in /lib/views
by default, you don't need a build option with globs. Instead, you can configure this when you define your builder:
builders:
dirk_code_builder:
build_extensions: {'.dirk.html': '.dirk.dart'}
# ...
defaults:
generate_for: ['lib/views/**']
Users can override generate_for
if they want to. And in general, a single build step is the smallest unit of invalidation in the build system. So if you want to process more than one file, doing that in multiple build steps enables fast and incremental rebuilds. If you have a single build step where you do everything by matching a glob, you'd have to re-process every file on every edit. With those simple extensions, your builder gets a new build step for each file.
The builder definition from above has the problem that generated code goes to lib/views
as well (not in lib/views/code
). Allowing directory moves in builders is not well supported at the moment (dart-lang/build#1689 ), so I think it might be reasonable to just generate the files in lib/views
as well. You could implement directory moves with a combination of three builders:
build_to: cache
) builder from .dirk.html
to .dirk.dart
. Users would never see the generated files in lib/views
since this is only for intermediate resultsbuild_extensions: {'lib/$lib$': ['views.tar']}
, build_to: cache
) that writes all the generated files into a tar filelib/views/code/
.In addition to being complicated, this approach has downsides as well (other builders couldn't read the final .dirk.dart
files for instance). Each regular (non post-processing) builders may only have a fixed amount of outputs per primary input, and they must be relative to the input file. This might sound very limiting, but it's exactly what enables build_runner
to do fast and incremental rebuilds. So everything that prevents fast units of work tends to be a little bit more complicated :)
--production
flag
_shouldSkipNode
to manually exclude nodes that were generated and don't end in .dart.js
.
Hi everyone, i have a question. I'm using json_serializable and firestore. json_serializable creates a factory method with Map<String, dynamic> as parameter but after nullsafety changes, firestore start to return a Map<String, dynamic>? as document data and now i can't call json_serializable factory to map my firestore types. can someone help me with this?
I wrote this example:
class Entity {
const Entity({required this.id});
final String id;
factory Entity.fromJson(Map<String, dynamic> json) => _$EntityFromJson(json);
Map<String, dynamic> toJson() => _$EntityToJson(this);
}
class Repository {
Stream<List<Entity>> list() {
return FirebaseFirestore.instance.collection('entity').snapshots().map((event) {
return event.docs.map((e) {
return Entity.fromJson(
e.data() // The argument type 'Map<String, dynamic>?' can't be assigned to the parameter type 'Map<String, dynamic>'
);
}).toList();
});
}
}
Hi everyone,
I noticed that - after json_serializable 4.0 update - I cannot use it to generate JSON for my project anymore (needless to say, my project is not null-safe).
As the main option (downgrading json_serializable) is not available for me (I have other packages conflicts that needs me to keep latest version), I'm currently trying to make json_serializable >= 4.0 work on an unsafe nullable environment.
Should I raise an issue on GitHub?
Do someone has any ideas?
Hi @caiogranero. Unfortunately, this is not the problem... I forgot to say that I cannot even generate my .g.dart files as I cannot use json_serializable >=4.0 in a nullable environment.
Your problem can easily be solved by regenerating your files with latest json_serializable: if you, for instance, declare a string like final String uid;
, then json_serializable will act as if it's surely not null on Firebase and will throw an error otherwise.
If you make that string nullable, json_serializable will generate a nullable deserializer and let the string null if it's null on Firebase.
runs_before
and applies_builders
but nothing seems to work… any suggestions? Thanks!
targets:
openapi_client_builder:
builders:
openapi_client_builder:schemaBuilder:
enabled: true
openapi_client_builder:clientBuilder:
enabled: true
builders:
schemaBuilder:
import: package:openapi_client_builder/openapi_schema_builder.dart
builder_factories: [ schemaBuilder ]
build_extensions:
input/openapi_spec.md: [ output/schema_classes.dart ]
build_to: source
auto_apply: root_package
runs_before: [ openapi_client_builder:clientBuilder ]
clientBuilder:
import: package:openapi_client_builder/openapi_client_builder.dart
builder_factories: [ clientBuilder ]
build_extensions:
input/app-store-connect-openapi.json: [ output/client.dart ]
build_to: source
auto_apply: root_package
wanting Dart code generated in a build step to be part of the running program in a later step
Yup, that doesn't work. The build script is compiled ahead of time, so builders can't generate themselves :D The runs_before
option only reflects that assets generated by schemaBuilder
can be read by clientBuilder
.
so I guess I would need to have separate packages, one dependent on the other, and run build_runner separately on each in this case
That would be a solution, but really I would try to avoid a situation where you need to run build_runner in a specific order for each change. That will be very annoying in practice. Maybe you can rewrite your builders in such a way that none depends on generated code? I could envision a build setup like this:
build_to: cache
builder transforming the markdown into a json structure that your builders can pick upschemaBuilder
take that json as input and use that to write the schema classesclientBuilder
take the two jsons to figure out the right client code without importing it itself.useGrpc
flag, but not found others. Could anyone shed some light?
if (options.useGrpc) {
if (grpcGenerators.isNotEmpty) {
files.add(makeFile(".pbgrpc.dart", generateGrpcFile(config)));
}
} else {
files.add(makeFile(".pbserver.dart", generateServerFile(config)));
}
Hi!
Is it possible to have two targets in build.yaml
that use the same builder but with different options on different inputs?
My config looks like:
targets:
$default:
builders:
build_web_compilers|entrypoint:
generate_for:
- web/t1/main.dart
options:
compiler: dart2js
dart2js_args:
- -DDEF=t1
web2:
builders:
build_web_compilers|entrypoint:
generate_for:
- web/t2/main.dart
options:
compiler: dart2js
dart2js_args:
- -DDEF=t2
Running dart run build_runner build --release
results in
[SEVERE] Conflicting outputs
Both build_modules:module_library and build_modules:module_library may output lib/foo.module.library. Potential outputs must be unique across all builders. See https://github.com/dart-lang/build/blob/master/docs/faq.md#why-do-build
ers-need-unique-outputs
Thanks!
build_runner
does not like that, since each output needs to have a well-defined target. I'd probably define three targets: One default target that just disables the entrypoint builder and then the two targets you have, except that they should only apply the entrypoint:targets:
$default:
builders:
build_web_compilers|entrypoint:
enabled: false
web1:
auto_apply_builders: false
dependencies: ['$default']
builders:
build_web_compilers|entrypoint:
enabled: true
generate_for:
- web/t1/main.dart
options: # ...
web2:
auto_apply_builders: false
dependencies: ['$default']
builders:
build_web_compilers|entrypoint:
enabled: true
generate_for:
- web/t2/main.dart
options: # ...
build_modules
, which are doing some preparation work for compilation, would be applied to the default target only. The web1/web2 targets then use the the generated module information from the default target, which is why they need to depend on it. If you have any codegen builders like json_serializable
or built_value
, those would also run in the default target.
.dart
). It reads this meta file when visiting each dart file to see if that dart file is the "primary" of a larger module, if not it just returns and outputs nothing. If so then it creates the module.
.html
as its primary input, if you are creating one of these for each html file effectively.