|
1 | | -# build [](https://travis-ci.org/dart-lang/build) [](https://coveralls.io/r/dart-lang/build) |
| 1 | +# [](https://travis-ci.org/dart-lang/build) [](https://coveralls.io/r/dart-lang/build) |
2 | 2 |
|
3 | | -A build system for Dart. |
| 3 | +This package provides an way of generating files using Dart code, outside of |
| 4 | +`pub`. These files are generated directly on disk in the current package folder, |
| 5 | +and rebuilds are _incremental_. |
| 6 | + |
| 7 | +## Running Builds |
| 8 | + |
| 9 | +In order to run a build, you write a script which uses one of the three top |
| 10 | +level functions defined by this library: |
| 11 | + |
| 12 | +- **build**: Runs a single build and exits. |
| 13 | +- **watch**: Continuously runs builds as you edit files. |
| 14 | +- **serve**: Same as `watch`, but also provides a basic file server which blocks |
| 15 | + if there are ongoing builds. |
| 16 | + |
| 17 | +All three of these methods have a single required argument, a `PhaseGroup`. This |
| 18 | +is conceptually just a `List<Phase>` with some helper methods. Each of these |
| 19 | +`Phase`s runs sequentially, and blocks until the previous `Phase` is completed. |
| 20 | + |
| 21 | +A single `Phase` may be composed of one or more `BuildAction`s, which are just |
| 22 | +a combination of a single `Builder` and a single `InputSet`. The `Builder` is |
| 23 | +what will actually generate outputs, and the `InputSet` determines what the |
| 24 | +primary inputs to that `Builder` will be. All `BuildAction`s in a `Phase` can |
| 25 | +be ran at the same time, and cannot read in the outputs of any other |
| 26 | +`BuildAction` in the same `Phase`. |
| 27 | + |
| 28 | +Lets look at a very simple example, with a single `BuildAction`. You can ignore |
| 29 | +the `CopyBuilder` for now, just know that its a `Builder` which copies files: |
| 30 | + |
| 31 | +```dart |
| 32 | +import 'package:build/build.dart'; |
| 33 | +
|
| 34 | +main() async { |
| 35 | + /// The [PhaseGroup#singleAction] constructor is a shorthand for: |
| 36 | + /// |
| 37 | + /// new PhaseGroup().newPhase().addAction(builder, inputSet); |
| 38 | + await build(new PhaseGroup.singleAction( |
| 39 | + new CopyBuilder('.copy'), new InputSet('my_package', ['lib/*.dart']))); |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +The above example would copy all `*.dart` files directly under `lib` to |
| 44 | +corresponding `*.dart.copy` files. Each time you run a build, it will check for |
| 45 | +any changes to the input files, and rerun the `CopyBuilder` only for the inputs |
| 46 | +that actually changed. |
| 47 | + |
| 48 | +You can add as many actions as you want to the first phase using the `addAction` |
| 49 | +method, and they will all run at the same time. For example, you could make |
| 50 | +multiple copies: |
| 51 | + |
| 52 | +```dart |
| 53 | +main() async { |
| 54 | + var inputs = new InputSet('my_package', ['lib/*.dart']); |
| 55 | + await build(new PhaseGroup().newPhase() |
| 56 | + ..addAction(new CopyBuilder('.copy1'), inputs) |
| 57 | + ..addAction(new CopyBuilder('.copy2'), inputs)); |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +Lets say however, that you want to make a copy of one of your copies. Since |
| 62 | +no action can read outputs of another action in the same phase, you need to add |
| 63 | +an additional `Phase`: |
| 64 | + |
| 65 | +```dart |
| 66 | +main() async { |
| 67 | + var phases = new PhaseGroup(); |
| 68 | + group.newPhase().addAction( |
| 69 | + new CopyBuilder('.copy'), new InputSet('my_package', ['lib/*.dart'])); |
| 70 | + group.newPhase().addAction( |
| 71 | + new CopyBuilder('.bak'), new InputSet('my_package', ['lib/*.dart.copy'])); |
| 72 | +
|
| 73 | + await build(phases); |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +This time, all the `*.dart.copy` files will be created first, and then the next |
| 78 | +`Phase` will read those in and create additional `*.dart.copy.bak` files. You |
| 79 | +can add as many phases as you want, but in general it's better to add more |
| 80 | +actions to a single phase since they can run at the same time. |
| 81 | + |
| 82 | +**Note**: Any time you change your build script (or any of its dependencies), |
| 83 | +the next build will be a full rebuild. This is because the system has no way |
| 84 | +of knowing how that change may have affected the outputs. |
| 85 | + |
| 86 | +### Inputs |
| 87 | + |
| 88 | +Valid inputs follow the general dart package rules. You can read any files under |
| 89 | +the top level `lib` folder any package dependency, and you can read all files |
| 90 | +from the current package. |
| 91 | + |
| 92 | +In general it is best to be as specific as possible with your `InputSet`s, |
| 93 | +because all matching files will be provided to `declareOutputs`. |
| 94 | + |
| 95 | +### Outputs |
| 96 | + |
| 97 | +You may only output files in the current package, but anywhere in the current |
| 98 | +package is allowed. |
| 99 | + |
| 100 | +You are not allowed to overwrite existing files, only create new ones. |
| 101 | + |
| 102 | +Outputs from previous builds will not be treated as inputs to later ones. |
| 103 | + |
| 104 | +### Source control |
| 105 | + |
| 106 | +This package creates a top level `.build` folder in your package, which should |
| 107 | +not be submitted to your source control repo (likely this just means adding |
| 108 | +'.build' to your '.gitignore' file). |
| 109 | + |
| 110 | +When it comes to generated files it is generally best to not submit them to |
| 111 | +source control, but a specific `Builder` may provide a recommendation otherwise. |
| 112 | + |
| 113 | +It should be noted that if you do submit generated files to your repo then when |
| 114 | +you change branches or merge in changes you may get a warning on your next build |
| 115 | +about declared outputs that already exist. This will be followed up with a |
| 116 | +prompt to delete those files. You can type `l` to list the files, and then type |
| 117 | +`y` to delete them if everything looks correct. If you think something is wrong |
| 118 | +you can type `n` to abandon the build without taking any action. |
| 119 | + |
| 120 | +### Publishing packages |
| 121 | + |
| 122 | +In general generated files should be published with your package, but this may |
| 123 | +not always be the case. Some `Builder`s may provide a recommendation for this as |
| 124 | +well. |
| 125 | + |
| 126 | +## Implementing your own Builders |
| 127 | + |
| 128 | +If you have written a pub `Transformer` in the past, then the `Builder`api |
| 129 | +should be familiar to you. The main difference is that `Builders` must always |
| 130 | +declare their outputs, similar to a `DeclaringTransformer`. |
| 131 | + |
| 132 | +The basic api looks like this: |
| 133 | + |
| 134 | +```dart |
| 135 | +abstract class Builder { |
| 136 | + /// You can only output files in `build` that you declare here. You are not |
| 137 | + /// required to output all of these files, but no other [Builder] is allowed |
| 138 | + /// to declare the same outputs. |
| 139 | + List<AssetId> declareOutputs(AssetId input); |
| 140 | +
|
| 141 | + /// Similar to `Transformer.apply`. This is where you build and output files. |
| 142 | + Future build(BuildStep buildStep); |
| 143 | +} |
| 144 | +``` |
| 145 | + |
| 146 | +Building on the example in [Running Builds](#running-builds), here is an |
| 147 | +implementation of a `Builder` which just copies files to other files with the |
| 148 | +same name, but an additional extension: |
| 149 | + |
| 150 | +```dart |
| 151 | +/// A really simple [Builder], it just makes copies! |
| 152 | +class CopyBuilder implements Builder { |
| 153 | + final String extension; |
| 154 | +
|
| 155 | + CopyBuilder(this.extension) |
| 156 | +
|
| 157 | + Future build(BuildStep buildStep) async { |
| 158 | + /// Each [buildStep] has just one input. This is a fully realized [Asset] |
| 159 | + /// with its [stringContents] already available. |
| 160 | + var input = buildStep.input; |
| 161 | +
|
| 162 | + /// Create a new [Asset], with the new [AssetId]. |
| 163 | + var copy = new Asset(_copiedId(input.id), input.stringContents); |
| 164 | +
|
| 165 | + /// Write out the [Asset]. |
| 166 | + /// |
| 167 | + /// There is no need to `await` here, the system handles waiting on these |
| 168 | + /// files as necessary before advancing to the next phase. |
| 169 | + buildStep.writeAsString(copy); |
| 170 | + } |
| 171 | +
|
| 172 | + /// Declare your outputs, just one file in this case. |
| 173 | + List<AssetId> declareOutputs(AssetId inputId) => [_copiedId(inputId)]; |
| 174 | +
|
| 175 | + AssetId _copiedId(AssetId inputId) => inputId.addExtension('$extension'); |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +It should be noted that you should _never_ touch the file system directly. Go |
| 180 | +through the `buildStep#readAsString` and `buildStep#writeAsString` methods in |
| 181 | +order to read and write assets. This is what enables the package to track all of |
| 182 | +your dependencies and do incremental rebuilds. It is also what enables your |
| 183 | +`Builder` to run on different environments. |
| 184 | + |
| 185 | +### Using the analyzer (**experimental**) |
| 186 | + |
| 187 | +If you need to do analyzer resolution, you can use the `BuildStep#resolve` |
| 188 | +method. This makes sure that all `Builder`s in the system share the same |
| 189 | +analysis context, which greatly speeds up the overall system when multiple |
| 190 | +`Builder`s are doing resolution. Additionally, it handles for you making the |
| 191 | +analyzer work in an async environment. |
| 192 | + |
| 193 | +This `Resolver` has the exact same api as the one from `code_transformers`, so |
| 194 | +migrating to it should be easy if you have used `code_transformers` in the past. |
| 195 | + |
| 196 | +Here is an example of a `Builder` which uses the `resolve` method: |
| 197 | + |
| 198 | +```dart |
| 199 | +class ResolvingCopyBuilder { |
| 200 | + Future build(BuildStep buildStep) { |
| 201 | + /// Resolves all libraries reachable from the primary input. |
| 202 | + var resolver = await buildStep.resolve(buildStep.input.id); |
| 203 | + /// Get a [LibraryElement] by asset id. |
| 204 | + var entryLib = resolver.getLibrary(buildStep.input.id); |
| 205 | + /// Or get a [LibraryElement] by name. |
| 206 | + var otherLib = resolver.getLibraryByName('my.library'); |
| 207 | +
|
| 208 | + /// **IMPORTANT**: If you don't release a resolver, your builds will hang. |
| 209 | + resolver.release(); |
| 210 | + } |
| 211 | +
|
| 212 | + /// Declare outputs as well.... |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +Once you have gotten a `LibraryElement` using one of the methods on `Resolver`, |
| 217 | +you are now just using the regular `analyzer` package to explore your app. |
| 218 | + |
| 219 | +**Important Note**: As shown in the code above, you _must_ call `release` on |
| 220 | +your resolver when you are done. If you don't then the next call to `resolve` |
| 221 | +will never complete. |
4 | 222 |
|
5 | 223 | ## Features and bugs |
6 | 224 |
|
|
0 commit comments