build 0.12.0+2 
build: ^0.12.0+2 copied to clipboard
A build system for Dart.
 #
build #
Defines the basic pieces of how a build happens and how they interact.
Builder #
The business logic for code generation. Most consumers of the build package
will create custom implementations of Builder.
BuildStep #
The way a Builder interacts with the outside world. Defines the unit of work
and allows reading/writing files and resolving Dart source code.
Resolver class #
An interface into the dart analyzer to allow resolution of code that needs static analysis and/or code generation.
Differences between the build package and pub + barback. #
You might be asking, why use this package instead of a barback Transformer?
There are a few key differences that make this package better for most use
cases.
Also see https://github.com/dart-lang/build/wiki/Writing-a-Builder.
Outputs #
pub build will only ever output files into your build directory, and
pub serve only ever outputs them into an in-memory file system. With the
build package, you can output files anywhere in the current package.
This enables you to generate dart files which are imported by your project, without getting any warnings about missing files from the analyzer. At the same time it enables you to easily go explore those files in your editor, and set breakpoints inside those files.
Consistency #
You can't overwrite any pre-existing files using build, you can only generate
new ones.
In barback a transformer can overwrite a file any number of times, and this
can lead to confusion and difficulty when debugging, especially with source
maps.
Incremental builds #
The build package requires Builders to configure which output file extensions
will be built for corresponding input file extensions. This allows fine grained
and incremental builds. See the build_runner package for an approach to
incremental builds.
With barback, some transformations on your package dependencies may be cached,
but any transformations on your current package are always redone each time you
call pub build or pub serve. In serve mode it will do incremental builds
once the first build has run, but if you restart it then you have to start over
from scratch.
Execution modes and reflection #
Most frameworks that use reflection today enable running in two different modes,
one which uses dart:mirrors for development in dartium, and one which uses
static code generated by a Transformer for deployment. All features that use
reflection have to be implemented in both modes, and bugs might exist in only
one mode. This all ends up resulting in obscure deployment time only issues,
as well as a lot of wasted cycles testing the same app in multiple different
modes.
With build, you can eliminate entirely the dart:mirrors mode. You are always
using the generated code, all the time. This makes it easier on framework devs,
and easier on users who have fewer modes to support.
Implementing your own Builders #
If you have written a barback Transformer in the past, then the
Builder API should be familiar to you. The main difference
is that Builders must always configure outputs based on input extensions.
The basic API looks like this:
abstract class Builder {
  /// You can only output files in `build` that are configured here. You are not
  /// required to output all of these files, but no other [Builder] is allowed
  /// to produce the same outputs.
  Map<String, List<String>> get buildExtensions;
  /// Similar to `Transformer.apply`. This is where you build and output files.
  Future build(BuildStep buildStep);
}
Here is an implementation of a Builder which just copies files to other files
with the same name, but an additional extension:
/// A really simple [Builder], it just makes copies!
class CopyBuilder implements Builder {
  final String extension;
  CopyBuilder(this.extension)
  Future build(BuildStep buildStep) async {
    /// Each [buildStep] has a single input.
    var input = buildStep.inputId;
    /// Create a new target [AssetId] based on the old one.
    var copy = inputId.addExtension(extension);
    var contents = await buildStep.readAsString(input);
    /// Write out the new asset.
    ///
    /// There is no need to `await` here, the system handles waiting on these
    /// files as necessary before advancing to the next phase.
    buildStep.writeAsString(copy, contents);
  }
  /// Configure output extensions. All possible inputs match the empty input
  /// extension. For each input 1 output is created with `extension` appended to
  /// the path.
  Map<String, List<String>> get buildExtensions =>  {'': [extension]};
}
It should be noted that you should never touch the file system directly. Go
through the buildStep#readAsString and buildStep#writeAsString methods in
order to read and write assets. This is what enables the package to track all of
your dependencies and do incremental rebuilds. It is also what enables your
Builder to run on different environments.
Using the analyzer #
If you need to do analyzer resolution, you can use the BuildStep#resolver
object. This makes sure that all Builders in the system share the same
analysis context, which greatly speeds up the overall system when multiple
Builders are doing resolution.
Here is an example of a Builder which uses the resolve method:
class ResolvingCopyBuilder {
  Future build(BuildStep buildStep) async {
    // Get the [LibraryElement] for the primary input.
    var entryLib = buildStep.inputLibrary;
    // Resolves all libraries reachable from the primary input.
    var resolver = await buildStep.resolver;
    // Get a [LibraryElement] for another asset.
    var otherLib = resolver.getLibrary(new AssetId.resolve('some_import.dart'),
        from: buildStep.inputId);
    // Or get a [LibraryElement] by name.
    var otherLib = resolver.getLibraryByName('my.library');
  }
  /// Configure outputs as well....
}
Once you have gotten a LibraryElement using one of the methods on Resolver,
you are now just using the regular analyzer package to explore your app.
Sharing expensive objects across build steps #
The build package includes a Resource class, which can give you an instance
of an expensive object that is guaranteed to be unique across builds, but may
be re-used by multiple build steps within a single build (to the extent that
the implementation allows). It also gives you a way of disposing of your
resource at the end of its lifecycle.
The Resource<T> constructor takes a single required argument which is a
factory function that returns a FutureOr<T>. There is also a named argument
dispose which is called at the end of life for the resource, with the
instance that should be disposed. This returns a FutureOr<dynamic>.
So a simple example Resource would look like this:
final resource = new Resource(
  () => createMyExpensiveResource(),
  dispose: (instance) async {
    await instance.doSomeCleanup();
  });
You can get an instance of the underlying resource by using the
BuildStep#fetchResource method, whose type signature looks like
Future<T> fetchResource<T>(Resource<T>).
Important Note: It may be tempting to try and use a Resource instance to
cache information from previous build steps (or even assets), but this should
be avoided because it can break the soundness of the build, and may introduce
subtle bugs for incremental builds (remember the whole build doesn't run every
time!). The build package relies on the BuildStep#canRead and
BuildStep#readAs* methods to track build step dependencies, so sidestepping
those can and will break the dependency tracking, resulting in inconsistent and
stale assets.
Features and bugs #
Please file feature requests and bugs at the issue tracker.