Skip to content

Latest commit

 

History

History
115 lines (94 loc) · 5.29 KB

builder_author_faq.md

File metadata and controls

115 lines (94 loc) · 5.29 KB

When should a Builder build to cache vs source?

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.

How expensive is it to get a resolved library (ex: buildStep.inputLibrary)

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.

How can I have temporary outputs only used during the Build?

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

How can I debug my builder?

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

Why can't my builder resolve code output by another builder?

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.