Skip to content

Commit 7ca1fab

Browse files
committed
trigger a full rebuild when the build script changes
1 parent c0b6283 commit 7ca1fab

File tree

4 files changed

+105
-5
lines changed

4 files changed

+105
-5
lines changed

lib/src/generate/build_impl.dart

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import 'dart:async';
55
import 'dart:convert';
66
import 'dart:io';
7+
import 'dart:mirrors';
78

89
import 'package:logging/logging.dart';
910
import 'package:path/path.dart' as path;
@@ -73,8 +74,18 @@ class BuildImpl {
7374

7475
/// Applies all [updates] to the [_assetGraph] as well as doing other
7576
/// necessary cleanup.
77+
_logger.info('Updating dependency graph with changes since last build.');
7678
await _updateWithChanges(updates);
77-
_assetGraph.validAsOf = validAsOf;
79+
80+
/// Sometimes we need to fully invalidate the graph, such as when the
81+
/// build script itself is updated.
82+
_logger.info('Checking if asset graph needs to be rebuilt');
83+
if (await _shouldInvalidateAssetGraph()) {
84+
_logger.info('Invalidating asset graph due to build script update');
85+
_assetGraph.allNodes
86+
.where((node) => node is GeneratedAssetNode)
87+
.forEach((node) => (node as GeneratedAssetNode).needsUpdate = true);
88+
}
7889

7990
/// Wait while all inputs are collected.
8091
_logger.info('Initializing inputs');
@@ -89,6 +100,8 @@ class BuildImpl {
89100
var result = await _runPhases();
90101

91102
/// Write out the dependency graph file.
103+
_logger.info('Caching finalized dependency graph');
104+
_assetGraph.validAsOf = validAsOf;
92105
var assetGraphAsset =
93106
new Asset(_assetGraphId, JSON.encode(_assetGraph.serialize()));
94107
await _writer.writeAsString(assetGraphAsset);
@@ -120,6 +133,55 @@ class BuildImpl {
120133
}
121134
}
122135

136+
/// Checks if the [_assetGraph] needs to be completely invalidated.
137+
///
138+
/// For now this just means looking at the current running program, and seeing
139+
/// if any of its sources are newer than the asset graph itself.
140+
Future<bool> _shouldInvalidateAssetGraph() async {
141+
var completer = new Completer<bool>();
142+
Future.wait(currentMirrorSystem().libraries.keys.map((Uri uri) async {
143+
/// Short-circuit
144+
if (completer.isCompleted) return;
145+
var lastModified;
146+
switch (uri.scheme) {
147+
case 'dart':
148+
return;
149+
case 'package':
150+
var parts = uri.pathSegments;
151+
var id = new AssetId(
152+
parts[0],
153+
path.url
154+
.joinAll(['lib']..addAll(parts.getRange(1, parts.length))));
155+
lastModified = await _reader.lastModified(id);
156+
break;
157+
case 'file':
158+
159+
/// TODO(jakemac): Probably shouldn't use dart:io directly, but its
160+
/// definitely the easiest solution and should be fine.
161+
var file = new File.fromUri(uri);
162+
lastModified = await file.lastModified();
163+
break;
164+
case 'data':
165+
/// Test runner uses a `data` scheme, don't invalidate for those.
166+
if (uri.path.contains('package:test')) return;
167+
continue unknownUri;
168+
unknownUri:
169+
default:
170+
_logger.info('Unrecognized uri scheme `${uri.scheme}` found for '
171+
'library in build script, falling back on full rebuild.');
172+
if (!completer.isCompleted) completer.complete(true);
173+
return;
174+
}
175+
assert(lastModified != null);
176+
if (lastModified.compareTo(_assetGraph.validAsOf) > 0) {
177+
if (!completer.isCompleted) completer.complete(true);
178+
}
179+
})).then((_) {
180+
if (!completer.isCompleted) completer.complete(false);
181+
});
182+
return completer.future;
183+
}
184+
123185
/// Creates and returns a map of updates to assets based on [_assetGraph].
124186
Future<Map<AssetId, ChangeType>> _getUpdates() async {
125187
/// Collect updates to the graph based on any changed assets.

lib/src/generate/watch_impl.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import 'package:logging/logging.dart';
77
import 'package:path/path.dart' as path;
88
import 'package:watcher/watcher.dart';
99

10-
import '../asset/cache.dart';
1110
import '../asset/id.dart';
1211
import '../asset/reader.dart';
1312
import '../asset/writer.dart';

test/common/in_memory_reader.dart

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44
import 'dart:async';
55
import 'dart:convert';
6+
import 'dart:mirrors';
67

78
import 'package:build/build.dart';
89

@@ -35,9 +36,25 @@ class InMemoryAssetReader implements AssetReader {
3536
}
3637
}
3738

39+
final Map<Uri, LibraryMirror> _allLibraries = currentMirrorSystem().libraries;
40+
3841
@override
3942
Future<DateTime> lastModified(AssetId id) async {
40-
if (!await hasInput(id)) throw new AssetNotFoundException(id);
43+
if (!assets.containsKey(id)) {
44+
/// Support reading files imported by the test script as well. Also
45+
/// pretend like they are very old so that builds don't get invalidated.
46+
///
47+
/// Overridden values in [assets] will take precedence.
48+
var uri = new Uri(
49+
scheme: 'package',
50+
path: '${id.package}/${id.path.replaceFirst("lib/", "")}');
51+
if (_allLibraries[uri] != null) {
52+
return new DateTime.fromMillisecondsSinceEpoch(0);
53+
}
54+
55+
throw new AssetNotFoundException(id);
56+
}
57+
4158
return assets[id].date;
4259
}
4360
}

test/generate/build_test.dart

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,7 @@ main() {
265265
],
266266
];
267267

268-
var graph = new AssetGraph()
269-
..validAsOf = new DateTime.now().subtract(new Duration(days: 1));
268+
var graph = new AssetGraph()..validAsOf = new DateTime.now();
270269

271270
var aCloneNode = new GeneratedAssetNode(makeAssetId('a|lib/a.txt.copy'),
272271
false, true, makeAssetId('a|lib/a.txt.copy.clone'));
@@ -345,6 +344,29 @@ main() {
345344
expect(writer.assets.containsKey(aCopyNode.id), isFalse);
346345
expect(writer.assets.containsKey(aCloneNode.id), isFalse);
347346
});
347+
348+
test('invalidates graph if build script updates', () async {
349+
var graph = new AssetGraph();
350+
var aId = makeAssetId('a|web/a.txt');
351+
var aCopyNode = new GeneratedAssetNode(
352+
aId, false, true, makeAssetId('a|web/a.txt.copy'));
353+
graph.add(aCopyNode);
354+
var aNode = makeAssetNode('a|web/a.txt', [aCopyNode.id]);
355+
graph.add(aNode);
356+
357+
var writer = new InMemoryAssetWriter();
358+
/// Spoof the `package:test/test.dart` import and pretend its newer than
359+
/// the current graph to cause a rebuild.
360+
writer.writeAsString(makeAsset('test|lib/test.dart', ''),
361+
lastModified: graph.validAsOf.add(new Duration(hours: 1)));
362+
await testPhases(copyAPhaseGroup, {
363+
'a|web/a.txt': 'a',
364+
'a|web/a.txt.copy': 'a',
365+
'a|.build/asset_graph.json': JSON.encode(graph.serialize()),
366+
}, outputs: {
367+
'a|web/a.txt.copy': 'a',
368+
});
369+
});
348370
});
349371
}
350372

0 commit comments

Comments
 (0)