@@ -8,13 +8,17 @@ import 'package:build/build.dart';
88import 'package:built_collection/built_collection.dart' ;
99import 'package:watcher/watcher.dart' ;
1010
11- import '../bootstrap/build_script_updates.dart' ;
1211import '../build_plan/build_directory.dart' ;
1312import '../build_plan/build_filter.dart' ;
1413import '../build_plan/build_plan.dart' ;
14+ import '../commands/watch/asset_change.dart' ;
15+ import '../constants.dart' ;
16+ import '../io/asset_tracker.dart' ;
17+ import '../io/build_output_reader.dart' ;
1518import '../io/filesystem_cache.dart' ;
1619import '../io/reader_writer.dart' ;
1720import 'asset_graph/graph.dart' ;
21+ import 'asset_graph/node.dart' ;
1822import 'build.dart' ;
1923import 'build_result.dart' ;
2024
@@ -31,46 +35,126 @@ import 'build_result.dart';
3135/// this serialized state is not actually used: the `AssetGraph` instance
3236/// already in memory is used directly.
3337class BuildSeries {
34- final BuildPlan buildPlan ;
38+ final BuildPlan _buildPlan ;
3539
36- final AssetGraph assetGraph;
37- final BuildScriptUpdates ? buildScriptUpdates;
40+ final AssetGraph _assetGraph;
3841
39- final ReaderWriter readerWriter ;
40- final ResourceManager resourceManager = ResourceManager ();
42+ final ReaderWriter _readerWriter ;
43+ final ResourceManager _resourceManager = ResourceManager ();
4144
4245 /// For the first build only, updates from the previous serialized build
4346 /// state.
4447 ///
4548 /// Null after the first build, or if there was no serialized build state, or
4649 /// if the serialized build state was discarded.
47- BuiltMap <AssetId , ChangeType >? updatesFromLoad ;
50+ BuiltMap <AssetId , ChangeType >? _updatesFromLoad ;
4851
4952 final StreamController <BuildResult > _buildResultsController =
5053 StreamController .broadcast ();
5154
5255 /// Whether the next build is the first build.
5356 bool firstBuild = true ;
5457
55- Future <void > beforeExit () => resourceManager.beforeExit ();
56-
57- BuildSeries ._(
58- this .buildPlan,
59- this .assetGraph,
60- this .buildScriptUpdates,
61- this .updatesFromLoad,
62- ) : readerWriter = buildPlan.readerWriter.copyWith (
63- generatedAssetHider: assetGraph,
64- cache:
65- buildPlan.buildOptions.enableLowResourcesMode
66- ? const PassthroughFilesystemCache ()
67- : InMemoryFilesystemCache (),
68- );
58+ BuildSeries ._({
59+ required BuildPlan buildPlan,
60+ required AssetGraph assetGraph,
61+ required ReaderWriter readerWriter,
62+ required BuiltMap <AssetId , ChangeType >? updatesFromLoad,
63+ }) : _buildPlan = buildPlan,
64+ _assetGraph = assetGraph,
65+ _readerWriter = readerWriter,
66+ _updatesFromLoad = updatesFromLoad;
67+
68+ factory BuildSeries (BuildPlan buildPlan) {
69+ final assetGraph = buildPlan.takeAssetGraph ();
70+ final readerWriter = buildPlan.readerWriter.copyWith (
71+ generatedAssetHider: assetGraph,
72+ cache:
73+ buildPlan.buildOptions.enableLowResourcesMode
74+ ? const PassthroughFilesystemCache ()
75+ : InMemoryFilesystemCache (),
76+ );
77+ return BuildSeries ._(
78+ buildPlan: buildPlan,
79+ assetGraph: assetGraph,
80+ readerWriter: readerWriter,
81+ updatesFromLoad: buildPlan.updates,
82+ );
83+ }
6984
7085 /// Broadcast stream of build results.
7186 Stream <BuildResult > get buildResults => _buildResultsController.stream;
7287 Future <BuildResult >? _currentBuildResult;
7388
89+ bool _hasBuildScriptChanged (Set <AssetId > changes) {
90+ if (_buildPlan.buildOptions.skipBuildScriptCheck) return false ;
91+ if (_buildPlan.buildScriptUpdates == null ) return true ;
92+ return _buildPlan.buildScriptUpdates! .hasBeenUpdated (changes);
93+ }
94+
95+ /// Returns whether [change] might trigger a build.
96+ ///
97+ /// Pass expected deletes in [expectedDeletes] . Expected deletes do not
98+ /// trigger a build. A delete that matches is removed from the set.
99+ Future <bool > shouldProcess (
100+ AssetChange change,
101+ Set <AssetId > expectedDeletes,
102+ ) async {
103+ // Ignore any expected delete once.
104+ if (change.type == ChangeType .REMOVE && expectedDeletes.remove (change.id)) {
105+ return false ;
106+ }
107+
108+ final node =
109+ _assetGraph.contains (change.id) ? _assetGraph.get (change.id) : null ;
110+
111+ // Changes to files that are not currently part of the build.
112+ if (node == null ) {
113+ // Ignore under `.dart_tool/build`.
114+ if (change.id.path.startsWith (cacheDir)) return false ;
115+
116+ // Ignore modifications and deletes.
117+ if (change.type != ChangeType .ADD ) return false ;
118+
119+ // It's an add: return whether it's a new input.
120+ return _buildPlan.targetGraph.anyMatchesAsset (change.id);
121+ }
122+
123+ // Changes to files that are part of the build.
124+
125+ // If not copying to a merged output directory, ignore changes to files with
126+ // no outputs.
127+ if (! _buildPlan.buildOptions.anyMergedOutputDirectory &&
128+ ! node.changesRequireRebuild) {
129+ return false ;
130+ }
131+
132+ // Ignore creation or modification of outputs.
133+ if (node.type == NodeType .generated && change.type != ChangeType .REMOVE ) {
134+ return false ;
135+ }
136+
137+ // For modifications, confirm that the content actually changed.
138+ if (change.type == ChangeType .MODIFY ) {
139+ _readerWriter.cache.invalidate ([change.id]);
140+ final newDigest = await _readerWriter.digest (change.id);
141+ return node.digest != newDigest;
142+ }
143+
144+ // It's an add of "missing source" node or a deletion of an input.
145+ return true ;
146+ }
147+
148+ Future <List <WatchEvent >> checkForChanges () async {
149+ final updates = await AssetTracker (
150+ _buildPlan.readerWriter,
151+ _buildPlan.targetGraph,
152+ ).collectChanges (_assetGraph);
153+ return List .of (
154+ updates.entries.map ((entry) => WatchEvent (entry.value, '${entry .key }' )),
155+ );
156+ }
157+
74158 /// If a build is running, the build result when it's done.
75159 ///
76160 /// If no build has ever run, returns the first build result when it's
@@ -93,27 +177,39 @@ class BuildSeries {
93177 BuiltSet <BuildDirectory >? buildDirs,
94178 BuiltSet <BuildFilter >? buildFilters,
95179 }) async {
96- buildDirs ?? = buildPlan.buildOptions.buildDirs;
97- buildFilters ?? = buildPlan.buildOptions.buildFilters;
180+ if (_hasBuildScriptChanged (updates.keys.toSet ())) {
181+ return BuildResult (
182+ status: BuildStatus .failure,
183+ failureType: FailureType .buildScriptChanged,
184+ buildOutputReader: BuildOutputReader (
185+ buildPlan: _buildPlan,
186+ readerWriter: _readerWriter,
187+ assetGraph: _assetGraph,
188+ ),
189+ );
190+ }
191+
192+ buildDirs ?? = _buildPlan.buildOptions.buildDirs;
193+ buildFilters ?? = _buildPlan.buildOptions.buildFilters;
98194 if (firstBuild) {
99- if (updatesFromLoad != null ) {
100- updates = updatesFromLoad ! .toMap ()..addAll (updates);
101- updatesFromLoad = null ;
195+ if (_updatesFromLoad != null ) {
196+ updates = _updatesFromLoad ! .toMap ()..addAll (updates);
197+ _updatesFromLoad = null ;
102198 }
103199 } else {
104- if (updatesFromLoad != null ) {
200+ if (_updatesFromLoad != null ) {
105201 throw StateError ('Only first build can have updates from load.' );
106202 }
107203 }
108204
109205 final build = Build (
110- buildPlan: buildPlan .copyWith (
206+ buildPlan: _buildPlan .copyWith (
111207 buildDirs: buildDirs,
112208 buildFilters: buildFilters,
113209 ),
114- assetGraph: assetGraph ,
115- readerWriter: readerWriter ,
116- resourceManager: resourceManager ,
210+ assetGraph: _assetGraph ,
211+ readerWriter: _readerWriter ,
212+ resourceManager: _resourceManager ,
117213 );
118214 if (firstBuild) firstBuild = false ;
119215
@@ -123,14 +219,5 @@ class BuildSeries {
123219 return result;
124220 }
125221
126- static Future <BuildSeries > create ({required BuildPlan buildPlan}) async {
127- final assetGraph = buildPlan.takeAssetGraph ();
128- final build = BuildSeries ._(
129- buildPlan,
130- assetGraph,
131- buildPlan.buildScriptUpdates,
132- buildPlan.updates,
133- );
134- return build;
135- }
222+ Future <void > beforeExit () => _resourceManager.beforeExit ();
136223}
0 commit comments