-
Notifications
You must be signed in to change notification settings - Fork 212
Add Filesystem
underneath Reader
, simplify tests further.
#3860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<bool> exists(AssetId id); | ||
|
||
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}); | ||
Future<Uint8List> readAsBytes(AssetId id); | ||
|
||
/// Deletes a file. | ||
/// | ||
/// If the file does not exist, does nothing. | ||
Future<void> 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<void> 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<int> contents); | ||
|
||
/// Writes a file. | ||
/// | ||
/// Creates enclosing directories as needed if they don't exist. | ||
Future<void> writeAsBytes(AssetId id, List<int> 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<bool> exists(AssetId id) => _pool.withResource(_fileFor(id).exists); | ||
|
||
@override | ||
Future<Uint8List> readAsBytes(AssetId id) => | ||
_pool.withResource(_fileFor(id).readAsBytes); | ||
|
||
@override | ||
Future<String> 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<void> delete(AssetId id) { | ||
return _pool.withResource(() async { | ||
final file = _fileFor(id); | ||
if (await file.exists()) await file.delete(); | ||
}); | ||
} | ||
|
||
@override | ||
void writeAsBytesSync(AssetId id, List<int> contents, | ||
{Encoding encoding = utf8}) { | ||
final file = _fileFor(id); | ||
file.parent.createSync(recursive: true); | ||
file.writeAsBytesSync(contents); | ||
} | ||
|
||
@override | ||
Future<void> writeAsBytes(AssetId id, List<int> 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<void> 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<AssetId, List<int>> assets = {}; | ||
|
||
@override | ||
Future<bool> exists(AssetId id) async => assets.containsKey(id); | ||
|
||
@override | ||
Future<Uint8List> readAsBytes(AssetId id) async => assets[id] as Uint8List; | ||
|
||
@override | ||
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) async => | ||
encoding.decode(assets[id] as Uint8List); | ||
|
||
@override | ||
Future<void> delete(AssetId id) async { | ||
assets.remove(id); | ||
} | ||
|
||
@override | ||
void deleteSync(AssetId id) async { | ||
assets.remove(id); | ||
} | ||
|
||
@override | ||
void writeAsBytesSync(AssetId id, List<int> contents) { | ||
assets[id] = contents; | ||
} | ||
|
||
@override | ||
Future<void> writeAsBytes(AssetId id, List<int> contents) async { | ||
assets[id] = contents; | ||
} | ||
|
||
@override | ||
void writeAsStringSync(AssetId id, String contents, | ||
{Encoding encoding = utf8}) { | ||
assets[id] = encoding.encode(contents); | ||
} | ||
|
||
@override | ||
Future<void> writeAsString(AssetId id, String contents, | ||
{Encoding encoding = utf8}) async { | ||
assets[id] = encoding.encode(contents); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need to be on the interface? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are correct that only test code uses this for now. That's because non-test code was restricted to using the Each generator has a different view on the filesystem, with some set of files hidden because they are "generated after" the current generator runs. Currently the import graph computation deals with that by checking the whole thing for each generator. Through this refactoring Then they can be fast :) by building the base state once then applying the diff for the current generator on top. |
||
|
||
/// The [AssetFinder] associated with this reader. | ||
/// | ||
/// All readers have an [AssetFinder], but the functionality it provides, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A
existsSync
wouldn't be useful? Or sync versions of read? Are there any "rules" for when calling the sync version or not?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It certainly will :) in this PR I just added what's already used.
The generator-facing interfaces,
AssetReader
/AssetWriter
, are only async. We can add sync methods but we can't move existing async use to sync.But for
build_runner
internal use we can switch things to sync and it's probably better--benchmarking showed it gives about a 2x speedup. So one of the goals of this refactoring is to givebuild_runner
internals the choice.