3
3
// BSD-style license that can be found in the LICENSE file.
4
4
5
5
import 'dart:async' ;
6
+ import 'dart:collection' ;
6
7
8
+ import 'package:analyzer/dart/analysis/utilities.dart' ;
9
+ import 'package:analyzer/dart/ast/ast.dart' ;
7
10
import 'package:analyzer/file_system/memory_file_system.dart' ;
8
11
// ignore: implementation_imports
9
12
import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart' ;
10
13
import 'package:build/build.dart' ;
14
+ import 'package:path/path.dart' as p;
15
+
16
+ import 'analysis_driver_model_uri_resolver.dart' ;
11
17
12
18
/// Manages analysis driver and related build state.
13
19
///
@@ -19,36 +25,148 @@ import 'package:build/build.dart';
19
25
/// - Maintains an in-memory filesystem that is the analyzer's view of the
20
26
/// build.
21
27
/// - Notifies the analyzer of changes to that in-memory filesystem.
22
- abstract class AnalysisDriverModel {
28
+ ///
29
+ /// TODO(davidmorgan): the implementation here is unfinished and not used
30
+ /// anywhere; finish it. See `build_asset_uri_resolver.dart` for the current
31
+ /// implementation.
32
+ class AnalysisDriverModel {
23
33
/// In-memory filesystem for the analyzer.
24
- abstract final MemoryResourceProvider resourceProvider;
34
+ final MemoryResourceProvider resourceProvider =
35
+ MemoryResourceProvider (context: p.posix);
25
36
26
37
/// Notifies that [step] has completed.
27
38
///
28
39
/// All build steps must complete before [reset] is called.
29
- void notifyComplete (BuildStep step);
40
+ void notifyComplete (BuildStep step) {
41
+ // TODO(davidmorgan): add test coverage, fix implementation.
42
+ }
30
43
31
44
/// Clear cached information specific to an individual build.
32
- void reset ();
45
+ void reset () {
46
+ // TODO(davidmorgan): add test coverage, fix implementation.
47
+ }
33
48
34
49
/// Attempts to parse [uri] into an [AssetId] and returns it if it is cached.
35
50
///
36
51
/// Handles 'package:' or 'asset:' URIs, as well as 'file:' URIs of the form
37
52
/// `/$packageName/$assetPath` .
38
53
///
39
- /// Returns null if the Uri cannot be parsed or is not cached.
40
- AssetId ? lookupCachedAsset (Uri uri);
54
+ /// Returns null if the `Uri` cannot be parsed or is not cached.
55
+ AssetId ? lookupCachedAsset (Uri uri) {
56
+ final assetId = AnalysisDriverModelUriResolver .parseAsset (uri);
57
+ // TODO(davidmorgan): not clear if this is the right "exists" check.
58
+ if (assetId == null || ! resourceProvider.getFile (assetId.asPath).exists) {
59
+ return null ;
60
+ }
61
+
62
+ return assetId;
63
+ }
41
64
42
65
/// Updates [resourceProvider] and the analysis driver given by
43
66
/// `withDriverResource` with updated versions of [entryPoints] .
44
67
///
45
68
/// If [transitive] , then all the transitive imports from [entryPoints] are
46
69
/// also updated.
70
+ ///
71
+ /// Notifies [buildStep] of all inputs that result from analysis. If
72
+ /// [transitive] , this includes all transitive dependencies.
73
+ ///
74
+ /// If while finding transitive deps a `.transitive_deps` file is
75
+ /// encountered next to a source file then this cuts off the reporting
76
+ /// of deps to the [buildStep] , but does not affect the reporting of
77
+ /// files to the analysis driver.
47
78
Future <void > performResolve (
48
79
BuildStep buildStep,
49
80
List <AssetId > entryPoints,
50
81
Future <void > Function (
51
82
FutureOr <void > Function (AnalysisDriverForPackageBuild ))
52
83
withDriverResource,
53
- {required bool transitive});
84
+ {required bool transitive}) async {
85
+ /// TODO(davidmorgan): add test coverage for whether transitive
86
+ /// sources are read when [transitive] is false, fix the implementation
87
+ /// here.
88
+ /// TODO(davidmorgan): add test coverage for whether
89
+ /// `.transitive_deps` files cut off the reporting of deps to the
90
+ /// [buildStep] , fix the implementation here.
91
+
92
+ // Find transitive deps, this also informs [buildStep] of all inputs).
93
+ final ids = await _expandToTransitive (buildStep, entryPoints);
94
+
95
+ // Apply changes to in-memory filesystem.
96
+ for (final id in ids) {
97
+ if (await buildStep.canRead (id)) {
98
+ final content = await buildStep.readAsString (id);
99
+
100
+ /// TODO(davidmorgan): add test coverage for when a file is
101
+ /// modified rather than added, fix the implementation here.
102
+ resourceProvider.newFile (id.asPath, content);
103
+ } else {
104
+ if (resourceProvider.getFile (id.asPath).exists) {
105
+ resourceProvider.deleteFile (id.asPath);
106
+ }
107
+ }
108
+ }
109
+
110
+ // Notify the analyzer of changes.
111
+ await withDriverResource ((driver) async {
112
+ for (final id in ids) {
113
+ // TODO(davidmorgan): add test coverage for over-notification of
114
+ // changes, fix the implementaion here.
115
+ driver.changeFile (id.asPath);
116
+ }
117
+ await driver.applyPendingFileChanges ();
118
+ });
119
+ }
120
+
121
+ /// Walks the import graph from [ids] , returns full transitive deps.s
122
+ Future <Set <AssetId >> _expandToTransitive (
123
+ AssetReader reader, Iterable <AssetId > ids) async {
124
+ final result = < AssetId > {};
125
+ await __expandToTransitive (reader, ids, result);
126
+ return result;
127
+ }
128
+
129
+ /// Walks the import graph from [ids] , ignoring nodes already in [result] .
130
+ ///
131
+ /// Call with [result] empty to add full transitive deps to [result] .
132
+ Future <void > __expandToTransitive (
133
+ AssetReader reader, Iterable <AssetId > ids, Set <AssetId > result) async {
134
+ final nextIds = Queue .of (ids);
135
+ while (nextIds.isNotEmpty) {
136
+ final nextId = nextIds.removeFirst ();
137
+
138
+ // Skip if already seen.
139
+ if (! result.add (nextId)) continue ;
140
+
141
+ // Skip if not readable.
142
+ if (! await reader.canRead (nextId)) continue ;
143
+
144
+ final content = await reader.readAsString (nextId);
145
+ final deps = _parseDependencies (content, nextId);
146
+ nextIds.addAll (deps.where ((id) => ! result.contains (id)));
147
+ }
148
+ }
149
+ }
150
+
151
+ const _ignoredSchemes = ['dart' , 'dart-ext' ];
152
+
153
+ /// Parses Dart source in [content] , returns all depedencies: all assets
154
+ /// mentioned in directives, excluding `dart:` and `dart-ext` schemes.
155
+ List <AssetId > _parseDependencies (String content, AssetId from) =>
156
+ parseString (content: content, throwIfDiagnostics: false )
157
+ .unit
158
+ .directives
159
+ .whereType <UriBasedDirective >()
160
+ .map ((directive) => directive.uri.stringValue)
161
+ // Uri.stringValue can be null for strings that use interpolation.
162
+ .nonNulls
163
+ .where (
164
+ (uriContent) => ! _ignoredSchemes.any (Uri .parse (uriContent).isScheme),
165
+ )
166
+ .map ((content) => AssetId .resolve (Uri .parse (content), from: from))
167
+ .toList ();
168
+
169
+ extension _AssetIdExtensions on AssetId {
170
+ /// Asset path for the in-memory filesystem.
171
+ String get asPath => AnalysisDriverModelUriResolver .assetPath (this );
54
172
}
0 commit comments