build 0.7.1 build: ^0.7.1 copied to clipboard
A build system for Dart.
#
build
#
Defines the basic pieces of how a build happens and how they interact.
Builder
class #
The business logic for code generation. Most consumers of the build
package
will create custom implementations of Builder.
BuildStep
class #
The way a Builder
interacts with the outside world. Defines the unit of work
and allows reading/writing files and resolving code.
Resolver
class #
An interface into the dart analyzer to allow resolution of code that needs generation.
Differences between the build
package and pub
. #
You might be asking, why use this package instead of a pub Transformer
? There
are a few key differences that make this package better for some use cases.
Outputs #
Pub 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.
Inputs #
With build
, you can use any file from any package you depend on as a primary
input, where with pub
you can only use files from your current package.
Consistency #
You can't overwrite any pre-existing files using build
, you can only generate
new ones.
In pub a transformer could 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 declare outptus which allows fine
grained and incremental builds. See the build_runner
package for an approach
to incremental builds.
With pub
, 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 pub Transformer
in the past, then the Builder
api
should be familiar to you. The main difference is that Builders
must always
declare their outputs, similar to a DeclaringTransformer
.
The basic api looks like this:
abstract class Builder {
/// You can only output files in `build` that you declare here. You are not
/// required to output all of these files, but no other [Builder] is allowed
/// to declare the same outputs.
List<AssetId> declareOutputs(AssetId input);
/// 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 just one input. This is a fully realized [Asset]
/// with its [stringContents] already available.
var input = buildStep.input;
/// Create a new [Asset], with the new [AssetId].
var copy = new Asset(_copiedId(input.id), input.stringContents);
/// Write out the [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);
}
/// Declare your outputs, just one file in this case.
List<AssetId> declareOutputs(AssetId inputId) => [_copiedId(inputId)];
AssetId _copiedId(AssetId inputId) => inputId.addExtension('$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#resolve
method. This makes sure that all Builder
s in the system share the same
analysis context, which greatly speeds up the overall system when multiple
Builder
s are doing resolution. Additionally, it handles for you making the
analyzer work in an async environment.
This Resolver
has a subset of the apis of the one from code_transformers
, so
migrating to it should be easy if you have used code_transformers
in the past.
Here is an example of a Builder
which uses the resolve
method:
class ResolvingCopyBuilder {
Future build(BuildStep buildStep) {
/// Resolves all libraries reachable from the primary input.
var resolver = await buildStep.resolve(buildStep.input.id);
/// Get a [LibraryElement] by asset id.
var entryLib = resolver.getLibrary(buildStep.input.id);
/// Or get a [LibraryElement] by name.
var otherLib = resolver.getLibraryByName('my.library');
/// **IMPORTANT**: If you don't release a resolver, your builds will hang.
resolver.release();
}
/// Declare 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.
Important Note: As shown in the code above, you must call release
on
your resolver when you are done. If you don't then the next call to resolve
will never complete.
Features and bugs #
Please file feature requests and bugs at the issue tracker.