Skip to content

Commit 21c9a7f

Browse files
committed
Generally improve logging, and exit automatically on build update
1 parent 700ec23 commit 21c9a7f

File tree

7 files changed

+163
-105
lines changed

7 files changed

+163
-105
lines changed

e2e_example/tool/build.dart

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
4-
import 'dart:io';
5-
64
import 'package:build/build.dart';
75

86
import 'package:e2e_example/copy_builder.dart';
@@ -17,11 +15,5 @@ main() async {
1715
/// the transitive deps issue.
1816
CopyBuilder.addPhases(phases, graph);
1917

20-
var result = await build(phases);
21-
22-
if (result.status == BuildStatus.Success) {
23-
stdout.writeln(result);
24-
} else {
25-
stderr.writeln(result);
26-
}
18+
await build(phases);
2719
}

e2e_example/tool/watch.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
4-
import 'dart:io';
5-
64
import 'package:build/build.dart';
75

86
import 'package:e2e_example/copy_builder.dart';

lib/src/generate/build.dart

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,12 @@ Future<BuildResult> build(PhaseGroup phaseGroup,
5656
var futureResult = buildImpl.runBuild();
5757

5858
// Stop doing new builds when told to terminate.
59-
var listener = _setupTerminateLogic(terminateEventStream, () {
59+
_setupTerminateLogic(terminateEventStream, () {
6060
new Logger('Build').info('Waiting for build to finish...');
6161
return futureResult;
62-
});
62+
}, cancelWhen: futureResult);
6363

6464
var result = await futureResult;
65-
listener.cancel();
6665
options.logListener.cancel();
6766
return result;
6867
}
@@ -106,6 +105,9 @@ Stream<BuildResult> watch(PhaseGroup phaseGroup,
106105
// Stop doing new builds when told to terminate.
107106
_setupTerminateLogic(terminateEventStream, () async {
108107
await watchImpl.terminate();
108+
}, cancelWhen: watchImpl.onTerminated);
109+
110+
watchImpl.onTerminated.then((_) {
109111
options.logListener.cancel();
110112
});
111113

@@ -151,6 +153,9 @@ Stream<BuildResult> serve(PhaseGroup phaseGroup,
151153
// Stop doing new builds when told to terminate.
152154
_setupTerminateLogic(terminateEventStream, () async {
153155
await watchImpl.terminate();
156+
}, cancelWhen: watchImpl.onTerminated);
157+
158+
watchImpl.onTerminated.then((_) async{
154159
await serverStarted;
155160
await stopServer();
156161
options.logListener.cancel();
@@ -162,7 +167,7 @@ Stream<BuildResult> serve(PhaseGroup phaseGroup,
162167
/// Given [terminateEventStream], call [onTerminate] the first time an event is
163168
/// seen. If a second event is recieved, simply exit.
164169
StreamSubscription _setupTerminateLogic(
165-
Stream terminateEventStream, Future onTerminate()) {
170+
Stream terminateEventStream, Future onTerminate(), {Future cancelWhen}) {
166171
terminateEventStream ??= ProcessSignal.SIGINT.watch();
167172
int numEventsSeen = 0;
168173
var terminateListener;
@@ -176,5 +181,9 @@ StreamSubscription _setupTerminateLogic(
176181
exit(2);
177182
}
178183
});
184+
185+
cancelWhen?.then((_) {
186+
terminateListener.cancel();
187+
});
179188
return terminateListener;
180189
}

lib/src/generate/build_impl.dart

Lines changed: 57 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import '../asset_graph/graph.dart';
2222
import '../asset_graph/node.dart';
2323
import '../builder/build_step_impl.dart';
2424
import '../builder/builder.dart';
25+
import '../logging/logging.dart';
2526
import '../package_graph/package_graph.dart';
2627
import 'build_result.dart';
2728
import 'exceptions.dart';
@@ -76,13 +77,14 @@ class BuildImpl {
7677

7778
/// Initialize the [assetGraph] if its not yet set up.
7879
if (_assetGraph == null) {
79-
_logger.info('Reading cached dependency graph');
80-
_assetGraph = await _readAssetGraph();
81-
82-
/// Collect updates since the asset graph was last created. This only
83-
/// handles updates and deletes, not adds. We list the file system for
84-
/// all inputs later on (in [_initializeInputsByPackage]).
85-
updates.addAll(await _getUpdates());
80+
await logWithTime(_logger, 'Reading cached dependency graph', () async {
81+
_assetGraph = await _readAssetGraph();
82+
83+
/// Collect updates since the asset graph was last created. This only
84+
/// handles updates and deletes, not adds. We list the file system for
85+
/// all inputs later on (in [_initializeInputsByPackage]).
86+
updates.addAll(await _getUpdates());
87+
});
8688
}
8789

8890
/// If the build script gets updated, we need to either fully invalidate
@@ -95,48 +97,53 @@ class BuildImpl {
9597
///
9698
/// TODO(jakemac): Come up with a better way of telling if the script
9799
/// has been updated since it started running.
98-
_logger.info('Checking if the build script has been updated');
99-
if (await _buildScriptUpdated()) {
100-
buildType = BuildType.Full;
101-
if (_isFirstBuild) {
102-
_logger.info('Invalidating asset graph due to build script update');
103-
_assetGraph.allNodes
104-
.where((node) => node is GeneratedAssetNode)
105-
.forEach(
106-
(node) => (node as GeneratedAssetNode).needsUpdate = true);
107-
} else {
108-
done.complete(new BuildResult(BuildStatus.Failure, buildType, [],
109-
exception: 'Build abandoned due to change to the build script or '
110-
'one of its dependencies. This could be caused by a pub get '
111-
'or any other change. Please terminate the build script and '
112-
'restart it.'));
113-
return;
100+
await logWithTime(_logger, 'Checking build script for updates', () async {
101+
if (await _buildScriptUpdated()) {
102+
buildType = BuildType.Full;
103+
if (_isFirstBuild) {
104+
_logger
105+
.warning('Invalidating asset graph due to build script update');
106+
_assetGraph.allNodes
107+
.where((node) => node is GeneratedAssetNode)
108+
.forEach(
109+
(node) => (node as GeneratedAssetNode).needsUpdate = true);
110+
} else {
111+
done.complete(new BuildResult(BuildStatus.Failure, buildType, [],
112+
exception: new BuildScriptUpdatedException()));
113+
}
114114
}
115-
}
116-
117-
/// Applies all [updates] to the [_assetGraph] as well as doing other
118-
/// necessary cleanup.
119-
_logger.info('Updating dependency graph with changes since last build.');
120-
await _updateWithChanges(updates);
121-
122-
/// Wait while all inputs are collected.
123-
_logger.info('Initializing inputs');
124-
await _initializeInputsByPackage();
125-
126-
/// Delete all previous outputs!
127-
_logger.info('Deleting previous outputs');
128-
await _deletePreviousOutputs();
115+
});
116+
// Bail if the previous step completed the build.
117+
if (done.isCompleted) return;
118+
119+
await logWithTime(_logger, 'Finalizing build setup', () async {
120+
/// Applies all [updates] to the [_assetGraph] as well as doing other
121+
/// necessary cleanup.
122+
_logger
123+
.info('Updating dependency graph with changes since last build.');
124+
await _updateWithChanges(updates);
125+
126+
/// Wait while all inputs are collected.
127+
_logger.info('Initializing inputs');
128+
await _initializeInputsByPackage();
129+
130+
/// Delete all previous outputs!
131+
_logger.info('Deleting previous outputs');
132+
await _deletePreviousOutputs();
133+
});
129134

130135
/// Run a fresh build.
131-
_logger.info('Running build phases');
132-
var result = await _runPhases();
136+
var result =
137+
await logWithTime(_logger, 'Running build', _runPhases);
133138

134139
/// Write out the dependency graph file.
135-
_logger.info('Caching finalized dependency graph');
136-
_assetGraph.validAsOf = validAsOf;
137-
var assetGraphAsset =
138-
new Asset(_assetGraphId, JSON.encode(_assetGraph.serialize()));
139-
await _writer.writeAsString(assetGraphAsset);
140+
await logWithTime(_logger, 'Caching finalized dependency graph',
141+
() async {
142+
_assetGraph.validAsOf = validAsOf;
143+
var assetGraphAsset =
144+
new Asset(_assetGraphId, JSON.encode(_assetGraph.serialize()));
145+
await _writer.writeAsString(assetGraphAsset);
146+
});
140147

141148
done.complete(result);
142149
}, onError: (e, Chain chain) {
@@ -148,12 +155,14 @@ class BuildImpl {
148155
_isFirstBuild = false;
149156
if (result.status == BuildStatus.Success) {
150157
_logger.info('Succeeded after ${watch.elapsedMilliseconds}ms with '
151-
'${result.outputs.length} outputs\n');
158+
'${result.outputs.length} outputs\n\n');
152159
} else {
160+
var exceptionString =
161+
result.exception != null ? '\n${result.exception}' : '';
153162
var stackTraceString =
154-
result.stackTrace != null ? '\n\n${result.stackTrace}' : '';
155-
_logger.severe('Failed after ${watch.elapsedMilliseconds}ms:'
156-
'${result.exception}$stackTraceString\n');
163+
result.stackTrace != null ? '\n${result.stackTrace}' : '';
164+
_logger.severe('Failed after ${watch.elapsedMilliseconds}ms'
165+
'$exceptionString$stackTraceString\n');
157166
}
158167
return result;
159168
}

lib/src/generate/exceptions.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@ class ConcurrentBuildException implements Exception {
88
String toString() =>
99
'ConcurrentBuildException: Only one build may be running at a time.';
1010
}
11+
12+
class BuildScriptUpdatedException implements Exception {
13+
const BuildScriptUpdatedException();
14+
15+
@override
16+
String toString() => 'Build abandoned due to change to the build script or '
17+
'one of its dependencies. This could be caused by a pub get or any other '
18+
'change. Please restart the build script.';
19+
}

lib/src/generate/watch_impl.dart

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import '../asset/id.dart';
1111
import '../asset/writer.dart';
1212
import '../asset_graph/graph.dart';
1313
import '../asset_graph/node.dart';
14+
import '../logging/logging.dart';
1415
import '../package_graph/package_graph.dart';
1516
import 'build_impl.dart';
1617
import 'build_result.dart';
1718
import 'directory_watcher_factory.dart';
19+
import 'exceptions.dart';
1820
import 'options.dart';
1921
import 'phase.dart';
2022

@@ -46,8 +48,8 @@ class WatchImpl {
4648
final AssetWriter _writer;
4749

4850
/// A future that completes when the current build is done.
49-
Future _currentBuild;
50-
Future get currentBuild => _currentBuild;
51+
Future<BuildResult> _currentBuild;
52+
Future<BuildResult> get currentBuild => _currentBuild;
5153

5254
/// Whether or not another build is scheduled.
5355
bool _nextBuildScheduled;
@@ -61,6 +63,9 @@ class WatchImpl {
6163
/// Whether we are in the process of terminating.
6264
bool _terminating = false;
6365

66+
Completer _onTerminatedCompleter = new Completer();
67+
Future get onTerminated => _onTerminatedCompleter.future;
68+
6469
WatchImpl(BuildOptions options, PhaseGroup phaseGroup)
6570
: _directoryWatcherFactory = options.directoryWatcherFactory,
6671
_debounceDelay = options.debounceDelay,
@@ -71,7 +76,10 @@ class WatchImpl {
7176
/// Completes after the current build is done, and stops further builds from
7277
/// happening.
7378
Future terminate() async {
74-
if (_terminating) throw new StateError('Already terminating.');
79+
if (_terminating) {
80+
_logger.warning('Already terminating.');
81+
return;
82+
}
7583
if (_resultStreamController == null) {
7684
throw new StateError('`terminate` called before `runWatch`');
7785
}
@@ -93,6 +101,7 @@ class WatchImpl {
93101
}
94102
_terminating = false;
95103
_logger.info('Build watching terminated, safe to exit.\n');
104+
_onTerminatedCompleter.complete();
96105
}
97106

98107
/// Runs a build any time relevant files change.
@@ -140,11 +149,17 @@ class WatchImpl {
140149
return;
141150
}
142151

143-
_logger.info('Preparing for next build');
144-
_logger.info('Starting build');
152+
_logger.info('Starting next build');
145153
_currentBuild =
146154
_buildImpl.runBuild(validAsOf: validAsOf, updates: updatedInputsCopy);
147155
_currentBuild.then((result) {
156+
// Terminate the watcher if the build script is updated, there is no
157+
// need to continue listening.
158+
if (result.status == BuildStatus.Failure &&
159+
result.exception is BuildScriptUpdatedException) {
160+
terminate();
161+
}
162+
148163
if (_resultStreamController.hasListener) {
149164
_resultStreamController.add(result);
150165
}
@@ -162,46 +177,48 @@ class WatchImpl {
162177
buildTimer = new Timer(_debounceDelay, doBuild);
163178
}
164179

165-
final watchers = <DirectoryWatcher>[];
166-
_logger.info('Setting up file watchers');
180+
logWithTime(_logger, 'Setting up file watchers', () {
181+
final watchers = <DirectoryWatcher>[];
167182

168-
// Collect absolute file paths for all the packages. This needs to happen
169-
// before setting up the watchers.
170-
final absolutePackagePaths = <PackageNode, String>{};
171-
for (var package in _packageGraph.allPackages.values) {
172-
absolutePackagePaths[package] =
173-
path.normalize(path.absolute(package.location.toFilePath()));
174-
}
183+
// Collect absolute file paths for all the packages. This needs to happen
184+
// before setting up the watchers.
185+
final absolutePackagePaths = <PackageNode, String>{};
186+
for (var package in _packageGraph.allPackages.values) {
187+
absolutePackagePaths[package] =
188+
path.normalize(path.absolute(package.location.toFilePath()));
189+
}
175190

176-
// Set up watchers for all the packages
177-
for (var package in _packageGraph.allPackages.values) {
178-
var absolutePackagePath = absolutePackagePaths[package];
179-
_logger.fine('Setting up watcher at $absolutePackagePath');
180-
181-
// Ignore all subfolders which are other packages.
182-
var pathsToIgnore = absolutePackagePaths.values.where((path) =>
183-
path != absolutePackagePath && path.startsWith(absolutePackagePath));
184-
185-
var watcher = _directoryWatcherFactory(absolutePackagePath);
186-
watchers.add(watcher);
187-
_allListeners.add(watcher.events.listen((WatchEvent e) {
188-
// Check for ignored paths and immediately bail.
189-
if (pathsToIgnore.any((path) => e.path.startsWith(path))) return;
190-
191-
var relativePath = path.relative(e.path, from: absolutePackagePath);
192-
_logger.finest(
193-
'Got ${e.type} event for path $relativePath from ${watcher.path}');
194-
var id = new AssetId(package.name, relativePath);
195-
var node = _assetGraph.get(id);
196-
// Short circuit for deletes of nodes that aren't in the graph.
197-
if (e.type == ChangeType.REMOVE && node == null) return;
198-
199-
updatedInputs[id] = e.type;
200-
scheduleBuild();
201-
}));
202-
}
191+
// Set up watchers for all the packages
192+
for (var package in _packageGraph.allPackages.values) {
193+
var absolutePackagePath = absolutePackagePaths[package];
194+
_logger.fine('Setting up watcher at $absolutePackagePath');
195+
196+
// Ignore all subfolders which are other packages.
197+
var pathsToIgnore = absolutePackagePaths.values.where((path) =>
198+
path != absolutePackagePath &&
199+
path.startsWith(absolutePackagePath));
200+
201+
var watcher = _directoryWatcherFactory(absolutePackagePath);
202+
watchers.add(watcher);
203+
_allListeners.add(watcher.events.listen((WatchEvent e) {
204+
// Check for ignored paths and immediately bail.
205+
if (pathsToIgnore.any((path) => e.path.startsWith(path))) return;
206+
207+
var relativePath = path.relative(e.path, from: absolutePackagePath);
208+
_logger.finest(
209+
'Got ${e.type} event for path $relativePath from ${watcher.path}');
210+
var id = new AssetId(package.name, relativePath);
211+
var node = _assetGraph.get(id);
212+
// Short circuit for deletes of nodes that aren't in the graph.
213+
if (e.type == ChangeType.REMOVE && node == null) return;
214+
215+
updatedInputs[id] = e.type;
216+
scheduleBuild();
217+
}));
218+
}
203219

204-
Future.wait(watchers.map((w) => w.ready)).then((_) {
220+
return Future.wait(watchers.map((w) => w.ready));
221+
}).then((_) {
205222
// Schedule the first build!
206223
doBuild(true);
207224
});

0 commit comments

Comments
 (0)