diff --git a/_test_common/lib/in_memory_reader_writer.dart b/_test_common/lib/in_memory_reader_writer.dart index ae5cf9b4f..15a508efc 100644 --- a/_test_common/lib/in_memory_reader_writer.dart +++ b/_test_common/lib/in_memory_reader_writer.dart @@ -16,7 +16,7 @@ class InMemoryRunnerAssetReaderWriter extends InMemoryAssetReaderWriter Stream get onCanRead => _onCanReadController.stream; void Function(AssetId)? onDelete; - InMemoryRunnerAssetReaderWriter({super.sourceAssets, super.rootPackage}); + InMemoryRunnerAssetReaderWriter({super.rootPackage}); @override Future canRead(AssetId id) { diff --git a/_test_common/lib/test_phases.dart b/_test_common/lib/test_phases.dart index c9d228db2..3195fe29c 100644 --- a/_test_common/lib/test_phases.dart +++ b/_test_common/lib/test_phases.dart @@ -113,9 +113,9 @@ Future testBuilders( inputs.forEach((serializedId, contents) { var id = makeAssetId(serializedId); if (contents is String) { - readerWriter.cacheStringAsset(id, contents); + readerWriter.filesystem.writeAsStringSync(id, contents); } else if (contents is List) { - readerWriter.cacheBytesAsset(id, contents); + readerWriter.filesystem.writeAsBytesSync(id, contents); } }); diff --git a/build/CHANGELOG.md b/build/CHANGELOG.md index 2cf4d338f..658d5b176 100644 --- a/build/CHANGELOG.md +++ b/build/CHANGELOG.md @@ -5,6 +5,8 @@ - Use `build_test` 3.0.0. - Refactor `PathProvidingAssetReader` to `AssetPathProvider`. - Refactor `MultiPackageAssetReader` to internal `AssetFinder`. +- Add internal `Filesystem` that backs `AssetReader` and `AssetWriter` + implementations. ## 2.4.2 diff --git a/build/lib/src/builder/build_step_impl.dart b/build/lib/src/builder/build_step_impl.dart index 73f97037b..55c769e29 100644 --- a/build/lib/src/builder/build_step_impl.dart +++ b/build/lib/src/builder/build_step_impl.dart @@ -20,6 +20,7 @@ import '../asset/writer.dart'; import '../resource/resource.dart'; import '../state/asset_finder.dart'; import '../state/asset_path_provider.dart'; +import '../state/filesystem.dart'; import '../state/input_tracker.dart'; import '../state/reader_state.dart'; import 'build_step.dart'; @@ -82,6 +83,9 @@ class BuildStepImpl implements BuildStep, AssetReaderState { _stageTracker = stageTracker ?? NoOpStageTracker.instance, _reportUnusedAssets = reportUnusedAssets; + @override + Filesystem get filesystem => _reader.filesystem; + @override AssetFinder get assetFinder => _reader.assetFinder; diff --git a/build/lib/src/internal.dart b/build/lib/src/internal.dart index 22e32f771..f331af190 100644 --- a/build/lib/src/internal.dart +++ b/build/lib/src/internal.dart @@ -8,5 +8,6 @@ library; export 'state/asset_finder.dart'; export 'state/asset_path_provider.dart'; +export 'state/filesystem.dart'; export 'state/input_tracker.dart'; export 'state/reader_state.dart'; diff --git a/build/lib/src/state/filesystem.dart b/build/lib/src/state/filesystem.dart new file mode 100644 index 000000000..c03e496e7 --- /dev/null +++ b/build/lib/src/state/filesystem.dart @@ -0,0 +1,177 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:pool/pool.dart'; + +import '../asset/id.dart'; +import 'asset_path_provider.dart'; + +/// The filesystem the build is running on. +/// +/// Methods behave as the `dart:io` methods with the same names, with some +/// exceptions noted. +abstract interface class Filesystem { + Future exists(AssetId id); + + Future readAsString(AssetId id, {Encoding encoding = utf8}); + Future readAsBytes(AssetId id); + + /// Deletes a file. + /// + /// If the file does not exist, does nothing. + Future delete(AssetId id); + + /// Deletes a file. + /// + /// If the file does not exist, does nothing. + void deleteSync(AssetId id); + + /// Writes a file. + /// + /// Creates enclosing directories as needed if they don't exist. + void writeAsStringSync(AssetId id, String contents, + {Encoding encoding = utf8}); + + /// Writes a file. + /// + /// Creates enclosing directories as needed if they don't exist. + Future writeAsString(AssetId id, String contents, + {Encoding encoding = utf8}); + + /// Writes a file. + /// + /// Creates enclosing directories as needed if they don't exist. + void writeAsBytesSync(AssetId id, List contents); + + /// Writes a file. + /// + /// Creates enclosing directories as needed if they don't exist. + Future writeAsBytes(AssetId id, List contents); +} + +/// A filesystem using [assetPathProvider] to map to the `dart:io` filesystem. +class IoFilesystem implements Filesystem { + final AssetPathProvider assetPathProvider; + + /// Pool for async file operations. + final _pool = Pool(32); + + IoFilesystem({required this.assetPathProvider}); + + @override + Future exists(AssetId id) => _pool.withResource(_fileFor(id).exists); + + @override + Future readAsBytes(AssetId id) => + _pool.withResource(_fileFor(id).readAsBytes); + + @override + Future readAsString(AssetId id, {Encoding encoding = utf8}) => + _pool.withResource(_fileFor(id).readAsString); + + @override + void deleteSync(AssetId id) { + final file = _fileFor(id); + if (file.existsSync()) file.deleteSync(); + } + + @override + Future delete(AssetId id) { + return _pool.withResource(() async { + final file = _fileFor(id); + if (await file.exists()) await file.delete(); + }); + } + + @override + void writeAsBytesSync(AssetId id, List contents, + {Encoding encoding = utf8}) { + final file = _fileFor(id); + file.parent.createSync(recursive: true); + file.writeAsBytesSync(contents); + } + + @override + Future writeAsBytes(AssetId id, List contents) { + return _pool.withResource(() async { + final file = _fileFor(id); + await file.parent.create(recursive: true); + await file.writeAsBytes(contents); + }); + } + + @override + void writeAsStringSync(AssetId id, String contents, + {Encoding encoding = utf8}) { + final file = _fileFor(id); + file.parent.createSync(recursive: true); + file.writeAsStringSync(contents, encoding: encoding); + } + + @override + Future writeAsString(AssetId id, String contents, + {Encoding encoding = utf8}) { + return _pool.withResource(() async { + final file = _fileFor(id); + await file.parent.create(recursive: true); + await file.writeAsString(contents, encoding: encoding); + }); + } + + /// Returns a [File] for [id] for the current [assetPathProvider]. + File _fileFor(AssetId id) { + return File(assetPathProvider.pathFor(id)); + } +} + +/// An in-memory [Filesystem]. +class InMemoryFilesystem implements Filesystem { + final Map> assets = {}; + + @override + Future exists(AssetId id) async => assets.containsKey(id); + + @override + Future readAsBytes(AssetId id) async => assets[id] as Uint8List; + + @override + Future readAsString(AssetId id, {Encoding encoding = utf8}) async => + encoding.decode(assets[id] as Uint8List); + + @override + Future delete(AssetId id) async { + assets.remove(id); + } + + @override + void deleteSync(AssetId id) async { + assets.remove(id); + } + + @override + void writeAsBytesSync(AssetId id, List contents) { + assets[id] = contents; + } + + @override + Future writeAsBytes(AssetId id, List contents) async { + assets[id] = contents; + } + + @override + void writeAsStringSync(AssetId id, String contents, + {Encoding encoding = utf8}) { + assets[id] = encoding.encode(contents); + } + + @override + Future writeAsString(AssetId id, String contents, + {Encoding encoding = utf8}) async { + assets[id] = encoding.encode(contents); + } +} diff --git a/build/lib/src/state/reader_state.dart b/build/lib/src/state/reader_state.dart index 589257ff2..cbd547c6d 100644 --- a/build/lib/src/state/reader_state.dart +++ b/build/lib/src/state/reader_state.dart @@ -5,10 +5,16 @@ import '../asset/reader.dart'; import 'asset_finder.dart'; import 'asset_path_provider.dart'; +import 'filesystem.dart'; import 'input_tracker.dart'; /// Provides access to the state backing an [AssetReader]. extension AssetReaderStateExtension on AssetReader { + Filesystem get filesystem { + _requireIsAssetReaderState(); + return (this as AssetReaderState).filesystem; + } + AssetFinder get assetFinder { _requireIsAssetReaderState(); return (this as AssetReaderState).assetFinder; @@ -43,6 +49,12 @@ extension AssetReaderStateExtension on AssetReader { /// The state backing an [AssetReader]. abstract interface class AssetReaderState { + /// The [Filesystem] that this reader reads from. + /// + /// Warning: this access to the filesystem bypasses reader functionality + /// such as read tracking, caching and visibility restriction. + Filesystem get filesystem; + /// The [AssetFinder] associated with this reader. /// /// All readers have an [AssetFinder], but the functionality it provides, diff --git a/build/pubspec.yaml b/build/pubspec.yaml index 22f4e9cb9..b8227e35f 100644 --- a/build/pubspec.yaml +++ b/build/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: meta: ^1.3.0 package_config: ^2.1.0 path: ^1.8.0 + pool: ^1.5.0 dev_dependencies: build_resolvers: ^2.4.0 diff --git a/build/test/generate/run_post_process_builder_test.dart b/build/test/generate/run_post_process_builder_test.dart index 00513fd9d..6dfe0868e 100644 --- a/build/test/generate/run_post_process_builder_test.dart +++ b/build/test/generate/run_post_process_builder_test.dart @@ -11,8 +11,7 @@ import '../common/builders.dart'; void main() { group('runPostProcessBuilder', () { - late InMemoryAssetReader reader; - late InMemoryAssetWriter writer; + late InMemoryAssetReaderWriter readerWriter; final copyBuilder = CopyingPostProcessBuilder(); final deleteBuilder = DeletePostProcessBuilder(); final aTxt = makeAssetId('a|lib/a.txt'); @@ -24,38 +23,37 @@ void main() { void addAsset(AssetId id) => adds[id] = true; void deleteAsset(AssetId id) => deletes[id] = true; - Map sourceAssets; - setUp(() async { - sourceAssets = { - aTxt: 'a', - }; - reader = InMemoryAssetReader(sourceAssets: sourceAssets); - writer = InMemoryAssetWriter(); + readerWriter = InMemoryAssetReaderWriter() + ..filesystem.writeAsStringSync(aTxt, 'a'); adds.clear(); deletes.clear(); }); test('can delete assets', () async { - await runPostProcessBuilder(copyBuilder, aTxt, reader, writer, logger, + await runPostProcessBuilder( + copyBuilder, aTxt, readerWriter, readerWriter, logger, addAsset: addAsset, deleteAsset: deleteAsset); - await runPostProcessBuilder(deleteBuilder, aTxt, reader, writer, logger, + await runPostProcessBuilder( + deleteBuilder, aTxt, readerWriter, readerWriter, logger, addAsset: addAsset, deleteAsset: deleteAsset); expect(deletes, contains(aTxt)); expect(deletes, isNot(contains(aTxtCopy))); }); test('can create assets and read the primary asset', () async { - await runPostProcessBuilder(copyBuilder, aTxt, reader, writer, logger, + await runPostProcessBuilder( + copyBuilder, aTxt, readerWriter, readerWriter, logger, addAsset: addAsset, deleteAsset: deleteAsset); - expect(writer.assets, contains(aTxtCopy)); - expect(writer.assets[aTxtCopy], decodedMatches('a')); + expect(readerWriter.assets, contains(aTxtCopy)); + expect(readerWriter.assets[aTxtCopy], decodedMatches('a')); expect(adds, contains(aTxtCopy)); }); test('throws if addAsset throws', () async { expect( - () => runPostProcessBuilder(copyBuilder, aTxt, reader, writer, logger, + () => runPostProcessBuilder( + copyBuilder, aTxt, readerWriter, readerWriter, logger, addAsset: (id) => throw InvalidOutputException(id, ''), deleteAsset: deleteAsset), throwsA(const TypeMatcher())); diff --git a/build_modules/test/decoding_cache_test.dart b/build_modules/test/decoding_cache_test.dart index 70650954a..46f7a582d 100644 --- a/build_modules/test/decoding_cache_test.dart +++ b/build_modules/test/decoding_cache_test.dart @@ -39,53 +39,64 @@ void main() { test('can fetch from disk', () async { final id = AssetId('foo', 'lib/foo'); - final reader = InMemoryAssetReader(sourceAssets: {id: 'foo'}); + final reader = InMemoryAssetReaderWriter() + ..filesystem.writeAsStringSync(id, 'foo'); expect(await cache.find(id, reader), 'foo'); - expect(reader.assetsRead, contains(id)); + expect(reader.inputTracker.assetsRead, contains(id)); expect(fromBytesCalls, contains('foo')); }); test('skips read for value written this build', () async { final id = AssetId('foo', 'lib/foo'); - final reader = InMemoryAssetReader(sourceAssets: {id: 'foo'}); + final reader = InMemoryAssetReaderWriter() + ..filesystem.writeAsStringSync(id, 'foo'); await cache.write(id, InMemoryAssetWriter(), 'bar'); expect(await cache.find(id, reader), 'bar'); - expect(reader.assetsRead, contains(id), reason: 'Should call canRead'); + expect(reader.inputTracker.assetsRead, contains(id), + reason: 'Should call canRead'); expect(fromBytesCalls, isNot(contains('bar'))); }); test('skips read on subsequent fetches', () async { final id = AssetId('foo', 'lib/foo'); - final reader1 = InMemoryAssetReader(sourceAssets: {id: 'foo'}); + final reader1 = InMemoryAssetReaderWriter() + ..filesystem.writeAsStringSync(id, 'foo'); await cache.find(id, reader1); expect(fromBytesCalls['foo'], 1); - final reader2 = InMemoryAssetReader(sourceAssets: {id: 'foo'}); + final reader2 = InMemoryAssetReaderWriter() + ..filesystem.writeAsStringSync(id, 'foo'); expect(await cache.find(id, reader2), 'foo'); - expect(reader2.assetsRead, contains(id), reason: 'Should call canRead'); + expect(reader2.inputTracker.assetsRead, contains(id), + reason: 'Should call canRead'); expect(fromBytesCalls['foo'], 1, reason: 'No extra call to deserialize'); }); test('skips read on subsequent builds if digest has not changed', () async { final id = AssetId('foo', 'lib/foo'); - final reader1 = InMemoryAssetReader(sourceAssets: {id: 'foo'}); + final reader1 = InMemoryAssetReaderWriter() + ..filesystem.writeAsStringSync(id, 'foo'); await cache.find(id, reader1); expect(fromBytesCalls['foo'], 1); await resourceManager.disposeAll(); - final reader2 = InMemoryAssetReader(sourceAssets: {id: 'foo'}); + final reader2 = InMemoryAssetReaderWriter() + ..filesystem.writeAsStringSync(id, 'foo'); expect(await cache.find(id, reader2), 'foo'); - expect(reader2.assetsRead, contains(id), reason: 'Should call canRead'); + expect(reader2.inputTracker.assetsRead, contains(id), + reason: 'Should call canRead'); expect(fromBytesCalls['foo'], 1, reason: 'No extra call to deserialize'); }); test('rereads on subsequent builds if digest has changed', () async { final id = AssetId('foo', 'lib/foo'); - final reader1 = InMemoryAssetReader(sourceAssets: {id: 'foo'}); + final reader1 = InMemoryAssetReaderWriter() + ..filesystem.writeAsStringSync(id, 'foo'); await cache.find(id, reader1); expect(fromBytesCalls['foo'], 1); await resourceManager.disposeAll(); - final reader2 = InMemoryAssetReader(sourceAssets: {id: 'bar'}); + final reader2 = InMemoryAssetReaderWriter() + ..filesystem.writeAsStringSync(id, 'bar'); expect(await cache.find(id, reader2), 'bar'); - expect(reader2.assetsRead, contains(id)); + expect(reader2.inputTracker.assetsRead, contains(id)); expect(fromBytesCalls['bar'], 1, reason: 'Deserialize with new value'); }); }); diff --git a/build_modules/test/meta_module_test.dart b/build_modules/test/meta_module_test.dart index 599c278a6..ffa51faed 100644 --- a/build_modules/test/meta_module_test.dart +++ b/build_modules/test/meta_module_test.dart @@ -23,7 +23,7 @@ void main() { var assets = {}; assetDescriptors.forEach((serializedId, content) { var id = AssetId.parse(serializedId); - reader.cacheStringAsset(id, content); + reader.filesystem.writeAsStringSync(id, content); assets.add(id); }); return assets.toList(); @@ -39,7 +39,7 @@ void main() { ModuleLibrary.fromSource(s, await reader.readAsString(s))))) .where((l) => l.isImportable); for (final library in libraries) { - reader.cacheStringAsset( + reader.filesystem.writeAsStringSync( library.id.changeExtension(moduleLibraryExtension), library.serialize()); } diff --git a/build_resolvers/test/resolver_test.dart b/build_resolvers/test/resolver_test.dart index 7e63e4bd6..015bd5cb6 100644 --- a/build_resolvers/test/resolver_test.dart +++ b/build_resolvers/test/resolver_test.dart @@ -138,7 +138,7 @@ void runTests(ResolversFactory resolversFactory) { }, (resolver) async { await resolver.libraryFor(entryPoint); }, assetReaderChecks: (reader) { - expect(reader.assetsRead, { + expect(reader.inputTracker!.assetsRead, { AssetId('a', 'web/main.dart'), AssetId('a', 'web/main.dart.transitive_digest'), AssetId('a', 'web/a.dart'), @@ -181,7 +181,7 @@ void runTests(ResolversFactory resolversFactory) { await resolveSources(sources, (resolver) async { await resolver.libraryFor(entryPoint); }, assetReaderChecks: (reader) { - expect(reader.assetsRead, { + expect(reader.inputTracker!.assetsRead, { AssetId('a', 'web/main.dart'), AssetId('a', 'web/main.dart.transitive_digest'), AssetId('a', 'web/a.dart'), @@ -194,7 +194,7 @@ void runTests(ResolversFactory resolversFactory) { await resolveSources(sources, (resolver) async { await resolver.libraryFor(entryPoint); }, assetReaderChecks: (reader) { - expect(reader.assetsRead, { + expect(reader.inputTracker!.assetsRead, { AssetId('a', 'web/main.dart'), AssetId('a', 'web/main.dart.transitive_digest'), AssetId('a', 'web/a.dart'), diff --git a/build_runner/test/generate/watch_test.dart b/build_runner/test/generate/watch_test.dart index 099d5befe..199500026 100644 --- a/build_runner/test/generate/watch_test.dart +++ b/build_runner/test/generate/watch_test.dart @@ -14,7 +14,6 @@ import 'package:build_runner/src/generate/watch_impl.dart' as watch_impl; import 'package:build_runner_core/build_runner_core.dart'; import 'package:build_runner_core/src/asset_graph/graph.dart'; import 'package:build_runner_core/src/asset_graph/node.dart'; -import 'package:build_test/build_test.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'package:test/test.dart'; @@ -284,7 +283,7 @@ void main() { {}, {packageConfigId}, buildPackageGraph({rootPackage('a'): []}), - InMemoryAssetReader(sourceAssets: readerWriter.assets)); + readerWriter); var builderOptionsId = makeAssetId('a|Phase0.builderOptions'); var builderOptionsNode = BuilderOptionsAssetNode(builderOptionsId, diff --git a/build_runner/test/server/asset_handler_test.dart b/build_runner/test/server/asset_handler_test.dart index 579859534..30d905153 100644 --- a/build_runner/test/server/asset_handler_test.dart +++ b/build_runner/test/server/asset_handler_test.dart @@ -43,7 +43,7 @@ void main() { node.deletedBy.add(node.id.addExtension('.post_anchor.1')); } graph.add(node); - delegate.cacheStringAsset(node.id, content); + delegate.filesystem.writeAsStringSync(node.id, content); } test('can not read deleted nodes', () async { diff --git a/build_runner/test/server/serve_handler_test.dart b/build_runner/test/server/serve_handler_test.dart index eb5ed4860..ec5e4ff2e 100644 --- a/build_runner/test/server/serve_handler_test.dart +++ b/build_runner/test/server/serve_handler_test.dart @@ -128,7 +128,7 @@ void main() { node.deletedBy.add(node.id.addExtension('.post_anchor.1')); } assetGraph.add(node); - readerWriter.cacheStringAsset(node.id, content); + readerWriter.filesystem.writeAsStringSync(node.id, content); } test('can get handlers for a subdirectory', () async { diff --git a/build_runner/test/server/serve_integration_test.dart b/build_runner/test/server/serve_integration_test.dart index 36b651916..9665674ab 100644 --- a/build_runner/test/server/serve_integration_test.dart +++ b/build_runner/test/server/serve_integration_test.dart @@ -31,10 +31,11 @@ void main() { setUp(() async { final graph = buildPackageGraph({rootPackage('example', path: path): []}); readerWriter = InMemoryRunnerAssetReaderWriter(rootPackage: 'example') - ..cacheStringAsset(AssetId('example', 'web/initial.txt'), 'initial') - ..cacheStringAsset(AssetId('example', 'web/large.txt'), + ..filesystem + .writeAsStringSync(AssetId('example', 'web/initial.txt'), 'initial') + ..filesystem.writeAsStringSync(AssetId('example', 'web/large.txt'), List.filled(10000, 'large').join('')) - ..cacheStringAsset( + ..filesystem.writeAsStringSync( makeAssetId('example|.dart_tool/package_config.json'), jsonEncode({ 'configVersion': 2, @@ -96,8 +97,8 @@ void main() { test('should serve built files', () async { final getHello = Uri.parse('http://localhost/initial.g.txt'); - readerWriter.cacheStringAsset( - AssetId('example', 'web/initial.g.txt'), 'INITIAL'); + readerWriter.filesystem + .writeAsStringSync(AssetId('example', 'web/initial.g.txt'), 'INITIAL'); final response = await handler(Request('GET', getHello)); expect(await response.readAsString(), 'INITIAL'); }); @@ -110,7 +111,8 @@ void main() { test('should serve newly added files', () async { final getNew = Uri.parse('http://localhost/new.txt'); - readerWriter.cacheStringAsset(AssetId('example', 'web/new.txt'), 'New'); + readerWriter.filesystem + .writeAsStringSync(AssetId('example', 'web/new.txt'), 'New'); await Future.value(); FakeWatcher.notifyWatchers( WatchEvent(ChangeType.ADD, '$path/web/new.txt'), @@ -122,7 +124,8 @@ void main() { test('should serve built newly added files', () async { final getNew = Uri.parse('http://localhost/new.g.txt'); - readerWriter.cacheStringAsset(AssetId('example', 'web/new.txt'), 'New'); + readerWriter.filesystem + .writeAsStringSync(AssetId('example', 'web/new.txt'), 'New'); await Future.value(); FakeWatcher.notifyWatchers( WatchEvent(ChangeType.ADD, '$path/web/new.txt'), diff --git a/build_runner_core/CHANGELOG.md b/build_runner_core/CHANGELOG.md index 86012e8d3..ef8afe07b 100644 --- a/build_runner_core/CHANGELOG.md +++ b/build_runner_core/CHANGELOG.md @@ -6,6 +6,8 @@ - Refactor `PathProvidingAssetReader` to `AssetPathProvider`. - Refactor `MultiPackageAssetReader` to internal `AssetFinder`. - `FinalizedReader` no longer implements `AssetReader`. +- Add internal `Filesystem` that backs `AssetReader` and `AssetWriter` + implementations. ## 8.0.0 diff --git a/build_runner_core/lib/src/asset/batch.dart b/build_runner_core/lib/src/asset/batch.dart index 776703b7c..dbc711de8 100644 --- a/build_runner_core/lib/src/asset/batch.dart +++ b/build_runner_core/lib/src/asset/batch.dart @@ -81,6 +81,9 @@ final class BatchReader extends AssetReader implements AssetReaderState { BatchReader(this._inner, this._batch); + @override + Filesystem get filesystem => _inner.filesystem; + @override AssetPathProvider? get assetPathProvider => _inner.assetPathProvider; diff --git a/build_runner_core/lib/src/asset/build_cache.dart b/build_runner_core/lib/src/asset/build_cache.dart index 855c717a1..ca601f1a1 100644 --- a/build_runner_core/lib/src/asset/build_cache.dart +++ b/build_runner_core/lib/src/asset/build_cache.dart @@ -33,6 +33,9 @@ class BuildCacheReader implements AssetReader, AssetReaderState { delegate: delegate.assetPathProvider!, overlay: (id) => _cacheLocation(id, assetGraph, rootPackage)); + @override + Filesystem get filesystem => _delegate.filesystem; + @override AssetFinder get assetFinder => _delegate.assetFinder; diff --git a/build_runner_core/lib/src/asset/cache.dart b/build_runner_core/lib/src/asset/cache.dart index 37a1e1d6b..dc2cde05b 100644 --- a/build_runner_core/lib/src/asset/cache.dart +++ b/build_runner_core/lib/src/asset/cache.dart @@ -47,6 +47,9 @@ class CachingAssetReader implements AssetReader, AssetReaderState { CachingAssetReader(this._delegate); + @override + Filesystem get filesystem => _delegate.filesystem; + @override AssetPathProvider? get assetPathProvider => _delegate.assetPathProvider; diff --git a/build_runner_core/lib/src/asset/file_based.dart b/build_runner_core/lib/src/asset/file_based.dart index a9359a859..6d61c4a4f 100644 --- a/build_runner_core/lib/src/asset/file_based.dart +++ b/build_runner_core/lib/src/asset/file_based.dart @@ -11,23 +11,22 @@ import 'package:build/src/internal.dart'; import 'package:glob/glob.dart'; import 'package:glob/list_local_fs.dart'; import 'package:path/path.dart' as path; -import 'package:pool/pool.dart'; import '../package_graph/package_graph.dart'; import 'writer.dart'; -/// Pool for async file operations, we don't want to use too many file handles. -final _descriptorPool = Pool(32); - /// Basic [AssetReader] which uses a [PackageGraph] to look up where to read /// files from disk. class FileBasedAssetReader extends AssetReader implements AssetReaderState { + @override + final Filesystem filesystem; @override late final AssetFinder assetFinder = FunctionAssetFinder(_findAssets); final PackageGraph packageGraph; - FileBasedAssetReader(this.packageGraph); + FileBasedAssetReader(this.packageGraph) + : filesystem = IoFilesystem(assetPathProvider: packageGraph); @override AssetPathProvider? get assetPathProvider => packageGraph; @@ -36,17 +35,23 @@ class FileBasedAssetReader extends AssetReader implements AssetReaderState { InputTracker? get inputTracker => null; @override - Future canRead(AssetId id) => - _descriptorPool.withResource(() => _fileFor(id, packageGraph).exists()); + Future canRead(AssetId id) => filesystem.exists(id); @override - Future> readAsBytes(AssetId id) => _fileForOrThrow(id, packageGraph) - .then((file) => _descriptorPool.withResource(file.readAsBytes)); + Future> readAsBytes(AssetId id) async { + if (!await filesystem.exists(id)) { + throw AssetNotFoundException(id); + } + return filesystem.readAsBytes(id); + } @override - Future readAsString(AssetId id, {Encoding encoding = utf8}) => - _fileForOrThrow(id, packageGraph).then((file) => _descriptorPool - .withResource(() => file.readAsString(encoding: encoding))); + Future readAsString(AssetId id, {Encoding encoding = utf8}) async { + if (!await filesystem.exists(id)) { + throw AssetNotFoundException(id); + } + return filesystem.readAsString(id, encoding: encoding); + } // This is only for generators, so only `BuildStep` needs to implement it. @override @@ -61,81 +66,50 @@ class FileBasedAssetReader extends AssetReader implements AssetReaderState { 'an input. Please ensure you have that package in your deps, or ' 'remove it from your input sets.'); } + // TODO(davidmorgan): make this read via `filesystem`, currently it + // reads directly via `dart:io`. return glob .list(followLinks: true, root: packageNode.path) .where((e) => e is File && !path.basename(e.path).startsWith('._')) .cast() .map((file) => _fileToAssetId(file, packageNode)); } -} -/// Creates an [AssetId] for [file], which is a part of [packageNode]. -AssetId _fileToAssetId(File file, PackageNode packageNode) { - var filePath = path.normalize(file.absolute.path); - var relativePath = path.relative(filePath, from: packageNode.path); - return AssetId(packageNode.name, relativePath); + /// Creates an [AssetId] for [file], which is a part of [packageNode]. + AssetId _fileToAssetId(File file, PackageNode packageNode) { + var filePath = path.normalize(file.absolute.path); + var relativePath = path.relative(filePath, from: packageNode.path); + return AssetId(packageNode.name, relativePath); + } } /// Basic [AssetWriter] which uses a [PackageGraph] to look up where to write /// files to disk. class FileBasedAssetWriter implements RunnerAssetWriter { final PackageGraph packageGraph; + final Filesystem filesystem; - FileBasedAssetWriter(this.packageGraph); + FileBasedAssetWriter(this.packageGraph) + : filesystem = IoFilesystem(assetPathProvider: packageGraph); @override - Future writeAsBytes(AssetId id, List bytes) async { - var file = _fileFor(id, packageGraph); - await _descriptorPool.withResource(() async { - await file.create(recursive: true); - await file.writeAsBytes(bytes); - }); - } + Future writeAsBytes(AssetId id, List bytes) => + filesystem.writeAsBytes(id, bytes); @override Future writeAsString(AssetId id, String contents, - {Encoding encoding = utf8}) async { - var file = _fileFor(id, packageGraph); - await _descriptorPool.withResource(() async { - await file.create(recursive: true); - await file.writeAsString(contents, encoding: encoding); - }); - } + {Encoding encoding = utf8}) => + filesystem.writeAsString(id, contents, encoding: encoding); @override - Future delete(AssetId id) { + Future delete(AssetId id) async { if (id.package != packageGraph.root.name) { throw InvalidOutputException( id, 'Should not delete assets outside of ${packageGraph.root.name}'); } - - var file = _fileFor(id, packageGraph); - return _descriptorPool.withResource(() async { - if (await file.exists()) { - await file.delete(); - } - }); + await filesystem.delete(id); } @override Future completeBuild() async {} } - -/// Returns a [File] for [id] given [packageGraph]. -File _fileFor(AssetId id, PackageGraph packageGraph) { - return File(packageGraph.pathFor(id)); -} - -/// Returns a `File` for the asset reference by [id] given [packageGraph]. -/// -/// Throws an `AssetNotFoundException` if it doesn't exist. -Future _fileForOrThrow(AssetId id, PackageGraph packageGraph) { - if (packageGraph[id.package] == null) { - return Future.error(PackageNotFoundException(id.package)); - } - var file = _fileFor(id, packageGraph); - return _descriptorPool.withResource(file.exists).then((exists) { - if (!exists) throw AssetNotFoundException(id); - return file; - }); -} diff --git a/build_runner_core/lib/src/asset/reader.dart b/build_runner_core/lib/src/asset/reader.dart index 8864937de..874e1a41a 100644 --- a/build_runner_core/lib/src/asset/reader.dart +++ b/build_runner_core/lib/src/asset/reader.dart @@ -74,6 +74,9 @@ class SingleStepReader implements AssetReader, AssetReaderState { this._primaryPackage, this._isReadableNode, this._checkInvalidInput, [this._getGlobNode, this._writtenAssets]); + @override + Filesystem get filesystem => _delegate.filesystem; + @override AssetPathProvider? get assetPathProvider => _delegate.assetPathProvider; diff --git a/build_runner_core/test/asset/cache_test.dart b/build_runner_core/test/asset/cache_test.dart index 93f193746..50b325a2b 100644 --- a/build_runner_core/test/asset/cache_test.dart +++ b/build_runner_core/test/asset/cache_test.dart @@ -12,14 +12,12 @@ void main() { var missingTxt = AssetId('a', 'missing.txt'); var fooContent = 'bar'; var fooutf8Bytes = decodedMatches('bar'); - var assets = { - fooTxt: 'bar', - }; late InMemoryRunnerAssetReaderWriter delegate; late CachingAssetReader reader; setUp(() { - delegate = InMemoryRunnerAssetReaderWriter(sourceAssets: assets); + delegate = InMemoryRunnerAssetReaderWriter() + ..filesystem.writeAsStringSync(fooTxt, 'bar'); reader = CachingAssetReader(delegate); }); @@ -27,104 +25,104 @@ void main() { test('should read from the delegate', () async { expect(await reader.canRead(fooTxt), isTrue); expect(await reader.canRead(missingTxt), isFalse); - expect(delegate.assetsRead, {fooTxt, missingTxt}); + expect(delegate.inputTracker.assetsRead, {fooTxt, missingTxt}); }); test('should not re-read from the delegate', () async { expect(await reader.canRead(fooTxt), isTrue); - delegate.assetsRead.clear(); + delegate.inputTracker.assetsRead.clear(); expect(await reader.canRead(fooTxt), isTrue); - expect(delegate.assetsRead, isEmpty); + expect(delegate.inputTracker.assetsRead, isEmpty); }); test('can be invalidated with invalidate', () async { expect(await reader.canRead(fooTxt), isTrue); - delegate.assetsRead.clear(); - expect(delegate.assetsRead, isEmpty); + delegate.inputTracker.assetsRead.clear(); + expect(delegate.inputTracker.assetsRead, isEmpty); reader.invalidate([fooTxt]); expect(await reader.canRead(fooTxt), isTrue); - expect(delegate.assetsRead, [fooTxt]); + expect(delegate.inputTracker.assetsRead, [fooTxt]); }); }); group('readAsBytes', () { test('should read from the delegate', () async { expect(await reader.readAsBytes(fooTxt), fooutf8Bytes); - expect(delegate.assetsRead, [fooTxt]); + expect(delegate.inputTracker.assetsRead, [fooTxt]); }); test('should not re-read from the delegate', () async { expect(await reader.readAsBytes(fooTxt), fooutf8Bytes); - delegate.assetsRead.clear(); + delegate.inputTracker.assetsRead.clear(); expect(await reader.readAsBytes(fooTxt), fooutf8Bytes); - expect(delegate.assetsRead, isEmpty); + expect(delegate.inputTracker.assetsRead, isEmpty); }); test('can be invalidated with invalidate', () async { expect(await reader.readAsBytes(fooTxt), fooutf8Bytes); - delegate.assetsRead.clear(); - expect(delegate.assetsRead, isEmpty); + delegate.inputTracker.assetsRead.clear(); + expect(delegate.inputTracker.assetsRead, isEmpty); reader.invalidate([fooTxt]); expect(await reader.readAsBytes(fooTxt), fooutf8Bytes); - expect(delegate.assetsRead, [fooTxt]); + expect(delegate.inputTracker.assetsRead, [fooTxt]); }); test('should not cache bytes during readAsString calls', () async { expect(await reader.readAsString(fooTxt), fooContent); - expect(delegate.assetsRead, [fooTxt]); - delegate.assetsRead.clear(); + expect(delegate.inputTracker.assetsRead, [fooTxt]); + delegate.inputTracker.assetsRead.clear(); expect(await reader.readAsBytes(fooTxt), fooutf8Bytes); - expect(delegate.assetsRead, [fooTxt]); + expect(delegate.inputTracker.assetsRead, [fooTxt]); }); }); group('readAsString', () { test('should read from the delegate', () async { expect(await reader.readAsString(fooTxt), fooContent); - expect(delegate.assetsRead, [fooTxt]); + expect(delegate.inputTracker.assetsRead, [fooTxt]); }); test('should not re-read from the delegate', () async { expect(await reader.readAsString(fooTxt), fooContent); - delegate.assetsRead.clear(); + delegate.inputTracker.assetsRead.clear(); expect(await reader.readAsString(fooTxt), fooContent); - expect(delegate.assetsRead, isEmpty); + expect(delegate.inputTracker.assetsRead, isEmpty); }); test('can be invalidated with invalidate', () async { expect(await reader.readAsString(fooTxt), fooContent); - delegate.assetsRead.clear(); - expect(delegate.assetsRead, isEmpty); + delegate.inputTracker.assetsRead.clear(); + expect(delegate.inputTracker.assetsRead, isEmpty); reader.invalidate([fooTxt]); expect(await reader.readAsString(fooTxt), fooContent); - expect(delegate.assetsRead, [fooTxt]); + expect(delegate.inputTracker.assetsRead, [fooTxt]); }); test('uses cached bytes if available', () async { expect(await reader.readAsBytes(fooTxt), fooutf8Bytes); - expect(delegate.assetsRead, [fooTxt]); - delegate.assetsRead.clear(); + expect(delegate.inputTracker.assetsRead, [fooTxt]); + delegate.inputTracker.assetsRead.clear(); expect(await reader.readAsString(fooTxt), fooContent); - expect(delegate.assetsRead, isEmpty); + expect(delegate.inputTracker.assetsRead, isEmpty); }); }); group('digest', () { test('should read from the delegate', () async { expect(await reader.digest(fooTxt), isNotNull); - expect(delegate.assetsRead, [fooTxt]); + expect(delegate.inputTracker.assetsRead, [fooTxt]); }); test('should re-read from the delegate (no cache)', () async { expect(await reader.digest(fooTxt), isNotNull); - delegate.assetsRead.clear(); + delegate.inputTracker.assetsRead.clear(); expect(await reader.digest(fooTxt), isNotNull); - expect(delegate.assetsRead, [fooTxt]); + expect(delegate.inputTracker.assetsRead, [fooTxt]); }); }); } diff --git a/build_runner_core/test/environment/create_merged_dir_test.dart b/build_runner_core/test/environment/create_merged_dir_test.dart index 9b3e4d171..8f7973e94 100644 --- a/build_runner_core/test/environment/create_merged_dir_test.dart +++ b/build_runner_core/test/environment/create_merged_dir_test.dart @@ -66,7 +66,10 @@ void main() { late FinalizedAssetsView finalizedAssetsView; setUp(() async { - readerWriter = InMemoryRunnerAssetReaderWriter(sourceAssets: sources); + readerWriter = InMemoryRunnerAssetReaderWriter(); + for (final source in sources.entries) { + readerWriter.filesystem.writeAsStringSync(source.key, source.value); + } environment = TestBuildEnvironment(readerWriter: readerWriter); graph = await AssetGraph.build(phases, sources.keys.toSet(), {}, packageGraph, readerWriter); @@ -81,7 +84,8 @@ void main() { ..state = NodeState.upToDate ..wasOutput = true ..isFailure = false; - readerWriter.cacheStringAsset(id, sources[node.primaryInput]!); + readerWriter.filesystem + .writeAsStringSync(id, sources[node.primaryInput]!); } tmpDir = await Directory.systemTemp.createTemp('build_tests'); anotherTmpDir = await Directory.systemTemp.createTemp('build_tests'); diff --git a/build_runner_core/test/generate/build_test.dart b/build_runner_core/test/generate/build_test.dart index 47079aa3f..1eaaaf570 100644 --- a/build_runner_core/test/generate/build_test.dart +++ b/build_runner_core/test/generate/build_test.dart @@ -15,7 +15,6 @@ import 'package:build_runner_core/src/asset_graph/node.dart'; import 'package:build_runner_core/src/generate/options.dart' show defaultNonRootVisibleAssets; import 'package:build_runner_core/src/util/constants.dart'; -import 'package:build_test/build_test.dart'; import 'package:glob/glob.dart'; import 'package:test/test.dart'; @@ -1090,7 +1089,7 @@ void main() { {}, {makeAssetId('a|.dart_tool/package_config.json')}, buildPackageGraph({rootPackage('a'): []}), - InMemoryAssetReader(sourceAssets: result.readerWriter.assets)); + result.readerWriter); // Source nodes var aSourceNode = makeAssetNode( diff --git a/build_test/CHANGELOG.md b/build_test/CHANGELOG.md index d4d7872d3..fc5aacad1 100644 --- a/build_test/CHANGELOG.md +++ b/build_test/CHANGELOG.md @@ -15,6 +15,11 @@ - Start using `package:build/src/internal.dart`. - Refactor `PathProvidingAssetReader` to `AssetPathProvider` - Refactor `MultiPackageAssetReader` to internal `AssetFinder`. +- Add internal `Filesystem` that backs `AssetReader` and `AssetWriter` + implementations. +- Breaking change: `InMemoryAssetReader` constrictur no longer accepts sources, + and `cacheBytes` methods are removed. Tests should write to its `filesystem` + instead. ## 2.2.3 diff --git a/build_test/lib/src/in_memory_reader.dart b/build_test/lib/src/in_memory_reader.dart index aa8ad493c..525f48d5c 100644 --- a/build_test/lib/src/in_memory_reader.dart +++ b/build_test/lib/src/in_memory_reader.dart @@ -2,6 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:convert'; +import 'dart:typed_data'; import 'package:build/build.dart'; // ignore: implementation_imports @@ -13,57 +14,35 @@ abstract class RecordingAssetReader implements AssetReader { Set get assetsRead; } -/// An implementation of [AssetWriter] that records outputs to [assets]. -abstract class RecordingAssetWriter implements AssetWriter { - Map> get assets; -} - /// An implementation of [AssetReader] and [AssetWriter] with primed in-memory /// assets. +/// +/// TODO(davidmorgan): merge into `FileBasedReader` and `FileBasedWriter`. class InMemoryAssetReaderWriter extends AssetReader implements InMemoryAssetReader, InMemoryAssetWriter { @override late final AssetFinder assetFinder = FunctionAssetFinder(_findAssets); - @override - final Map> assets; + final InMemoryFilesystem _filesystem = InMemoryFilesystem(); + final String? rootPackage; @override final InputTracker inputTracker = InputTracker(); - @override - Set get assetsRead => inputTracker.assetsRead; - - /// Create a new asset reader that contains [sourceAssets]. - /// - /// Any strings in [sourceAssets] will be converted into a `List` of - /// bytes. + /// Create a new asset reader/writer. /// /// May optionally define a [rootPackage], which is required for some APIs. - InMemoryAssetReaderWriter( - {Map? sourceAssets, this.rootPackage}) - : assets = _assetsAsBytes(sourceAssets); + InMemoryAssetReaderWriter({this.rootPackage}); @override AssetPathProvider? get assetPathProvider => null; - static Map> _assetsAsBytes(Map? assets) { - if (assets == null || assets.isEmpty) { - return {}; - } - final output = >{}; - assets.forEach((id, stringOrBytes) { - if (stringOrBytes is List) { - output[id] = stringOrBytes; - } else if (stringOrBytes is String) { - output[id] = utf8.encode(stringOrBytes); - } else { - throw UnsupportedError('Invalid asset contents: $stringOrBytes.'); - } - }); - return output; - } + @override + Filesystem get filesystem => _filesystem; + + @override + Map> get assets => _filesystem.assets; @override Future canRead(AssetId id) async { @@ -100,65 +79,38 @@ class InMemoryAssetReaderWriter extends AssetReader .where((id) => id.package == package && glob.matches(id.path))); } - @override - void cacheBytesAsset(AssetId id, List bytes) { - assets[id] = bytes; - } - - @override - void cacheStringAsset(AssetId id, String contents, {Encoding? encoding}) { - encoding ??= utf8; - assets[id] = encoding.encode(contents); - } - @override Future writeAsBytes(AssetId id, List bytes) async { - assets[id] = bytes; + assets[id] = bytes is Uint8List ? bytes : Uint8List.fromList(bytes); } @override Future writeAsString(AssetId id, String contents, {Encoding encoding = utf8}) async { - assets[id] = encoding.encode(contents); + assets[id] = encoding.encode(contents) as Uint8List; } } /// An implementation of [AssetReader] with primed in-memory assets. -abstract class InMemoryAssetReader - implements AssetReader, RecordingAssetReader, AssetReaderState { +abstract class InMemoryAssetReader implements AssetReader, AssetReaderState { abstract final Map> assets; - /// Create a new asset reader that contains [sourceAssets]. - /// - /// Any strings in [sourceAssets] will be converted into a `List` of - /// bytes. + /// Create a new asset reader. /// /// May optionally define a [rootPackage], which is required for some APIs. - factory InMemoryAssetReader( - {Map? sourceAssets, String? rootPackage}) => - InMemoryAssetReaderWriter( - sourceAssets: sourceAssets, rootPackage: rootPackage); - - /// Create a new asset reader backed by [assets]. - // InMemoryAssetReader.shareAssetCache(this.assets, {this.rootPackage}); - - void cacheBytesAsset(AssetId id, List bytes); - - void cacheStringAsset(AssetId id, String contents, {Encoding? encoding}); + factory InMemoryAssetReader({String? rootPackage}) => + InMemoryAssetReaderWriter(rootPackage: rootPackage); } /// An implementation of [AssetWriter] that writes outputs to memory. -abstract class InMemoryAssetWriter implements RecordingAssetWriter { +abstract class InMemoryAssetWriter implements AssetWriter { + abstract final Map> assets; + factory InMemoryAssetWriter() => InMemoryAssetReaderWriter(); @override - Future writeAsBytes(AssetId id, List bytes) async { - assets[id] = bytes; - } + Future writeAsBytes(AssetId id, List bytes); @override - Future writeAsString(AssetId id, String contents, - {Encoding encoding = utf8}) async { - assets[id] = encoding.encode(contents); - } + Future writeAsString(AssetId id, String contents, {Encoding encoding = utf8}); } diff --git a/build_test/lib/src/in_memory_writer.dart b/build_test/lib/src/in_memory_writer.dart index 92046e5fb..5be92c207 100644 --- a/build_test/lib/src/in_memory_writer.dart +++ b/build_test/lib/src/in_memory_writer.dart @@ -2,4 +2,4 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -export 'in_memory_reader.dart' show InMemoryAssetWriter, RecordingAssetWriter; +export 'in_memory_reader.dart' show InMemoryAssetWriter; diff --git a/build_test/lib/src/resolve_source.dart b/build_test/lib/src/resolve_source.dart index 23a65952b..dbe0c11cf 100644 --- a/build_test/lib/src/resolve_source.dart +++ b/build_test/lib/src/resolve_source.dart @@ -191,24 +191,24 @@ Future _resolveAssets( tearDown, ); + final inputAssetIds = inputs.keys.map(AssetId.parse).toList(); + // Prepare the in-memory filesystem the build will run on. - // + final readerWriter = InMemoryAssetReaderWriter( + rootPackage: rootPackage, + ); + // First, add directly-passed [inputs], reading from the filesystem if the // string passed is [useAssetReader]. final assetReader = PackageAssetReader(resolvedConfig, rootPackage); - final inputAssets = {}; - await Future.wait(inputs.keys.map((String rawAssetId) async { - final assetId = AssetId.parse(rawAssetId); - var assetValue = inputs[rawAssetId]!; + + for (final assetId in inputAssetIds) { + var assetValue = inputs[assetId.toString()]!; if (assetValue == useAssetReader) { assetValue = await assetReader.readAsString(assetId); } - inputAssets[assetId] = assetValue; - })); - final readerWriter = InMemoryAssetReaderWriter( - sourceAssets: inputAssets, - rootPackage: rootPackage, - ); + await readerWriter.writeAsString(assetId, assetValue); + } // Then, copy any additionally requested files from the filesystem to the // in-memory filesystem. @@ -234,7 +234,7 @@ Future _resolveAssets( // `onDone` future that we return. unawaited(runBuilder( resolveBuilder, - inputAssets.keys, + inputAssetIds, readerWriter, readerWriter, resolvers, diff --git a/build_test/lib/src/test_builder.dart b/build_test/lib/src/test_builder.dart index c654d47e3..667a97c94 100644 --- a/build_test/lib/src/test_builder.dart +++ b/build_test/lib/src/test_builder.dart @@ -37,7 +37,7 @@ AssetId _passThrough(AssetId id) => id; void checkOutputs( Map|String|Matcher>*/ Object>? outputs, Iterable actualAssets, - RecordingAssetWriter writer, + InMemoryAssetWriter writer, {AssetId Function(AssetId id) mapAssetIds = _passThrough}) { var modifiableActualAssets = Set.of(actualAssets); if (outputs != null) { @@ -141,9 +141,9 @@ Future testBuilder( sourceAssets.forEach((serializedId, contents) { var id = makeAssetId(serializedId); if (contents is String) { - readerWriter.cacheStringAsset(id, contents); + readerWriter.filesystem.writeAsStringSync(id, contents); } else if (contents is List) { - readerWriter.cacheBytesAsset(id, contents); + readerWriter.filesystem.writeAsBytesSync(id, contents); } }); diff --git a/build_test/lib/src/written_asset_reader.dart b/build_test/lib/src/written_asset_reader.dart index 3acf7b321..8521b29f4 100644 --- a/build_test/lib/src/written_asset_reader.dart +++ b/build_test/lib/src/written_asset_reader.dart @@ -9,11 +9,11 @@ import 'package:build/build.dart'; import 'package:build/src/internal.dart'; import 'package:glob/glob.dart'; -import 'in_memory_writer.dart'; +import 'in_memory_reader.dart'; /// An [AssetReader] which supports reads from previous outputs. class WrittenAssetReader extends AssetReader implements AssetReaderState { - final RecordingAssetWriter source; + final InMemoryAssetReaderWriter source; /// An optional [AssetWriterSpy] to limit what's readable through this reader. /// @@ -26,6 +26,9 @@ class WrittenAssetReader extends AssetReader implements AssetReaderState { WrittenAssetReader(this.source, [this.filterSpy]); + @override + Filesystem get filesystem => source.filesystem; + @override late final AssetFinder assetFinder = FunctionAssetFinder(_findAssets); diff --git a/build_test/test/in_memory_reader_test.dart b/build_test/test/in_memory_reader_test.dart index 4bed4461f..1f965059b 100644 --- a/build_test/test/in_memory_reader_test.dart +++ b/build_test/test/in_memory_reader_test.dart @@ -16,14 +16,11 @@ void main() { late InMemoryAssetReader assetReader; setUp(() { - var allAssets = { - libAsset: 'libAsset', - testAsset: 'testAsset', - }; - assetReader = InMemoryAssetReader( - sourceAssets: allAssets, + assetReader = InMemoryAssetReaderWriter( rootPackage: packageName, - ); + ) + ..filesystem.writeAsStringSync(libAsset, 'libAsset') + ..filesystem.writeAsStringSync(testAsset, 'testAsset'); }); test('#findAssets should throw if rootPackage and package are not supplied', @@ -48,7 +45,7 @@ void main() { test('#findAssets should be able to list files in non-root packages', () async { var otherLibAsset = AssetId('other', 'lib/other.dart'); - assetReader.cacheStringAsset(otherLibAsset, 'otherLibAsset'); + assetReader.filesystem.writeAsStringSync(otherLibAsset, 'otherLibAsset'); expect( await assetReader.assetFinder .find(Glob('lib/*.dart'), package: 'other') diff --git a/build_test/test/written_asset_reader_test.dart b/build_test/test/written_asset_reader_test.dart index a0b6526b0..8af5cabca 100644 --- a/build_test/test/written_asset_reader_test.dart +++ b/build_test/test/written_asset_reader_test.dart @@ -5,10 +5,10 @@ import 'package:test/test.dart'; void main() { late WrittenAssetReader reader; - late InMemoryAssetWriter writer; + late InMemoryAssetReaderWriter writer; setUp(() async { - writer = InMemoryAssetWriter(); + writer = InMemoryAssetReaderWriter(); await writer.writeAsString(AssetId.parse('foo|a.txt'), 'a'); await writer.writeAsString(AssetId.parse('foo|lib/b.dart'), 'b'); await writer.writeAsString(AssetId.parse('bar|a.txt'), 'b_a'); diff --git a/scratch_space/test/scratch_space_test.dart b/scratch_space/test/scratch_space_test.dart index 86448fa56..461412d33 100644 --- a/scratch_space/test/scratch_space_test.dart +++ b/scratch_space/test/scratch_space_test.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; @@ -23,14 +24,18 @@ void main() { 'dep|lib/dep.dart', 'myapp|lib/myapp.dart', 'myapp|web/main.dart', - ].fold>>({}, (assets, serializedId) { - assets[AssetId.parse(serializedId)] = serializedId.codeUnits; + ].fold>({}, (assets, serializedId) { + assets[AssetId.parse(serializedId)] = + Uint8List.fromList(serializedId.codeUnits); return assets; }); setUp(() async { scratchSpace = ScratchSpace(); - assetReader = InMemoryAssetReader(sourceAssets: allAssets); + assetReader = InMemoryAssetReaderWriter(); + for (final asset in allAssets.entries) { + assetReader.filesystem.writeAsBytesSync(asset.key, asset.value); + } await scratchSpace.ensureAssets(allAssets.keys, assetReader); });