Outputs to source
will generally be published with the package on pub. This
can be risky. If the generated code depends on any details from the current
pub solve - that is it reads information from dependencies - then a consumer of
the package which has different versions of dependencies in their pub solve
may be broken. If the generated code imports any libraries, including from
the package providing the builder, it must only use the API surface area which
is guaranteed to not break without a major version bump.
Outputs to cache
will never be published with the package, and if they are
required to compile or run, then the build system must be used by every consumer
of the code. This is always safe, but may place limitations on the end user.
Any outputs which are used during a build to produce other outputs, but don't
need to be compiled or seen by the user, should also be built to cache
.
This can be a very expensive operation, depending on a number of factors.
The first time this is done in the build process, all transitive imports of the file have to be parsed and analyzed, and even the entire SDK may have to be analyzed.
We have some optimizations to make this cheaper for subsequent builders, but they can be circumvented by certain package structures. You should assume the cost will be at least on the order of the number of all transitive imports.
The build step will also be invalidated if any transitive import of the resolved library changes, even in the best case scenarios.
You should only use a resolved library if you absolutely need on. If you can use simply a parsed compilation unit instead, you should consider using that.
Due to build restrictions - namely that a builder can't read the outputs produced by other builders in the same phase - it's sometimes necessary to write information to a temporary file in one phase, then read that in a subsequent phase, but the intermediate result is not a useful output outside of the build.
Use a PostProcessBuilder
to "delete" files so they are not included in the
merged output directory or available through the development server. Note that
files are never deleted from disk, instead a "delete" by a PostProcessBuilder
acts a filter on what assets can be seen in the result of the build. This works
best if temporary assets have a unique extension.
The FileDeletingBuilder
from the build
package is designed for this case and
only needs to be configured with the extensions it should remove. In some cases
the builder should only operate in release mode so the files can see be seen in
development mode - use the isEnabled
argument to the constructor rather than
returning a different builder or passing a different set of extensions - if the
extensions change between modes it will invalidate the entire build.
For example:
// In lib/builder.dart
PostProcessBuilder temporaryFileCleanup(BuilderOptions options) =>
FileDeletingBuilder(const ['.used_during_build'],
isEnabled: options.config['enabled'] as bool? ?? false);
Builder writesTemporary([_]) => ...
Builder readsTemporaryWritesPermanent([_]) => ...
builders:
my_builder:
import: "package:my_package/builders.dart"
builder_factories:
- writesTemporary
- readsTemporaryWritesPermanent
build_extensions:
.dart:
- .used_during_build
- .output_for_real
auto_apply: dependents
applies_builders:
- my_package|temporary_file_cleanup
post_process_builders:
temporary_file_cleanup:
import: "package:my_package/builders.dart"
builder_factory: temporaryFileCleanup
defaults:
release_options:
enabled: true
After running a build, or by running the generate-build-script
command, a
build script will be written to .dart_tool/build/entrypoint/build.dart
. This
is a Dart VM application that can be run manually, including with debugging
enabled. See the devtool docs or IntelliJ debugging docs
for usage instructions. The build script takes the same arguments as dart run build_runner
, for example:
dart --observe --pause-isolates-on-start .dart_tool/build/entrypoint/build.dart build
A builder may only read a generated output if it is ordered after the builder
that emitted it. Ordering can be adjusted using the runs_before
or
required_inputs
configuration options for builders. In particular this can be
tricky for the SharedPartBuilder
from package:source_gen
. When using this
utility it is the source_gen:combining_builder
which emits the Dart code in a
.g.dart
file, and this always runs after all builders emitting .g.part
files. No SharedPartBuilder
will be able to resolve the Dart code emitted by
another SharedPartBuilder
. In order for emitted code to be used with the
Resolver
by later build steps, it must write to a .dart
file directly, and
should be a different file extension than .g.dart
.