Skip to content

Commit 3700547

Browse files
committed
Add to resolver_test, add to AnalysisDriverModel as needed to satisfy the tests.
Run the tests with both "shared" instances, re-used between tests (as before) and "new" instances.
1 parent 5c1ddd4 commit 3700547

File tree

3 files changed

+363
-71
lines changed

3 files changed

+363
-71
lines changed

build_resolvers/lib/src/analysis_driver_model.dart

Lines changed: 156 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import 'package:path/path.dart' as p;
1515

1616
import 'analysis_driver_model_uri_resolver.dart';
1717

18+
const _transitiveDigestExtension = '.transitive_digest';
19+
1820
/// Manages analysis driver and related build state.
1921
///
2022
/// - Tracks the import graph of all sources needed for analysis.
@@ -34,16 +36,18 @@ class AnalysisDriverModel {
3436
final MemoryResourceProvider resourceProvider =
3537
MemoryResourceProvider(context: p.posix);
3638

39+
final _graph = _Graph();
40+
final _readForAnalyzer = <AssetId>{};
41+
3742
/// Notifies that [step] has completed.
3843
///
3944
/// All build steps must complete before [reset] is called.
40-
void notifyComplete(BuildStep step) {
41-
// TODO(davidmorgan): add test coverage, fix implementation.
42-
}
45+
void notifyComplete(BuildStep step) {}
4346

4447
/// Clear cached information specific to an individual build.
4548
void reset() {
46-
// TODO(davidmorgan): add test coverage, fix implementation.
49+
_graph.clear();
50+
_readForAnalyzer.clear();
4751
}
4852

4953
/// Attempts to parse [uri] into an [AssetId] and returns it if it is cached.
@@ -82,66 +86,70 @@ class AnalysisDriverModel {
8286
FutureOr<void> Function(AnalysisDriverForPackageBuild))
8387
withDriverResource,
8488
{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);
89+
await withDriverResource((driver) async {
90+
var analyzerIds = entryPoints;
91+
Iterable<AssetId> inputIds = entryPoints;
92+
93+
// If requested, find transitive imports.
94+
if (transitive) {
95+
await _graph.load(buildStep, entryPoints);
96+
// TODO(davidmorgan): use just the set for the entrypoints.
97+
analyzerIds = _graph.nodes.keys.toList();
98+
inputIds = _graph.inputsFor(entryPoints);
99+
100+
// Check for missing inputs that were written during the build.
101+
for (final id in inputIds
102+
.where((id) => !id.path.endsWith(_transitiveDigestExtension))) {
103+
if (_graph.nodes[id]!.isMissing) {
104+
if (await buildStep.canRead(id)) {
105+
analyzerIds.add(id);
106+
_readForAnalyzer.remove(id);
107+
}
108+
}
106109
}
107110
}
108-
}
109111

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);
112+
// Notify [buildStep] of its inputs.
113+
for (final id in inputIds) {
114+
await buildStep.canRead(id);
116115
}
117-
await driver.applyPendingFileChanges();
118-
});
119-
}
120116

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);
117+
// Apply changes to in-memory filesystem.
118+
final changedIds = <AssetId>[];
119+
for (final id in analyzerIds) {
120+
if (!_readForAnalyzer.add(id)) continue;
121+
final content = await buildStep.canRead(id)
122+
? await buildStep.readAsString(id)
123+
: null;
124+
final inMemoryFile = resourceProvider.getFile(id.asPath);
125+
final inMemoryContent =
126+
inMemoryFile.exists ? inMemoryFile.readAsStringSync() : null;
135127

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);
128+
if (content != inMemoryContent) {
129+
if (content == null) {
130+
// TODO(davidmorgan): per "globallySeenAssets" in
131+
// BuildAssetUriResolver, deletes should only be applied at the end
132+
// of the build, in case the file is actually there but not visible
133+
// to the current reader.
134+
resourceProvider.deleteFile(id.asPath);
135+
changedIds.add(id);
136+
} else {
137+
if (inMemoryContent == null) {
138+
resourceProvider.newFile(id.asPath, content);
139+
} else {
140+
resourceProvider.modifyFile(id.asPath, content);
141+
}
142+
changedIds.add(id);
143+
}
141144
}
142145
}
143-
}
144-
return result;
146+
147+
// Notify the analyzer of changes.
148+
for (final id in changedIds) {
149+
driver.changeFile(id.asPath);
150+
}
151+
await driver.applyPendingFileChanges();
152+
});
145153
}
146154
}
147155

@@ -167,3 +175,95 @@ extension _AssetIdExtensions on AssetId {
167175
/// Asset path for the in-memory filesystem.
168176
String get asPath => AnalysisDriverModelUriResolver.assetPath(this);
169177
}
178+
179+
class _Graph {
180+
final Map<AssetId, _Node> nodes = {};
181+
182+
/// Walks the import graph from [ids] loading into [nodes].
183+
Future<void> load(AssetReader reader, Iterable<AssetId> ids) async {
184+
final nextIds = Queue.of(ids);
185+
while (nextIds.isNotEmpty) {
186+
final nextId = nextIds.removeFirst();
187+
188+
// Skip if already seen.
189+
if (nodes.containsKey(nextId)) continue;
190+
191+
final hasTransitiveDigestAsset =
192+
await reader.canRead(nextId.addExtension(_transitiveDigestExtension));
193+
194+
// Skip if not readable.
195+
if (!await reader.canRead(nextId)) {
196+
nodes[nextId] = _Node(
197+
id: nextId,
198+
deps: null,
199+
hasTransitiveDigestAsset: hasTransitiveDigestAsset);
200+
continue;
201+
}
202+
203+
final content = await reader.readAsString(nextId);
204+
final deps = _parseDependencies(content, nextId);
205+
nodes[nextId] = _Node(
206+
id: nextId,
207+
deps: deps,
208+
hasTransitiveDigestAsset: hasTransitiveDigestAsset);
209+
nextIds.addAll(deps.where((id) => !nodes.containsKey(id)));
210+
}
211+
}
212+
213+
void clear() {
214+
nodes.clear();
215+
}
216+
217+
/// The inputs for a build action analyzing [entryPoints].
218+
///
219+
/// This is transitive deps, but cut off by the presence of any
220+
/// `.transitive_digest` file next to an asset.
221+
Set<AssetId> inputsFor(Iterable<AssetId> entryPoints) {
222+
final result = entryPoints.toSet();
223+
final nextIds = Queue.of(entryPoints);
224+
225+
while (nextIds.isNotEmpty) {
226+
final nextId = nextIds.removeFirst();
227+
final node = nodes[nextId]!;
228+
229+
// Add the transitive digest file as an input. If it exists, skip deps.
230+
result.add(nextId.addExtension(_transitiveDigestExtension));
231+
if (node.hasTransitiveDigestAsset) {
232+
continue;
233+
}
234+
235+
// Skip if there are no deps because the file is missing.
236+
if (node.deps == null) continue;
237+
238+
// For each dep, if it's not in `result` yet, it's newly-discovered:
239+
// add it to `nextIds`.
240+
for (final dep in node.deps!) {
241+
if (result.add(dep)) {
242+
nextIds.add(dep);
243+
}
244+
}
245+
}
246+
return result;
247+
}
248+
249+
@override
250+
String toString() => nodes.toString();
251+
}
252+
253+
class _Node {
254+
final AssetId id;
255+
final List<AssetId>? deps;
256+
final bool hasTransitiveDigestAsset;
257+
258+
_Node(
259+
{required this.id,
260+
required this.deps,
261+
required this.hasTransitiveDigestAsset});
262+
263+
bool get isMissing => deps == null;
264+
265+
@override
266+
String toString() => '$id:'
267+
'${hasTransitiveDigestAsset ? 'digest:' : ''}'
268+
'${deps?.toString() ?? 'missing'}';
269+
}

0 commit comments

Comments
 (0)