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,145 @@ 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.
122
+ Future <Set <AssetId >> _expandToTransitive (
123
+ AssetReader reader, Iterable <AssetId > ids) async {
124
+ final result = ids.toSet ();
125
+ final nextIds = Queue .of (ids);
126
+ while (nextIds.isNotEmpty) {
127
+ final nextId = nextIds.removeFirst ();
128
+
129
+ // Skip if not readable. Note that calling `canRead` still makes it a
130
+ // dependency of the `BuildStep`.
131
+ if (! await reader.canRead (nextId)) continue ;
132
+
133
+ final content = await reader.readAsString (nextId);
134
+ final deps = _parseDependencies (content, nextId);
135
+
136
+ // For each dep, if it's not in `result` yet, it's newly-discovered:
137
+ // add it to `nextIds`.
138
+ for (final dep in deps) {
139
+ if (result.add (dep)) {
140
+ nextIds.add (dep);
141
+ }
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+ }
147
+
148
+ const _ignoredSchemes = ['dart' , 'dart-ext' ];
149
+
150
+ /// Parses Dart source in [content] , returns all depedencies: all assets
151
+ /// mentioned in directives, excluding `dart:` and `dart-ext` schemes.
152
+ List <AssetId > _parseDependencies (String content, AssetId from) =>
153
+ parseString (content: content, throwIfDiagnostics: false )
154
+ .unit
155
+ .directives
156
+ .whereType <UriBasedDirective >()
157
+ .map ((directive) => directive.uri.stringValue)
158
+ // Uri.stringValue can be null for strings that use interpolation.
159
+ .nonNulls
160
+ .where (
161
+ (uriContent) => ! _ignoredSchemes.any (Uri .parse (uriContent).isScheme),
162
+ )
163
+ .map ((content) => AssetId .resolve (Uri .parse (content), from: from))
164
+ .toList ();
165
+
166
+ extension _AssetIdExtensions on AssetId {
167
+ /// Asset path for the in-memory filesystem.
168
+ String get asPath => AnalysisDriverModelUriResolver .assetPath (this );
54
169
}
0 commit comments