Skip to content

Commit ffbba7f

Browse files
authored
Delete previous source outputs (#856)
1 parent 7886db3 commit ffbba7f

File tree

7 files changed

+172
-16
lines changed

7 files changed

+172
-16
lines changed

build_runner/lib/src/generate/build_definition.dart

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ class _Loader {
7575
_checkBuildActions();
7676

7777
_logger.info('Initializing inputs');
78+
79+
var assetGraph = await _tryReadCachedAssetGraph();
7880
var inputSources = await _findInputSources();
7981
var cacheDirSources = await _findCacheDirSources();
8082
var internalSources = await _findInternalSources();
8183

82-
var assetGraph = await _tryReadCachedAssetGraph();
83-
8484
BuildScriptUpdates buildScriptUpdates;
8585
if (assetGraph != null) {
8686
var updates = await logTimedAsync(
@@ -94,7 +94,8 @@ class _Loader {
9494
if (!_options.skipBuildScriptCheck &&
9595
buildScriptUpdates.hasBeenUpdated(updates.keys.toSet())) {
9696
_logger.warning('Invalidating asset graph due to build script update');
97-
await _deleteGeneratedDir();
97+
var deletedSourceOutputs = await _cleanupOldOutputs(assetGraph);
98+
inputSources.removeAll(deletedSourceOutputs);
9899
assetGraph = null;
99100
buildScriptUpdates = null;
100101
}
@@ -191,10 +192,10 @@ class _Loader {
191192
cachedGraph.buildActionsDigest) {
192193
_logger.warning(
193194
'Throwing away cached asset graph because the build actions have '
194-
'changed. This could happen as a result of adding a new '
195-
'dependency, or if you are using a build script which changes '
196-
'the build structure based on command line flags or other '
197-
'configuration.');
195+
'changed. This most commonly would happen as a result of adding a '
196+
'new dependency or updating your dependencies.');
197+
198+
await _cleanupOldOutputs(cachedGraph);
198199
return null;
199200
}
200201
return cachedGraph;
@@ -209,6 +210,26 @@ class _Loader {
209210
});
210211
}
211212

213+
/// Deletes all the old outputs from [graph] that were written to the source
214+
/// tree, and deletes the entire generated directory.
215+
Future<Iterable<AssetId>> _cleanupOldOutputs(AssetGraph graph) async {
216+
var deletedSources = <AssetId>[];
217+
await logTimedAsync(_logger, 'Cleaning up outputs from previous builds.',
218+
() async {
219+
// Delete all the non-hidden outputs.
220+
await Future.wait(graph.outputs.map((id) {
221+
var node = graph.get(id) as GeneratedAssetNode;
222+
if (node.wasOutput && !node.isHidden) {
223+
deletedSources.add(id);
224+
return _environment.writer.delete(id);
225+
}
226+
}).where((v) => v is Future));
227+
228+
await _deleteGeneratedDir();
229+
});
230+
return deletedSources;
231+
}
232+
212233
/// Updates [assetGraph] based on a the new view of the world.
213234
///
214235
/// Once done, this returns a map of [AssetId] to [ChangeType] for all the

build_runner/lib/src/generate/watch_impl.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import '../asset_graph/node.dart';
2121
import '../environment/build_environment.dart';
2222
import '../environment/io_environment.dart';
2323
import '../environment/overridable_environment.dart';
24+
import '../logging/logging.dart';
2425
import '../package_graph/apply_builders.dart';
2526
import '../package_graph/build_config_overrides.dart';
2627
import '../package_graph/package_graph.dart';
@@ -191,10 +192,11 @@ class WatchImpl implements BuildState {
191192
final rootPackagesId = new AssetId(packageGraph.root.name, '.packages');
192193

193194
// Start watching files immediately, before the first build is even started.
194-
new PackageGraphWatcher(packageGraph,
195-
logger: _logger,
196-
watch: (node) =>
197-
new PackageNodeWatcher(node, watch: _directoryWatcherFactory))
195+
var graphWatcher = new PackageGraphWatcher(packageGraph,
196+
logger: _logger,
197+
watch: (node) =>
198+
new PackageNodeWatcher(node, watch: _directoryWatcherFactory));
199+
graphWatcher
198200
.watch()
199201
.asyncMap<AssetChange>((change) {
200202
// Delay any events until the first build is completed.
@@ -243,9 +245,10 @@ class WatchImpl implements BuildState {
243245
// Schedule the actual first build for the future so we can return the
244246
// stream synchronously.
245247
() async {
248+
await logTimedAsync(_logger, 'Waiting for all file watchers to be ready',
249+
() => graphWatcher.ready);
246250
originalRootPackagesDigest =
247251
md5.convert(await environment.reader.readAsBytes(rootPackagesId));
248-
249252
_buildDefinition = await BuildDefinition.prepareWorkspace(
250253
environment, options, buildActions,
251254
onDelete: _expectedDeletes.add);

build_runner/lib/src/watcher/graph_watcher.dart

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ class PackageGraphWatcher {
2121
final _NodeWatcherStrategy _strategy;
2222
final PackageGraph _graph;
2323

24+
var _readyCompleter = new Completer<Null>();
25+
Future<Null> get ready => _readyCompleter.future;
26+
27+
StreamController<AssetChange> controller;
28+
2429
/// Creates a new watcher for a [PackageGraph].
2530
///
2631
/// May optionally specify a [watch] strategy, otherwise will attempt a
@@ -35,7 +40,7 @@ class PackageGraphWatcher {
3540

3641
/// Returns a stream of records for assets that changed in the package graph.
3742
Stream<AssetChange> watch() {
38-
StreamController<AssetChange> controller;
43+
if (controller != null) return controller.stream;
3944
List<StreamSubscription> subscriptions;
4045
controller = new StreamController<AssetChange>(
4146
sync: true,
@@ -50,17 +55,24 @@ class PackageGraphWatcher {
5055
for (final subscription in subscriptions) {
5156
subscription.cancel();
5257
}
58+
_readyCompleter = new Completer<Null>();
59+
var done = controller.close();
60+
controller = null;
61+
return done;
5362
},
5463
);
5564
return controller.stream;
5665
}
5766

5867
List<StreamSubscription> _watch(StreamSink<AssetChange> sink) {
5968
final subscriptions = <StreamSubscription>[];
69+
var allWatchers = <PackageNodeWatcher>[];
6070
_graph.allPackages.forEach((name, node) {
6171
final nestedPackages = _nestedPaths(node);
6272
_logger.fine('Setting up watcher at ${node.path}');
63-
subscriptions.add(_strategy(node).watch().listen((event) {
73+
var nodeWatcher = _strategy(node);
74+
allWatchers.add(nodeWatcher);
75+
subscriptions.add(nodeWatcher.watch().listen((event) {
6476
// TODO: Consider a faster filtering strategy.
6577
if (nestedPackages.any((path) => event.id.path.startsWith(path))) {
6678
return;
@@ -70,6 +82,13 @@ class PackageGraphWatcher {
7082
sink.add(event);
7183
}));
7284
});
85+
// Asynchronously complete the `_readyCompleter` once all the watchers
86+
// are done.
87+
() async {
88+
await Future
89+
.wait(allWatchers.map((nodeWatcher) => nodeWatcher.watcher.ready));
90+
_readyCompleter.complete();
91+
}();
7392
return subscriptions;
7493
}
7594

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:build_runner/build_runner.dart';
8+
import 'package:build/build.dart';
9+
10+
class RunnerAssetWriterSpy extends AssetWriterSpy implements RunnerAssetWriter {
11+
final RunnerAssetWriter _delegate;
12+
13+
final _assetsDeleted = new Set<AssetId>();
14+
Iterable<AssetId> get assetsDeleted => _assetsDeleted;
15+
16+
RunnerAssetWriterSpy(this._delegate) : super(_delegate);
17+
18+
@override
19+
Future delete(AssetId id) {
20+
_assetsDeleted.add(id);
21+
return _delegate.delete(id);
22+
}
23+
}

build_runner/test/generate/build_definition_test.dart

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import 'package:build_runner/src/util/constants.dart';
2626

2727
import '../common/common.dart';
2828
import '../common/package_graphs.dart';
29+
import '../common/runner_asset_writer_spy.dart';
2930

3031
main() {
3132
final aPackageGraph = buildPackageGraph({rootPackage('a'): []});
@@ -388,5 +389,43 @@ main() {
388389
expect(originalAssetGraph.buildActionsDigest,
389390
equals(newAssetGraph.buildActionsDigest));
390391
});
392+
393+
test('deletes old source outputs if the build actions change', () async {
394+
var buildActions = [
395+
new BuildAction(new TestBuilder(), 'a', hideOutput: false)
396+
];
397+
var aTxt = new AssetId('a', 'lib/a.txt');
398+
await createFile(aTxt.path, 'hello');
399+
400+
var writerSpy = new RunnerAssetWriterSpy(environment.writer);
401+
environment = new OverrideableEnvironment(environment, writer: writerSpy);
402+
options = new BuildOptions(environment,
403+
packageGraph: options.packageGraph,
404+
skipBuildScriptCheck: true,
405+
logLevel: Level.OFF);
406+
407+
var originalAssetGraph = await AssetGraph.build(
408+
buildActions,
409+
<AssetId>[aTxt].toSet(),
410+
new Set(),
411+
aPackageGraph,
412+
environment.reader);
413+
414+
var aTxtCopy = new AssetId('a', 'lib/a.txt.copy');
415+
// Pretend we already output this without actually running a build.
416+
(originalAssetGraph.get(aTxtCopy) as GeneratedAssetNode).wasOutput = true;
417+
await createFile(aTxtCopy.path, 'hello');
418+
419+
await createFile(
420+
assetGraphPath, JSON.encode(originalAssetGraph.serialize()));
421+
422+
buildActions.add(new BuildAction(new TestBuilder(), 'a',
423+
targetSources: const InputSet(include: const ['.copy']),
424+
hideOutput: true));
425+
426+
await BuildDefinition.prepareWorkspace(
427+
environment, options, buildActions);
428+
expect(writerSpy.assetsDeleted, contains(aTxtCopy));
429+
});
391430
});
392431
}

build_runner/test/watcher/graph_watcher_test.dart

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,38 @@ void main() {
6666
new AssetChange(new AssetId('b', 'lib/b.dart'), ChangeType.ADD),
6767
]));
6868
});
69+
70+
test('ready waits for all node watchers to be ready', () async {
71+
final graph = buildPackageGraph({
72+
rootPackage('a', path: '/g/a'): ['b'],
73+
package('b', path: '/g/b'): []
74+
});
75+
final nodes = {
76+
'a': new FakeNodeWatcher(graph['a']),
77+
'b': new FakeNodeWatcher(graph['b']),
78+
r'$sdk': new FakeNodeWatcher(null),
79+
};
80+
final watcher = new PackageGraphWatcher(graph, watch: (node) {
81+
return nodes[node.name];
82+
});
83+
// We have to listen in order for `ready` to complete.
84+
// ignore: unawaited_futures
85+
watcher.watch().drain();
86+
87+
var done = false;
88+
// ignore: unawaited_futures
89+
watcher.ready.then((_) => done = true);
90+
await new Future.value();
91+
92+
for (final node in nodes.values) {
93+
expect(done, isFalse);
94+
node.markReady();
95+
await new Future.value();
96+
}
97+
98+
await new Future.value();
99+
expect(done, isTrue);
100+
});
69101
});
70102
}
71103

@@ -76,7 +108,10 @@ class FakeNodeWatcher implements PackageNodeWatcher {
76108
FakeNodeWatcher(this._package);
77109

78110
@override
79-
Watcher get watcher => throw new UnimplementedError();
111+
Watcher get watcher => _watcher;
112+
final _watcher = new _FakeWatcher();
113+
114+
void markReady() => _watcher._readyCompleter.complete();
80115

81116
void emitAdd(String path) {
82117
_events.add(
@@ -90,3 +125,18 @@ class FakeNodeWatcher implements PackageNodeWatcher {
90125
@override
91126
Stream<AssetChange> watch([_]) => _events.stream;
92127
}
128+
129+
class _FakeWatcher implements Watcher {
130+
@override
131+
Stream<WatchEvent> get events => throw new UnimplementedError();
132+
133+
@override
134+
bool get isReady => _readyCompleter.isCompleted;
135+
136+
@override
137+
String get path => throw new UnimplementedError();
138+
139+
@override
140+
Future get ready => _readyCompleter.future;
141+
final _readyCompleter = new Completer();
142+
}

e2e_example/test/common/utils.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ Future<Null> stopServer({bool cleanUp}) async {
122122
_stdOutLines = null;
123123
_stdErrLines = null;
124124

125-
if (cleanUp && await _toolDir.exists())
125+
if (cleanUp && await _toolDir.exists()) {
126126
await _toolDir.delete(recursive: true);
127+
}
127128
}
128129

129130
/// Checks whether the current git client is "clean" (no pending changes) for

0 commit comments

Comments
 (0)