Skip to content
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

Add Filesystem underneath Reader, simplify tests further. #3860

Merged
merged 4 commits into from
Feb 19, 2025

Conversation

davidmorgan
Copy link
Contributor

@davidmorgan davidmorgan commented Feb 14, 2025

For #3811.

Copy link

github-actions bot commented Feb 14, 2025

PR Health

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

@davidmorgan davidmorgan force-pushed the reader-filesystem branch 4 times, most recently from 6bce827 to 8d83144 Compare February 17, 2025 12:54
@davidmorgan davidmorgan marked this pull request as ready for review February 17, 2025 13:29
@davidmorgan davidmorgan requested a review from jensjoha February 17, 2025 13:29
/// Methods behave as the `dart:io` methods with the same names, with some
/// exceptions noted.
abstract interface class Filesystem {
Future<bool> exists(AssetId id);

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?

Copy link
Contributor Author

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 give build_runner internals the choice.

///
/// Warning: this access to the filesystem bypasses reader functionality
/// such as read tracking, caching and visibility restriction.
Filesystem get filesystem;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be on the interface?
It - at least mostly - seems to be used on the InMemoryAssetReaderWriter.
And it generally seems like something that shouldn't be used (having a warning and all) - are there other use cases than testing? If not, should it perhaps also be called something else? (e.g. filesystemForTesting or something).

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 Reader interface, which hides the underlying files.

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 build_runner internals are allowed to bypass that abstraction and see the actual filesystem; in the next PRs they will also get access to information about what is different about the current generator's view.

Then they can be fast :) by building the base state once then applying the diff for the current generator on top.

});
}
{Encoding encoding = utf8}) =>
filesystem.writeAsString(id, contents);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you pass along the encoding as well?
Or not have options for enconding and always use utf8...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks.

This is from the generator-facing AssetWriter interface, so no choice about the signature :)

/// An implementation of [AssetReader] and [AssetWriter] with primed in-memory
/// assets.
///
/// TODO(davidmorgan): merge into `FileBasedReader` and `FileBasedWriter`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Memory based and file based seems at odds.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will end up as one class, with a different Filesystem implementation for in-memory and file based.

Once all the optional functionality is refactored from inheritance hierarchy to members like AssetFinder, it should be possible to get rid of AssetReaderState, get rid of delegation, and have one concrete AssetReaderWriter with all the interesting/optional functionality provided via those members.

The key thing allowing this refactoring is that the inheritance hierarchy / delegates are always built so they run in the same order--so for example caching always happens underneath per-generator visibility restriction. So actually that can just be "do I have visibility restriction, if so apply it, do I have a cache, if so use it" and so on :)

@override
Future writeAsBytes(AssetId id, List<int> bytes) async {
assets[id] = bytes;
assets[id] = Uint8List.fromList(bytes);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe bytes is Uint8List ? bytes : Uint8List.fromList(bytes);?
In general I think the interface should be changed, but probably not in this cl...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks.

This is generator-facing API so we can't easily change it.

Copy link
Contributor Author

@davidmorgan davidmorgan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks :)

Some quite detailed responses about where this is headed--hopefully they make sense, happy to elaborate / discuss as needed. Thanks!

/// Methods behave as the `dart:io` methods with the same names, with some
/// exceptions noted.
abstract interface class Filesystem {
Future<bool> exists(AssetId id);
Copy link
Contributor Author

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 give build_runner internals the choice.

///
/// Warning: this access to the filesystem bypasses reader functionality
/// such as read tracking, caching and visibility restriction.
Filesystem get filesystem;
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 Reader interface, which hides the underlying files.

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 build_runner internals are allowed to bypass that abstraction and see the actual filesystem; in the next PRs they will also get access to information about what is different about the current generator's view.

Then they can be fast :) by building the base state once then applying the diff for the current generator on top.

});
}
{Encoding encoding = utf8}) =>
filesystem.writeAsString(id, contents);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks.

This is from the generator-facing AssetWriter interface, so no choice about the signature :)

/// An implementation of [AssetReader] and [AssetWriter] with primed in-memory
/// assets.
///
/// TODO(davidmorgan): merge into `FileBasedReader` and `FileBasedWriter`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will end up as one class, with a different Filesystem implementation for in-memory and file based.

Once all the optional functionality is refactored from inheritance hierarchy to members like AssetFinder, it should be possible to get rid of AssetReaderState, get rid of delegation, and have one concrete AssetReaderWriter with all the interesting/optional functionality provided via those members.

The key thing allowing this refactoring is that the inheritance hierarchy / delegates are always built so they run in the same order--so for example caching always happens underneath per-generator visibility restriction. So actually that can just be "do I have visibility restriction, if so apply it, do I have a cache, if so use it" and so on :)

@override
Future writeAsBytes(AssetId id, List<int> bytes) async {
assets[id] = bytes;
assets[id] = Uint8List.fromList(bytes);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks.

This is generator-facing API so we can't easily change it.

@davidmorgan davidmorgan merged commit e6c9b0f into dart-lang:master Feb 19, 2025
75 checks passed
@davidmorgan davidmorgan deleted the reader-filesystem branch February 19, 2025 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants