From a242e5603a6b5a5f9d7bfe334d43865f39de966e Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 3 Dec 2022 15:57:51 +0100 Subject: [PATCH 01/14] Add option to delay file system writes --- _test_common/lib/in_memory_writer.dart | 3 + _test_common/lib/runner_asset_writer_spy.dart | 3 + build_runner/CHANGELOG.md | 6 +- .../lib/src/daemon/daemon_builder.dart | 1 + .../lib/src/entrypoint/base_command.dart | 7 +- build_runner/lib/src/entrypoint/build.dart | 1 + build_runner/lib/src/entrypoint/options.dart | 12 ++ build_runner/lib/src/generate/build.dart | 30 +++-- .../lib/src/generate/environment.dart | 36 ++++++ build_runner/lib/src/generate/watch_impl.dart | 20 ++-- .../lib/src/watcher/delete_writer.dart | 3 + build_runner/pubspec.yaml | 10 +- build_runner/test/entrypoint/run_test.dart | 11 ++ build_runner_core/CHANGELOG.md | 5 +- .../lib/src/asset/build_cache.dart | 3 + .../lib/src/asset/file_based.dart | 5 + build_runner_core/lib/src/asset/writer.dart | 111 ++++++++++++++++++ .../environment/overridable_environment.dart | 24 +++- .../lib/src/generate/build_definition.dart | 12 +- .../lib/src/generate/build_impl.dart | 9 ++ .../lib/src/generate/options.dart | 4 + build_runner_core/pubspec.yaml | 6 +- build_runner_core/test/asset/writer_test.dart | 92 +++++++++++++++ example/pubspec.yaml | 6 + 24 files changed, 389 insertions(+), 31 deletions(-) create mode 100644 build_runner/lib/src/generate/environment.dart create mode 100644 build_runner_core/test/asset/writer_test.dart diff --git a/_test_common/lib/in_memory_writer.dart b/_test_common/lib/in_memory_writer.dart index 148cb44fd..ab44190b6 100644 --- a/_test_common/lib/in_memory_writer.dart +++ b/_test_common/lib/in_memory_writer.dart @@ -38,4 +38,7 @@ class InMemoryRunnerAssetWriter extends InMemoryAssetWriter FakeWatcher.notifyWatchers( WatchEvent(ChangeType.REMOVE, p.absolute(id.package, id.path))); } + + @override + void onBuildComplete() {} } diff --git a/_test_common/lib/runner_asset_writer_spy.dart b/_test_common/lib/runner_asset_writer_spy.dart index 33ad297ba..77175a86e 100644 --- a/_test_common/lib/runner_asset_writer_spy.dart +++ b/_test_common/lib/runner_asset_writer_spy.dart @@ -20,4 +20,7 @@ class RunnerAssetWriterSpy extends AssetWriterSpy implements RunnerAssetWriter { _assetsDeleted.add(id); return _delegate.delete(id); } + + @override + FutureOr onBuildComplete() => _delegate.onBuildComplete(); } diff --git a/build_runner/CHANGELOG.md b/build_runner/CHANGELOG.md index a80ea6251..80144c379 100644 --- a/build_runner/CHANGELOG.md +++ b/build_runner/CHANGELOG.md @@ -1,4 +1,8 @@ -## 2.3.3-dev +## 2.4.0-dev + +- Add `--delay-writes` option to delay all writes to the end of a build. As no + intermediate build state is visible on the file system, this may improve + performance of third-party tools like the analysis server. ## 2.3.2 diff --git a/build_runner/lib/src/daemon/daemon_builder.dart b/build_runner/lib/src/daemon/daemon_builder.dart index 4af1c4610..b09ee5e05 100644 --- a/build_runner/lib/src/daemon/daemon_builder.dart +++ b/build_runner/lib/src/daemon/daemon_builder.dart @@ -220,6 +220,7 @@ class BuildRunnerDaemonBuilder implements DaemonBuilder { logSubscription, packageGraph: packageGraph, deleteFilesByDefault: daemonOptions.deleteFilesByDefault, + delayWrites: daemonOptions.delayAssetWrites, overrideBuildConfig: overrideBuildConfig, skipBuildScriptCheck: daemonOptions.skipBuildScriptCheck, enableLowResourcesMode: daemonOptions.enableLowResourcesMode, diff --git a/build_runner/lib/src/entrypoint/base_command.dart b/build_runner/lib/src/entrypoint/base_command.dart index 1dbcf626c..37b0fe001 100644 --- a/build_runner/lib/src/entrypoint/base_command.dart +++ b/build_runner/lib/src/entrypoint/base_command.dart @@ -97,7 +97,12 @@ abstract class BuildRunnerCommand extends Command { 'If multiple filters are applied then outputs matching any filter ' 'will be built (they do not need to match all filters).') ..addMultiOption(enableExperimentOption, - help: 'A list of dart language experiments to enable.'); + help: 'A list of dart language experiments to enable.') + ..addFlag( + delayWritesOption, + help: 'Delays all file system interactions until the end of a build, ' + 'potentially reducing load on tools like the analysis server.', + ); } /// Must be called inside [run] so that [argResults] is non-null. diff --git a/build_runner/lib/src/entrypoint/build.dart b/build_runner/lib/src/entrypoint/build.dart index d5b9153ae..705fbc435 100644 --- a/build_runner/lib/src/entrypoint/build.dart +++ b/build_runner/lib/src/entrypoint/build.dart @@ -47,6 +47,7 @@ class BuildCommand extends BuildRunnerCommand { trackPerformance: options.trackPerformance, skipBuildScriptCheck: options.skipBuildScriptCheck, logPerformanceDir: options.logPerformanceDir, + delayAssetWrites: options.delayAssetWrites, ); if (result.status == BuildStatus.success) { return ExitCode.success.code; diff --git a/build_runner/lib/src/entrypoint/options.dart b/build_runner/lib/src/entrypoint/options.dart index f78a844cc..ac9982043 100644 --- a/build_runner/lib/src/entrypoint/options.dart +++ b/build_runner/lib/src/entrypoint/options.dart @@ -33,6 +33,7 @@ const skipBuildScriptCheckOption = 'skip-build-script-check'; const symlinkOption = 'symlink'; const usePollingWatcherOption = 'use-polling-watcher'; const verboseOption = 'verbose'; +const delayWritesOption = 'delay-writes'; enum BuildUpdatesOption { none, liveReload } @@ -67,6 +68,9 @@ class SharedOptions { /// Enables performance tracking and the `/$perf` page. final bool trackPerformance; + /// Delay asset writes until the end of a build. + final bool delayAssetWrites; + /// A directory to log performance information to. String? logPerformanceDir; @@ -100,6 +104,7 @@ class SharedOptions { required this.isReleaseBuild, required this.logPerformanceDir, required this.enableExperiments, + required this.delayAssetWrites, }); SharedOptions.fromParsedArgs(ArgResults argResults, @@ -122,6 +127,7 @@ class SharedOptions { isReleaseBuild: argResults[releaseOption] as bool, logPerformanceDir: argResults[logPerformanceOption] as String?, enableExperiments: argResults[enableExperimentOption] as List, + delayAssetWrites: argResults[delayWritesOption] as bool, ); } @@ -147,6 +153,7 @@ class DaemonOptions extends WatchOptions { required String? logPerformanceDir, required bool usePollingWatcher, required List enableExperiments, + required super.delayAssetWrites, }) : super._( buildFilters: buildFilters, deleteFilesByDefault: deleteFilesByDefault, @@ -202,6 +209,7 @@ class DaemonOptions extends WatchOptions { logPerformanceDir: argResults[logPerformanceOption] as String?, usePollingWatcher: argResults[usePollingWatcherOption] as bool, enableExperiments: argResults[enableExperimentOption] as List, + delayAssetWrites: argResults[delayWritesOption] as bool, ); } } @@ -229,6 +237,7 @@ class WatchOptions extends SharedOptions { required bool isReleaseBuild, required String? logPerformanceDir, required List enableExperiments, + required super.delayAssetWrites, }) : super._( buildFilters: buildFilters, deleteFilesByDefault: deleteFilesByDefault, @@ -266,6 +275,7 @@ class WatchOptions extends SharedOptions { logPerformanceDir: argResults[logPerformanceOption] as String?, usePollingWatcher: argResults[usePollingWatcherOption] as bool, enableExperiments: argResults[enableExperimentOption] as List, + delayAssetWrites: argResults[delayWritesOption] as bool, ); } @@ -295,6 +305,7 @@ class ServeOptions extends WatchOptions { required String? logPerformanceDir, required bool usePollingWatcher, required List enableExperiments, + required super.delayAssetWrites, }) : super._( buildFilters: buildFilters, deleteFilesByDefault: deleteFilesByDefault, @@ -381,6 +392,7 @@ class ServeOptions extends WatchOptions { logPerformanceDir: argResults[logPerformanceOption] as String?, usePollingWatcher: argResults[usePollingWatcherOption] as bool, enableExperiments: argResults[enableExperimentOption] as List, + delayAssetWrites: argResults[delayWritesOption] as bool, ); } } diff --git a/build_runner/lib/src/generate/build.dart b/build_runner/lib/src/generate/build.dart index 93391c439..e0286fda2 100644 --- a/build_runner/lib/src/generate/build.dart +++ b/build_runner/lib/src/generate/build.dart @@ -12,9 +12,9 @@ import 'package:logging/logging.dart'; import 'package:shelf/shelf.dart'; import 'package:watcher/watcher.dart'; -import '../logging/std_io_logging.dart'; import '../package_graph/build_config_overrides.dart'; import '../server/server.dart'; +import 'environment.dart'; import 'watch_impl.dart' as watch_impl; /// Runs all of the BuilderApplications in [builders] once. @@ -68,7 +68,8 @@ Future build(List builders, bool? isReleaseBuild, Map>? builderConfigOverrides, String? logPerformanceDir, - Set? buildFilters}) async { + Set? buildFilters, + bool? delayAssetWrites}) async { builderConfigOverrides ??= const {}; buildDirs ??= {}; buildFilters ??= {}; @@ -80,20 +81,24 @@ Future build(List builders, skipBuildScriptCheck ??= false; trackPerformance ??= false; verbose ??= false; - var environment = OverrideableEnvironment( - IOEnvironment( - packageGraph, - assumeTty: assumeTty, - outputSymlinksOnly: outputSymlinksOnly, - ), - reader: reader, - writer: writer, - onLog: onLog ?? stdIOLogListener(assumeTty: assumeTty, verbose: verbose)); + + final environment = createEnvironment( + packageGraph: packageGraph, + assumeTty: assumeTty, + outputSymlinksOnly: outputSymlinksOnly, + reader: reader, + writer: writer, + delayAssetWrites: delayAssetWrites, + onLog: onLog, + verbose: verbose, + ); + var logSubscription = LogSubscription(environment, verbose: verbose, logLevel: logLevel); var options = await BuildOptions.create( logSubscription, deleteFilesByDefault: deleteFilesByDefault, + delayWrites: delayAssetWrites == true, packageGraph: packageGraph, skipBuildScriptCheck: skipBuildScriptCheck, overrideBuildConfig: await findBuildConfigOverrides( @@ -165,7 +170,8 @@ Future watch(List builders, bool? isReleaseBuild, Map>? builderConfigOverrides, String? logPerformanceDir, - Set? buildFilters}) => + Set? buildFilters, + bool? delayAssetWrites}) => watch_impl.watch( builders, assumeTty: assumeTty, diff --git a/build_runner/lib/src/generate/environment.dart b/build_runner/lib/src/generate/environment.dart new file mode 100644 index 000000000..897230bce --- /dev/null +++ b/build_runner/lib/src/generate/environment.dart @@ -0,0 +1,36 @@ +import 'package:build_runner_core/build_runner_core.dart'; +import 'package:logging/logging.dart'; + +import '../logging/std_io_logging.dart'; + +BuildEnvironment createEnvironment({ + required PackageGraph packageGraph, + required bool? assumeTty, + required bool outputSymlinksOnly, + required RunnerAssetReader? reader, + required RunnerAssetWriter? writer, + required bool? delayAssetWrites, + required void Function(LogRecord)? onLog, + required bool verbose, +}) { + BuildEnvironment environment = IOEnvironment( + packageGraph, + assumeTty: assumeTty, + outputSymlinksOnly: outputSymlinksOnly, + ); + reader ??= environment.reader; + writer ??= environment.writer; + + if (delayAssetWrites == true) { + final delayed = writer = DelayedAssetWriter(writer); + reader = delayed.reader(reader, packageGraph.root.name); + } + + environment = environment.change( + reader: reader, + writer: writer, + onLog: onLog ?? stdIOLogListener(assumeTty: assumeTty, verbose: verbose), + ); + + return environment; +} diff --git a/build_runner/lib/src/generate/watch_impl.dart b/build_runner/lib/src/generate/watch_impl.dart index a85d307e2..37ea1bb78 100644 --- a/build_runner/lib/src/generate/watch_impl.dart +++ b/build_runner/lib/src/generate/watch_impl.dart @@ -24,8 +24,8 @@ import 'package:logging/logging.dart'; import 'package:stream_transform/stream_transform.dart'; import 'package:watcher/watcher.dart'; -import '../logging/std_io_logging.dart'; import '../server/server.dart'; +import 'environment.dart'; import 'terminator.dart'; final _logger = Logger('Watch'); @@ -55,6 +55,7 @@ Future watch( bool? isReleaseBuild, String? logPerformanceDir, Set? buildFilters, + bool? delayAssetWrites, }) async { builderConfigOverrides ??= const {}; buildDirs ??= {}; @@ -68,12 +69,16 @@ Future watch( trackPerformance ??= false; verbose ??= false; - var environment = OverrideableEnvironment( - IOEnvironment(packageGraph, - assumeTty: assumeTty, outputSymlinksOnly: outputSymlinksOnly), - reader: reader, - writer: writer, - onLog: onLog ?? stdIOLogListener(assumeTty: assumeTty, verbose: verbose)); + final environment = createEnvironment( + packageGraph: packageGraph, + assumeTty: assumeTty, + outputSymlinksOnly: outputSymlinksOnly, + reader: reader, + writer: writer, + delayAssetWrites: delayAssetWrites, + onLog: onLog, + verbose: verbose, + ); var logSubscription = LogSubscription(environment, verbose: verbose, logLevel: logLevel); overrideBuildConfig ??= await findBuildConfigOverrides( @@ -82,6 +87,7 @@ Future watch( var options = await BuildOptions.create( logSubscription, deleteFilesByDefault: deleteFilesByDefault, + delayWrites: delayAssetWrites == true, packageGraph: packageGraph, overrideBuildConfig: overrideBuildConfig, debounceDelay: debounceDelay, diff --git a/build_runner/lib/src/watcher/delete_writer.dart b/build_runner/lib/src/watcher/delete_writer.dart index e8092f5dc..982a8c4dd 100644 --- a/build_runner/lib/src/watcher/delete_writer.dart +++ b/build_runner/lib/src/watcher/delete_writer.dart @@ -29,4 +29,7 @@ class OnDeleteWriter implements RunnerAssetWriter { Future writeAsString(AssetId id, String contents, {Encoding encoding = utf8}) => _writer.writeAsString(id, contents, encoding: encoding); + + @override + FutureOr onBuildComplete() => _writer.onBuildComplete(); } diff --git a/build_runner/pubspec.yaml b/build_runner/pubspec.yaml index 7620c7fbe..eae15baa2 100644 --- a/build_runner/pubspec.yaml +++ b/build_runner/pubspec.yaml @@ -1,10 +1,10 @@ name: build_runner -version: 2.3.3-dev +version: 2.4.0-dev description: A build system for Dart code generation and modular compilation. repository: https://github.com/dart-lang/build/tree/master/build_runner environment: - sdk: ">=2.14.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" platforms: linux: @@ -19,7 +19,7 @@ dependencies: build_config: ">=1.1.0 <1.2.0" build_daemon: ^3.1.0 build_resolvers: ^2.0.0 - build_runner_core: ^7.2.0 + build_runner_core: ^8.0.0-dev code_builder: ^4.2.0 collection: ^1.15.0 crypto: ^3.0.0 @@ -57,3 +57,7 @@ dev_dependencies: test_process: ^2.0.0 _test_common: path: ../_test_common + +dependency_overrides: + build_runner_core: + path: ../build_runner_core/ diff --git a/build_runner/test/entrypoint/run_test.dart b/build_runner/test/entrypoint/run_test.dart index 0a32527e0..e272501bc 100644 --- a/build_runner/test/entrypoint/run_test.dart +++ b/build_runner/test/entrypoint/run_test.dart @@ -105,6 +105,16 @@ main() { }); } + void testBasicBuildCommandWithDelayedWrites(String command) { + test('is supported by the $command command', () async { + var args = ['build_runner', command, 'web', '--delayed-writes']; + expect(await runSingleBuild(command, args), ExitCode.success.code); + expectOutput('web/main.dart.js', exists: true); + expectOutput('test/hello_test.dart.browser_test.dart.js', + exists: false); + }); + } + void testBuildCommandWithOutput(String command) { test('works with -o and the $command command', () async { var outputDirName = 'foo'; @@ -126,6 +136,7 @@ main() { for (var command in ['build', 'serve', 'watch']) { testBasicBuildCommand(command); + testBasicBuildCommandWithDelayedWrites(command); testBuildCommandWithOutput(command); } diff --git a/build_runner_core/CHANGELOG.md b/build_runner_core/CHANGELOG.md index eeb79f94f..3ebb49add 100644 --- a/build_runner_core/CHANGELOG.md +++ b/build_runner_core/CHANGELOG.md @@ -1,5 +1,8 @@ -## 7.2.8-dev +## 8.0.0-dev +- __Breaking__: Add `onBuildComplete` to `RunnerAssetWriter`. +- Add `DelayedAssetWriter`, a writer implementation delaying all writes until + `onBuildComplete` is called. - Raise the minimum SDK constraint to 2.18. ## 7.2.7 diff --git a/build_runner_core/lib/src/asset/build_cache.dart b/build_runner_core/lib/src/asset/build_cache.dart index b052ba374..b3896e08e 100644 --- a/build_runner_core/lib/src/asset/build_cache.dart +++ b/build_runner_core/lib/src/asset/build_cache.dart @@ -84,6 +84,9 @@ class BuildCacheWriter implements RunnerAssetWriter { @override Future delete(AssetId id) => _delegate.delete(_cacheLocation(id, _assetGraph, _rootPackage)); + + @override + FutureOr onBuildComplete() => _delegate.onBuildComplete(); } AssetId _cacheLocation(AssetId id, AssetGraph assetGraph, String rootPackage) { diff --git a/build_runner_core/lib/src/asset/file_based.dart b/build_runner_core/lib/src/asset/file_based.dart index 92ba3b5db..4f36f528d 100644 --- a/build_runner_core/lib/src/asset/file_based.dart +++ b/build_runner_core/lib/src/asset/file_based.dart @@ -107,6 +107,11 @@ class FileBasedAssetWriter implements RunnerAssetWriter { } }); } + + @override + void onBuildComplete() { + // Nothing to do here + } } /// Returns the path to [id] for a given [packageGraph]. diff --git a/build_runner_core/lib/src/asset/writer.dart b/build_runner_core/lib/src/asset/writer.dart index f4769eada..7f0164391 100644 --- a/build_runner_core/lib/src/asset/writer.dart +++ b/build_runner_core/lib/src/asset/writer.dart @@ -3,12 +3,123 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'package:build/build.dart'; +import 'package:glob/glob.dart'; + +import 'reader.dart'; @Deprecated('No longer used') typedef OnDelete = void Function(AssetId id); abstract class RunnerAssetWriter implements AssetWriter { Future delete(AssetId id); + + /// Potentially runs pending work that needs to happen just before the build + /// completes, after all other interactions with this [AssetWriter] have + /// already been completed. + FutureOr onBuildComplete() {} +} + +/// A [RunnerAssetWriter] implementation that performs all writes at once in +/// [onBuildComplete]. +/// +/// This write behavior may reduce load on external tools doing work on each +/// file system change (like the analysis server). +class DelayedAssetWriter implements RunnerAssetWriter { + final RunnerAssetWriter _delegate; + + final Map?> overlay = {}; + final List _work = []; + + DelayedAssetWriter(this._delegate); + + /// Obtain a [RunnerAssetReader] capable of reading pending writes of this + /// writer (before they are persisted with [onBuildComplete]). + RunnerAssetReader reader(RunnerAssetReader delegate, String rootPackage) { + return _DelayAwareReader(delegate, this, rootPackage); + } + + @override + Future delete(AssetId id) async { + overlay[id] = null; + _work.add(() => _delegate.delete(id)); + } + + @override + Future onBuildComplete() async { + final todos = _work.toList(); + _work.clear(); + + await Future.wait([for (final unit in todos) unit()]); + await _delegate.onBuildComplete(); + } + + @override + Future writeAsBytes(AssetId id, List bytes) async { + overlay[id] = bytes; + _work.add(() => _delegate.writeAsBytes(id, bytes)); + } + + @override + Future writeAsString(AssetId id, String contents, + {Encoding encoding = utf8}) { + final encoded = encoding.encode(contents); + return writeAsBytes(id, encoded); + } +} + +class _DelayAwareReader extends AssetReader implements RunnerAssetReader { + final RunnerAssetReader _delegate; + final DelayedAssetWriter _delayed; + final String _rootPackage; + + _DelayAwareReader(this._delegate, this._delayed, this._rootPackage); + + @override + Future canRead(AssetId id) async { + if (_delayed.overlay.containsKey(id)) { + return _delayed.overlay[id] != null; + } + + return await _delegate.canRead(id); + } + + @override + Stream findAssets(Glob glob, {String? package}) async* { + package ??= _rootPackage; + + await for (final assetId in _delegate.findAssets(glob, package: package)) { + if (!_delayed.overlay.containsKey(assetId)) { + // Overlay ids are reported afterwards + yield assetId; + } + } + + for (final entry in _delayed.overlay.entries) { + if (entry.key.package == package && entry.value != null) { + yield entry.key; + } + } + } + + @override + Future> readAsBytes(AssetId id) async { + if (_delayed.overlay.containsKey(id)) { + final fromOverlay = _delayed.overlay[id]; + if (fromOverlay == null) { + throw AssetNotFoundException(id); + } + + return fromOverlay; + } + + return await _delegate.readAsBytes(id); + } + + @override + Future readAsString(AssetId id, {Encoding encoding = utf8}) async { + return encoding.decode(await readAsBytes(id)); + } } diff --git a/build_runner_core/lib/src/environment/overridable_environment.dart b/build_runner_core/lib/src/environment/overridable_environment.dart index 53b1add3d..b12bc01af 100644 --- a/build_runner_core/lib/src/environment/overridable_environment.dart +++ b/build_runner_core/lib/src/environment/overridable_environment.dart @@ -14,6 +14,9 @@ import '../generate/build_result.dart'; import '../generate/finalized_assets_view.dart'; import 'build_environment.dart'; +typedef BuildFinalizer = Future Function( + BuildResult, FinalizedAssetsView, AssetReader, Set); + /// A [BuildEnvironment] which can have individual features overridden. class OverrideableEnvironment implements BuildEnvironment { final BuildEnvironment _default; @@ -32,9 +35,7 @@ class OverrideableEnvironment implements BuildEnvironment { RunnerAssetReader? reader, RunnerAssetWriter? writer, void Function(LogRecord)? onLog, - Future Function( - BuildResult, FinalizedAssetsView, AssetReader, Set)? - finalizeBuild, + BuildFinalizer? finalizeBuild, }) : _reader = reader, _writer = writer, _onLog = onLog, @@ -62,3 +63,20 @@ class OverrideableEnvironment implements BuildEnvironment { Future prompt(String message, List choices) => _default.prompt(message, choices); } + +extension OverrideEnvironment on BuildEnvironment { + BuildEnvironment change({ + RunnerAssetReader? reader, + RunnerAssetWriter? writer, + void Function(LogRecord)? onLog, + BuildFinalizer? finalizeBuild, + }) { + return OverrideableEnvironment( + this, + reader: reader, + writer: writer, + onLog: onLog, + finalizeBuild: finalizeBuild, + ); + } +} diff --git a/build_runner_core/lib/src/generate/build_definition.dart b/build_runner_core/lib/src/generate/build_definition.dart index de04d05e3..786f3fa14 100644 --- a/build_runner_core/lib/src/generate/build_definition.dart +++ b/build_runner_core/lib/src/generate/build_definition.dart @@ -543,8 +543,16 @@ class _Loader { var done = false; while (!done) { try { - var choice = await _environment.prompt('Delete these files?', - ['Delete', 'Cancel build', 'List conflicts']); + int choice; + + if (_options.delayWrites) { + choice = await _environment.prompt('Overwrite these files?', + ['Overwrite', 'Cancel build', 'List conflicts']); + } else { + choice = await _environment.prompt('Delete these files?', + ['Delete', 'Cancel build', 'List conflicts']); + } + switch (choice) { case 0: _logger.info('Deleting files...'); diff --git a/build_runner_core/lib/src/generate/build_impl.dart b/build_runner_core/lib/src/generate/build_impl.dart index 925742448..019b8046e 100644 --- a/build_runner_core/lib/src/generate/build_impl.dart +++ b/build_runner_core/lib/src/generate/build_impl.dart @@ -246,6 +246,15 @@ class _SingleBuild { } } await _resourceManager.disposeAll(); + + try { + await _environment.writer.onBuildComplete(); + } catch (e, s) { + _logger.severe('Assets could not be written', e, s); + result = BuildResult(BuildStatus.failure, result.outputs, + performance: result.performance); + } + result = await _environment.finalizeBuild( result, FinalizedAssetsView(_assetGraph, _packageGraph, optionalOutputTracker), diff --git a/build_runner_core/lib/src/generate/options.dart b/build_runner_core/lib/src/generate/options.dart index b49c8ef52..692c65836 100644 --- a/build_runner_core/lib/src/generate/options.dart +++ b/build_runner_core/lib/src/generate/options.dart @@ -134,6 +134,7 @@ class BuildFilter { /// Manages setting up consistent defaults for all options and build modes. class BuildOptions { final bool deleteFilesByDefault; + final bool delayWrites; final bool enableLowResourcesMode; final StreamSubscription logListener; @@ -154,6 +155,7 @@ class BuildOptions { BuildOptions._({ required this.debounceDelay, required this.deleteFilesByDefault, + required this.delayWrites, required this.enableLowResourcesMode, required this.logListener, required this.packageGraph, @@ -172,6 +174,7 @@ class BuildOptions { LogSubscription logSubscription, { Duration debounceDelay = const Duration(milliseconds: 250), bool deleteFilesByDefault = false, + bool delayWrites = false, bool enableLowResourcesMode = false, required PackageGraph packageGraph, Map overrideBuildConfig = const {}, @@ -213,6 +216,7 @@ feature, you may need to run `dart run build_runner clean` and then rebuild. return BuildOptions._( debounceDelay: debounceDelay, deleteFilesByDefault: deleteFilesByDefault, + delayWrites: delayWrites, enableLowResourcesMode: enableLowResourcesMode, logListener: logSubscription.logListener, packageGraph: packageGraph, diff --git a/build_runner_core/pubspec.yaml b/build_runner_core/pubspec.yaml index 8b992be2c..d6cbc775d 100644 --- a/build_runner_core/pubspec.yaml +++ b/build_runner_core/pubspec.yaml @@ -1,5 +1,5 @@ name: build_runner_core -version: 7.2.8-dev +version: 8.0.0-dev description: Core tools to organize the structure of a build and run Builders. repository: https://github.com/dart-lang/build/tree/master/build_runner_core @@ -42,3 +42,7 @@ dev_dependencies: test_process: ^2.0.0 _test_common: path: ../_test_common + +dependency_overrides: + build_runner: + path: ../build_runner diff --git a/build_runner_core/test/asset/writer_test.dart b/build_runner_core/test/asset/writer_test.dart new file mode 100644 index 000000000..1e5db8687 --- /dev/null +++ b/build_runner_core/test/asset/writer_test.dart @@ -0,0 +1,92 @@ +import 'dart:convert'; + +import 'package:_test_common/common.dart'; +import 'package:build/build.dart'; +import 'package:build_runner_core/build_runner_core.dart'; +import 'package:test/test.dart'; + +void main() { + late InMemoryRunnerAssetReader underlyingReader; + late InMemoryRunnerAssetWriter underlyingWriter; + const rootPackage = 'a'; + + setUp(() { + underlyingWriter = InMemoryRunnerAssetWriter(); + underlyingReader = InMemoryRunnerAssetReader.shareAssetCache( + underlyingWriter.assets, + rootPackage: rootPackage); + }); + + group('DelayedAssetWriter', () { + test('delays writes to end', () async { + final writer = DelayedAssetWriter(underlyingWriter); + + final a = makeAssetId('a|lib/a.dart'); + final b = makeAssetId('a|lib/b.dart'); + + underlyingWriter.assets[b] = []; + + await writer.writeAsString(a, 'a'); + await writer.delete(b); + expect(underlyingWriter.assets, {b: []}, + reason: 'Should not have applied writes yet'); + + await writer.onBuildComplete(); + expect(underlyingWriter.assets, {a: decodedMatches('a')}); + }); + + test('does not write twice', () async { + final writer = DelayedAssetWriter(underlyingWriter); + final asset = makeAssetId('a|lib/a.dart'); + + await writer.writeAsString(asset, ''); + await writer.onBuildComplete(); + + underlyingWriter.assets.remove(asset); + await writer.onBuildComplete(); // New build, no write to asset + + expect(underlyingWriter.assets, isEmpty); + }); + + group('has consistent reader', () { + test('reading unmodified sources', () async { + final asset = makeAssetId('a|lib/a.dart'); + underlyingWriter.assets[asset] = utf8.encode('foo bar'); + + final writer = DelayedAssetWriter(underlyingWriter); + final reader = writer.reader(underlyingReader, rootPackage); + + expect(await reader.canRead(asset), isTrue); + expect(await reader.readAsBytes(asset), decodedMatches('foo bar')); + expect(await reader.readAsString(asset), 'foo bar'); + }); + + test('unable to read deleted assets', () async { + final asset = makeAssetId('a|lib/a.dart'); + underlyingWriter.assets[asset] = [1, 2, 3]; + + final writer = DelayedAssetWriter(underlyingWriter); + final reader = writer.reader(underlyingReader, rootPackage); + + await writer.delete(asset); + expect(await reader.canRead(asset), isFalse); + expect( + reader.readAsBytes(asset), throwsA(isA())); + expect( + reader.readAsString(asset), throwsA(isA())); + }); + + test('reading changed data', () async { + final asset = makeAssetId('a|lib/a.dart'); + underlyingWriter.assets[asset] = [1, 2, 3]; + + final writer = DelayedAssetWriter(underlyingWriter); + final reader = writer.reader(underlyingReader, rootPackage); + await writer.writeAsString(asset, 'contents'); + + expect(await reader.readAsString(asset), 'contents'); + expect(await reader.readAsBytes(asset), decodedMatches('contents')); + }); + }); + }); +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 93a92c11e..7cfe6648c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -13,3 +13,9 @@ dev_dependencies: build_runner: ^2.0.0 build_web_compilers: ^3.0.0 lints: '>=1.0.0 <3.0.0' + +dependency_overrides: + build_runner: + path: ../build_runner + build_runner_core: + path: ../build_runner_core \ No newline at end of file From a3255078f749e700e45aee49c6098ae800214303 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 3 Dec 2022 16:06:34 +0100 Subject: [PATCH 02/14] Test findAssets --- build_runner_core/test/asset/writer_test.dart | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/build_runner_core/test/asset/writer_test.dart b/build_runner_core/test/asset/writer_test.dart index 1e5db8687..62e0cfc09 100644 --- a/build_runner_core/test/asset/writer_test.dart +++ b/build_runner_core/test/asset/writer_test.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:_test_common/common.dart'; import 'package:build/build.dart'; import 'package:build_runner_core/build_runner_core.dart'; +import 'package:glob/glob.dart'; import 'package:test/test.dart'; void main() { @@ -87,6 +88,24 @@ void main() { expect(await reader.readAsString(asset), 'contents'); expect(await reader.readAsBytes(asset), decodedMatches('contents')); }); + + test('finding right assets', () async { + final a = makeAssetId('a|lib/a.dart'); + final b = makeAssetId('a|lib/b.dart'); + final glob = Glob('**'); + underlyingWriter.assets[a] = [1, 2, 3]; + + final writer = DelayedAssetWriter(underlyingWriter); + final reader = writer.reader(underlyingReader, rootPackage); + + expect(await reader.findAssets(glob).toSet(), {a}); + await writer.writeAsString(b, 'b'); + expect(await reader.findAssets(glob).toSet(), {a, b}); + await writer.delete(a); + expect(await reader.findAssets(glob).toSet(), {b}); + await writer.delete(b); + expect(await reader.findAssets(glob).toSet(), isEmpty); + }); }); }); } From 6bf0185d5d3be573ab2909118d72183cecc68e2d Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 3 Dec 2022 16:10:22 +0100 Subject: [PATCH 03/14] Fix analysis warning --- build_runner/test/server/serve_integration_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_runner/test/server/serve_integration_test.dart b/build_runner/test/server/serve_integration_test.dart index 4093ce0dc..347016ad0 100644 --- a/build_runner/test/server/serve_integration_test.dart +++ b/build_runner/test/server/serve_integration_test.dart @@ -57,7 +57,7 @@ void main() { logLevel: Level.ALL, onLog: (record) => printOnFailure('[${record.level}] ' '${record.loggerName}: ${record.message}'), - directoryWatcherFactory: (path) => FakeWatcher(path), + directoryWatcherFactory: FakeWatcher.new, terminateEventStream: terminateController.stream, skipBuildScriptCheck: true, ); From 0eb6ec1672447dab0fb2f1a02982d272002989b0 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 7 Dec 2022 12:13:01 +0100 Subject: [PATCH 04/14] Review feedback --- .../lib/src/daemon/daemon_builder.dart | 20 ++++++---- .../lib/src/entrypoint/base_command.dart | 1 + build_runner/lib/src/generate/build.dart | 3 +- .../lib/src/generate/environment.dart | 2 +- build_runner/lib/src/generate/watch_impl.dart | 3 +- build_runner_core/lib/src/asset/writer.dart | 24 ++++++++---- .../lib/src/generate/build_definition.dart | 11 +----- .../lib/src/generate/build_impl.dart | 7 +++- .../lib/src/generate/options.dart | 4 -- build_runner_core/test/asset/writer_test.dart | 39 ++++++++++++++++++- 10 files changed, 79 insertions(+), 35 deletions(-) diff --git a/build_runner/lib/src/daemon/daemon_builder.dart b/build_runner/lib/src/daemon/daemon_builder.dart index b09ee5e05..aa7a13de7 100644 --- a/build_runner/lib/src/daemon/daemon_builder.dart +++ b/build_runner/lib/src/daemon/daemon_builder.dart @@ -12,6 +12,7 @@ import 'package:build_daemon/data/build_status.dart'; import 'package:build_daemon/data/build_target.dart' hide OutputLocation; import 'package:build_daemon/data/server_log.dart'; import 'package:build_runner/src/entrypoint/options.dart'; +import 'package:build_runner/src/generate/environment.dart'; import 'package:build_runner/src/package_graph/build_config_overrides.dart'; import 'package:build_runner/src/watcher/asset_change.dart'; import 'package:build_runner/src/watcher/change_filter.dart'; @@ -199,13 +200,17 @@ class BuildRunnerDaemonBuilder implements DaemonBuilder { var expectedDeletes = {}; var outputStreamController = StreamController(); - var environment = OverrideableEnvironment( - IOEnvironment(packageGraph, - outputSymlinksOnly: daemonOptions.outputSymlinksOnly), - onLog: (record) { - outputStreamController.add(ServerLog.fromLogRecord(record)); - }); - + var environment = createDefaultEnvironment( + packageGraph: packageGraph, + assumeTty: null, + outputSymlinksOnly: daemonOptions.outputSymlinksOnly, + reader: null, + writer: null, + delayAssetWrites: daemonOptions.delayAssetWrites, + onLog: (record) => + outputStreamController.add(ServerLog.fromLogRecord(record)), + verbose: true, + ); var daemonEnvironment = OverrideableEnvironment(environment, writer: OnDeleteWriter(environment.writer, expectedDeletes.add)); @@ -220,7 +225,6 @@ class BuildRunnerDaemonBuilder implements DaemonBuilder { logSubscription, packageGraph: packageGraph, deleteFilesByDefault: daemonOptions.deleteFilesByDefault, - delayWrites: daemonOptions.delayAssetWrites, overrideBuildConfig: overrideBuildConfig, skipBuildScriptCheck: daemonOptions.skipBuildScriptCheck, enableLowResourcesMode: daemonOptions.enableLowResourcesMode, diff --git a/build_runner/lib/src/entrypoint/base_command.dart b/build_runner/lib/src/entrypoint/base_command.dart index 37b0fe001..6ec5c290a 100644 --- a/build_runner/lib/src/entrypoint/base_command.dart +++ b/build_runner/lib/src/entrypoint/base_command.dart @@ -102,6 +102,7 @@ abstract class BuildRunnerCommand extends Command { delayWritesOption, help: 'Delays all file system interactions until the end of a build, ' 'potentially reducing load on tools like the analysis server.', + defaultsTo: true, ); } diff --git a/build_runner/lib/src/generate/build.dart b/build_runner/lib/src/generate/build.dart index e0286fda2..b92766120 100644 --- a/build_runner/lib/src/generate/build.dart +++ b/build_runner/lib/src/generate/build.dart @@ -82,7 +82,7 @@ Future build(List builders, trackPerformance ??= false; verbose ??= false; - final environment = createEnvironment( + final environment = createDefaultEnvironment( packageGraph: packageGraph, assumeTty: assumeTty, outputSymlinksOnly: outputSymlinksOnly, @@ -98,7 +98,6 @@ Future build(List builders, var options = await BuildOptions.create( logSubscription, deleteFilesByDefault: deleteFilesByDefault, - delayWrites: delayAssetWrites == true, packageGraph: packageGraph, skipBuildScriptCheck: skipBuildScriptCheck, overrideBuildConfig: await findBuildConfigOverrides( diff --git a/build_runner/lib/src/generate/environment.dart b/build_runner/lib/src/generate/environment.dart index 897230bce..2b9a1e41b 100644 --- a/build_runner/lib/src/generate/environment.dart +++ b/build_runner/lib/src/generate/environment.dart @@ -3,7 +3,7 @@ import 'package:logging/logging.dart'; import '../logging/std_io_logging.dart'; -BuildEnvironment createEnvironment({ +BuildEnvironment createDefaultEnvironment({ required PackageGraph packageGraph, required bool? assumeTty, required bool outputSymlinksOnly, diff --git a/build_runner/lib/src/generate/watch_impl.dart b/build_runner/lib/src/generate/watch_impl.dart index 37ea1bb78..83933098b 100644 --- a/build_runner/lib/src/generate/watch_impl.dart +++ b/build_runner/lib/src/generate/watch_impl.dart @@ -69,7 +69,7 @@ Future watch( trackPerformance ??= false; verbose ??= false; - final environment = createEnvironment( + final environment = createDefaultEnvironment( packageGraph: packageGraph, assumeTty: assumeTty, outputSymlinksOnly: outputSymlinksOnly, @@ -87,7 +87,6 @@ Future watch( var options = await BuildOptions.create( logSubscription, deleteFilesByDefault: deleteFilesByDefault, - delayWrites: delayAssetWrites == true, packageGraph: packageGraph, overrideBuildConfig: overrideBuildConfig, debounceDelay: debounceDelay, diff --git a/build_runner_core/lib/src/asset/writer.dart b/build_runner_core/lib/src/asset/writer.dart index 7f0164391..541140ea1 100644 --- a/build_runner_core/lib/src/asset/writer.dart +++ b/build_runner_core/lib/src/asset/writer.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'package:build/build.dart'; import 'package:glob/glob.dart'; +import 'package:meta/meta.dart'; import 'reader.dart'; @@ -19,7 +20,8 @@ abstract class RunnerAssetWriter implements AssetWriter { /// Potentially runs pending work that needs to happen just before the build /// completes, after all other interactions with this [AssetWriter] have /// already been completed. - FutureOr onBuildComplete() {} + @mustCallSuper + FutureOr onBuildComplete(); } /// A [RunnerAssetWriter] implementation that performs all writes at once in @@ -30,8 +32,12 @@ abstract class RunnerAssetWriter implements AssetWriter { class DelayedAssetWriter implements RunnerAssetWriter { final RunnerAssetWriter _delegate; + /// A map of asset ids to content that has been written with the [AssetWriter] + /// API but not yet been flushed to the underlying delegate (in [onBuildComplete]). + /// + /// The values are non-null if the contents of the file have been overwritten, + /// or null if the asset has been deleted. final Map?> overlay = {}; - final List _work = []; DelayedAssetWriter(this._delegate); @@ -44,22 +50,26 @@ class DelayedAssetWriter implements RunnerAssetWriter { @override Future delete(AssetId id) async { overlay[id] = null; - _work.add(() => _delegate.delete(id)); } @override Future onBuildComplete() async { - final todos = _work.toList(); - _work.clear(); + await Future.wait(overlay.entries.map((entry) { + final value = entry.value; + if (value == null) { + return _delegate.delete(entry.key); + } else { + return _delegate.writeAsBytes(entry.key, value); + } + })); - await Future.wait([for (final unit in todos) unit()]); await _delegate.onBuildComplete(); + overlay.clear(); } @override Future writeAsBytes(AssetId id, List bytes) async { overlay[id] = bytes; - _work.add(() => _delegate.writeAsBytes(id, bytes)); } @override diff --git a/build_runner_core/lib/src/generate/build_definition.dart b/build_runner_core/lib/src/generate/build_definition.dart index 786f3fa14..fad02a8a1 100644 --- a/build_runner_core/lib/src/generate/build_definition.dart +++ b/build_runner_core/lib/src/generate/build_definition.dart @@ -543,15 +543,8 @@ class _Loader { var done = false; while (!done) { try { - int choice; - - if (_options.delayWrites) { - choice = await _environment.prompt('Overwrite these files?', - ['Overwrite', 'Cancel build', 'List conflicts']); - } else { - choice = await _environment.prompt('Delete these files?', - ['Delete', 'Cancel build', 'List conflicts']); - } + final choice = await _environment.prompt('Delete these files?', + ['Delete', 'Cancel build', 'List conflicts']); switch (choice) { case 0: diff --git a/build_runner_core/lib/src/generate/build_impl.dart b/build_runner_core/lib/src/generate/build_impl.dart index 019b8046e..f221caec2 100644 --- a/build_runner_core/lib/src/generate/build_impl.dart +++ b/build_runner_core/lib/src/generate/build_impl.dart @@ -250,7 +250,12 @@ class _SingleBuild { try { await _environment.writer.onBuildComplete(); } catch (e, s) { - _logger.severe('Assets could not be written', e, s); + _logger.severe( + 'Could not complete writes for this build.\n' + 'The current state of the file system may not be valid.', + e, + s, + ); result = BuildResult(BuildStatus.failure, result.outputs, performance: result.performance); } diff --git a/build_runner_core/lib/src/generate/options.dart b/build_runner_core/lib/src/generate/options.dart index 692c65836..b49c8ef52 100644 --- a/build_runner_core/lib/src/generate/options.dart +++ b/build_runner_core/lib/src/generate/options.dart @@ -134,7 +134,6 @@ class BuildFilter { /// Manages setting up consistent defaults for all options and build modes. class BuildOptions { final bool deleteFilesByDefault; - final bool delayWrites; final bool enableLowResourcesMode; final StreamSubscription logListener; @@ -155,7 +154,6 @@ class BuildOptions { BuildOptions._({ required this.debounceDelay, required this.deleteFilesByDefault, - required this.delayWrites, required this.enableLowResourcesMode, required this.logListener, required this.packageGraph, @@ -174,7 +172,6 @@ class BuildOptions { LogSubscription logSubscription, { Duration debounceDelay = const Duration(milliseconds: 250), bool deleteFilesByDefault = false, - bool delayWrites = false, bool enableLowResourcesMode = false, required PackageGraph packageGraph, Map overrideBuildConfig = const {}, @@ -216,7 +213,6 @@ feature, you may need to run `dart run build_runner clean` and then rebuild. return BuildOptions._( debounceDelay: debounceDelay, deleteFilesByDefault: deleteFilesByDefault, - delayWrites: delayWrites, enableLowResourcesMode: enableLowResourcesMode, logListener: logSubscription.logListener, packageGraph: packageGraph, diff --git a/build_runner_core/test/asset/writer_test.dart b/build_runner_core/test/asset/writer_test.dart index 62e0cfc09..d73f9a96c 100644 --- a/build_runner_core/test/asset/writer_test.dart +++ b/build_runner_core/test/asset/writer_test.dart @@ -5,6 +5,8 @@ import 'package:build/build.dart'; import 'package:build_runner_core/build_runner_core.dart'; import 'package:glob/glob.dart'; import 'package:test/test.dart'; +import 'package:path/path.dart' as p; +import 'package:watcher/watcher.dart'; void main() { late InMemoryRunnerAssetReader underlyingReader; @@ -36,7 +38,7 @@ void main() { expect(underlyingWriter.assets, {a: decodedMatches('a')}); }); - test('does not write twice', () async { + test('does not write data that has already been written', () async { final writer = DelayedAssetWriter(underlyingWriter); final asset = makeAssetId('a|lib/a.dart'); @@ -49,6 +51,25 @@ void main() { expect(underlyingWriter.assets, isEmpty); }); + test('avoids duplicate writes on the same asset', () async { + final writer = DelayedAssetWriter(underlyingWriter); + final asset = makeAssetId('a|lib/a.dart'); + + expect( + FakeWatcher(p.absolute('a')).events, + emitsInOrder([ + isA().having( + (e) => e.path, 'path', p.absolute('a', 'lib', 'a.dart')), + isA().having( + (e) => e.path, 'path', p.absolute('a', 'lib', 'a.dart.copy')), + ])); + + await writer.writeAsString(asset, 'old content'); + await writer.writeAsString(asset, 'new content'); + await writer.writeAsString(asset.addExtension('.copy'), 'new content'); + await writer.onBuildComplete(); + }); + group('has consistent reader', () { test('reading unmodified sources', () async { final asset = makeAssetId('a|lib/a.dart'); @@ -89,6 +110,22 @@ void main() { expect(await reader.readAsBytes(asset), decodedMatches('contents')); }); + test('does not use overlay after flush', () async { + final asset = makeAssetId('a|lib/a.dart'); + underlyingWriter.assets[asset] = [1, 2, 3]; + + final writer = DelayedAssetWriter(underlyingWriter); + final reader = writer.reader(underlyingReader, rootPackage); + + await writer.writeAsString(asset, 'contents'); + await writer.onBuildComplete(); + // "contents" should have been written, but there may be another tool + // overwriting the file afterwards. + underlyingWriter.assets[asset] = [4, 5, 6]; + + expect(await reader.readAsBytes(asset), [4, 5, 6]); + }); + test('finding right assets', () async { final a = makeAssetId('a|lib/a.dart'); final b = makeAssetId('a|lib/b.dart'); From 86ebe73bfcc4676272c849479b1412483e2e6825 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 7 Dec 2022 12:19:08 +0100 Subject: [PATCH 05/14] Only enable by default if not in low resource mode --- build_runner/lib/src/entrypoint/base_command.dart | 2 +- build_runner/lib/src/entrypoint/options.dart | 15 +++++++++------ build_runner/test/entrypoint/run_test.dart | 11 ----------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/build_runner/lib/src/entrypoint/base_command.dart b/build_runner/lib/src/entrypoint/base_command.dart index 6ec5c290a..6950f3240 100644 --- a/build_runner/lib/src/entrypoint/base_command.dart +++ b/build_runner/lib/src/entrypoint/base_command.dart @@ -102,7 +102,7 @@ abstract class BuildRunnerCommand extends Command { delayWritesOption, help: 'Delays all file system interactions until the end of a build, ' 'potentially reducing load on tools like the analysis server.', - defaultsTo: true, + defaultsTo: null, ); } diff --git a/build_runner/lib/src/entrypoint/options.dart b/build_runner/lib/src/entrypoint/options.dart index ac9982043..99e41fb26 100644 --- a/build_runner/lib/src/entrypoint/options.dart +++ b/build_runner/lib/src/entrypoint/options.dart @@ -104,8 +104,11 @@ class SharedOptions { required this.isReleaseBuild, required this.logPerformanceDir, required this.enableExperiments, - required this.delayAssetWrites, - }); + bool? delayAssetWrites, + }) : + // Delayed asset writes should be enabled by default if we're not in the + // low-resources mode. + delayAssetWrites = delayAssetWrites ?? !enableLowResourcesMode; SharedOptions.fromParsedArgs(ArgResults argResults, Iterable positionalArgs, String rootPackage, Command command) @@ -127,7 +130,7 @@ class SharedOptions { isReleaseBuild: argResults[releaseOption] as bool, logPerformanceDir: argResults[logPerformanceOption] as String?, enableExperiments: argResults[enableExperimentOption] as List, - delayAssetWrites: argResults[delayWritesOption] as bool, + delayAssetWrites: argResults[delayWritesOption] as bool?, ); } @@ -209,7 +212,7 @@ class DaemonOptions extends WatchOptions { logPerformanceDir: argResults[logPerformanceOption] as String?, usePollingWatcher: argResults[usePollingWatcherOption] as bool, enableExperiments: argResults[enableExperimentOption] as List, - delayAssetWrites: argResults[delayWritesOption] as bool, + delayAssetWrites: argResults[delayWritesOption] as bool?, ); } } @@ -275,7 +278,7 @@ class WatchOptions extends SharedOptions { logPerformanceDir: argResults[logPerformanceOption] as String?, usePollingWatcher: argResults[usePollingWatcherOption] as bool, enableExperiments: argResults[enableExperimentOption] as List, - delayAssetWrites: argResults[delayWritesOption] as bool, + delayAssetWrites: argResults[delayWritesOption] as bool?, ); } @@ -392,7 +395,7 @@ class ServeOptions extends WatchOptions { logPerformanceDir: argResults[logPerformanceOption] as String?, usePollingWatcher: argResults[usePollingWatcherOption] as bool, enableExperiments: argResults[enableExperimentOption] as List, - delayAssetWrites: argResults[delayWritesOption] as bool, + delayAssetWrites: argResults[delayWritesOption] as bool?, ); } } diff --git a/build_runner/test/entrypoint/run_test.dart b/build_runner/test/entrypoint/run_test.dart index e272501bc..0a32527e0 100644 --- a/build_runner/test/entrypoint/run_test.dart +++ b/build_runner/test/entrypoint/run_test.dart @@ -105,16 +105,6 @@ main() { }); } - void testBasicBuildCommandWithDelayedWrites(String command) { - test('is supported by the $command command', () async { - var args = ['build_runner', command, 'web', '--delayed-writes']; - expect(await runSingleBuild(command, args), ExitCode.success.code); - expectOutput('web/main.dart.js', exists: true); - expectOutput('test/hello_test.dart.browser_test.dart.js', - exists: false); - }); - } - void testBuildCommandWithOutput(String command) { test('works with -o and the $command command', () async { var outputDirName = 'foo'; @@ -136,7 +126,6 @@ main() { for (var command in ['build', 'serve', 'watch']) { testBasicBuildCommand(command); - testBasicBuildCommandWithDelayedWrites(command); testBuildCommandWithOutput(command); } From b3cf705716fa9884b007b24502c238e6fe2e72fa Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 7 Dec 2022 12:19:21 +0100 Subject: [PATCH 06/14] Organize imports --- build_runner_core/test/asset/writer_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_runner_core/test/asset/writer_test.dart b/build_runner_core/test/asset/writer_test.dart index d73f9a96c..7356a00e5 100644 --- a/build_runner_core/test/asset/writer_test.dart +++ b/build_runner_core/test/asset/writer_test.dart @@ -4,8 +4,8 @@ import 'package:_test_common/common.dart'; import 'package:build/build.dart'; import 'package:build_runner_core/build_runner_core.dart'; import 'package:glob/glob.dart'; -import 'package:test/test.dart'; import 'package:path/path.dart' as p; +import 'package:test/test.dart'; import 'package:watcher/watcher.dart'; void main() { From 6fa7f4bb924c0cb97b1803d20aac010689aa92e7 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Wed, 7 Dec 2022 12:30:25 +0100 Subject: [PATCH 07/14] Revert unintentional change --- build_runner_core/lib/src/generate/build_definition.dart | 2 +- build_runner_core/lib/src/generate/build_impl.dart | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build_runner_core/lib/src/generate/build_definition.dart b/build_runner_core/lib/src/generate/build_definition.dart index fad02a8a1..c5a3c336e 100644 --- a/build_runner_core/lib/src/generate/build_definition.dart +++ b/build_runner_core/lib/src/generate/build_definition.dart @@ -543,7 +543,7 @@ class _Loader { var done = false; while (!done) { try { - final choice = await _environment.prompt('Delete these files?', + var choice = await _environment.prompt('Delete these files?', ['Delete', 'Cancel build', 'List conflicts']); switch (choice) { diff --git a/build_runner_core/lib/src/generate/build_impl.dart b/build_runner_core/lib/src/generate/build_impl.dart index f221caec2..4fe2f4792 100644 --- a/build_runner_core/lib/src/generate/build_impl.dart +++ b/build_runner_core/lib/src/generate/build_impl.dart @@ -252,7 +252,8 @@ class _SingleBuild { } catch (e, s) { _logger.severe( 'Could not complete writes for this build.\n' - 'The current state of the file system may not be valid.', + 'The current state of the file system may not reflect the expected ' + 'build outputs.', e, s, ); From 64a9c0c4b3de9df5391eaa51b2df0e611bc5ca14 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 9 Dec 2022 19:09:43 +0100 Subject: [PATCH 08/14] Match against glob --- build_runner_core/lib/src/asset/writer.dart | 5 ++++- build_runner_core/test/asset/writer_test.dart | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/build_runner_core/lib/src/asset/writer.dart b/build_runner_core/lib/src/asset/writer.dart index 541140ea1..f5b41f051 100644 --- a/build_runner_core/lib/src/asset/writer.dart +++ b/build_runner_core/lib/src/asset/writer.dart @@ -108,7 +108,10 @@ class _DelayAwareReader extends AssetReader implements RunnerAssetReader { } for (final entry in _delayed.overlay.entries) { - if (entry.key.package == package && entry.value != null) { + final assetId = entry.key; + if (assetId.package == package && + glob.matches(assetId.path) && + entry.value != null) { yield entry.key; } } diff --git a/build_runner_core/test/asset/writer_test.dart b/build_runner_core/test/asset/writer_test.dart index 7356a00e5..7f3a0cb38 100644 --- a/build_runner_core/test/asset/writer_test.dart +++ b/build_runner_core/test/asset/writer_test.dart @@ -129,17 +129,27 @@ void main() { test('finding right assets', () async { final a = makeAssetId('a|lib/a.dart'); final b = makeAssetId('a|lib/b.dart'); - final glob = Glob('**'); - underlyingWriter.assets[a] = [1, 2, 3]; + final c = makeAssetId('a|pubspec.yaml'); + + final glob = Glob('**/*.dart'); + underlyingWriter + ..assets[a] = [1, 2, 3] + ..assets[c] = [4, 5, 6]; final writer = DelayedAssetWriter(underlyingWriter); final reader = writer.reader(underlyingReader, rootPackage); expect(await reader.findAssets(glob).toSet(), {a}); + await writer.writeAsString(b, 'b'); expect(await reader.findAssets(glob).toSet(), {a, b}); + + await writer.writeAsString(c, 'not dart'); + expect(await reader.findAssets(glob).toSet(), {a, b}); + await writer.delete(a); expect(await reader.findAssets(glob).toSet(), {b}); + await writer.delete(b); expect(await reader.findAssets(glob).toSet(), isEmpty); }); From 81c5367b84ad0525a155451bc0fdf331650c3a50 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 9 Dec 2022 19:10:16 +0100 Subject: [PATCH 09/14] Add trailing newline to example pubspec --- example/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 7cfe6648c..c14f0752c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -18,4 +18,4 @@ dependency_overrides: build_runner: path: ../build_runner build_runner_core: - path: ../build_runner_core \ No newline at end of file + path: ../build_runner_core From 46decef42f6bf57bc7f95008840b191858e9f208 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 19 Dec 2022 22:39:38 +0100 Subject: [PATCH 10/14] Fix paths when notifying fake watchers --- _test_common/lib/in_memory_writer.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/_test_common/lib/in_memory_writer.dart b/_test_common/lib/in_memory_writer.dart index ab44190b6..d158f7671 100644 --- a/_test_common/lib/in_memory_writer.dart +++ b/_test_common/lib/in_memory_writer.dart @@ -14,12 +14,15 @@ class InMemoryRunnerAssetWriter extends InMemoryAssetWriter implements RunnerAssetWriter { void Function(AssetId)? onDelete; + String _assetPath(AssetId id) { + return p.absolute(p.joinAll([id.package, ...id.pathSegments])); + } + @override Future writeAsBytes(AssetId id, List bytes) async { var type = assets.containsKey(id) ? ChangeType.MODIFY : ChangeType.ADD; await super.writeAsBytes(id, bytes); - FakeWatcher.notifyWatchers( - WatchEvent(type, p.absolute(id.package, id.path))); + FakeWatcher.notifyWatchers(WatchEvent(type, _assetPath(id))); } @override @@ -27,16 +30,14 @@ class InMemoryRunnerAssetWriter extends InMemoryAssetWriter {Encoding encoding = utf8}) async { var type = assets.containsKey(id) ? ChangeType.MODIFY : ChangeType.ADD; await super.writeAsString(id, contents, encoding: encoding); - FakeWatcher.notifyWatchers( - WatchEvent(type, p.absolute(id.package, id.path))); + FakeWatcher.notifyWatchers(WatchEvent(type, _assetPath(id))); } @override Future delete(AssetId id) async { onDelete?.call(id); assets.remove(id); - FakeWatcher.notifyWatchers( - WatchEvent(ChangeType.REMOVE, p.absolute(id.package, id.path))); + FakeWatcher.notifyWatchers(WatchEvent(ChangeType.REMOVE, _assetPath(id))); } @override From b93d5343486874400d227692daf4d927320a85b3 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 20 Dec 2022 00:26:22 +0100 Subject: [PATCH 11/14] Fix symlink test --- build_runner_core/lib/src/asset/writer.dart | 46 ++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/build_runner_core/lib/src/asset/writer.dart b/build_runner_core/lib/src/asset/writer.dart index f5b41f051..1c3f32e3b 100644 --- a/build_runner_core/lib/src/asset/writer.dart +++ b/build_runner_core/lib/src/asset/writer.dart @@ -44,7 +44,17 @@ class DelayedAssetWriter implements RunnerAssetWriter { /// Obtain a [RunnerAssetReader] capable of reading pending writes of this /// writer (before they are persisted with [onBuildComplete]). RunnerAssetReader reader(RunnerAssetReader delegate, String rootPackage) { - return _DelayAwareReader(delegate, this, rootPackage); + final reader = _DelayAwareReader(delegate, this, rootPackage); + + if (delegate is PathProvidingAssetReader) { + // `createMergedOutputDirectories` needs a path providing asset reader, so + // keep that interface intact if we're wrapping a reader capable of doing + // that. + return _PathProvidingDelayAwareReader( + reader, delegate as PathProvidingAssetReader); + } else { + return reader; + } } @override @@ -136,3 +146,37 @@ class _DelayAwareReader extends AssetReader implements RunnerAssetReader { return encoding.decode(await readAsBytes(id)); } } + +class _PathProvidingDelayAwareReader extends AssetReader + with RunnerAssetReader + implements PathProvidingAssetReader { + final _DelayAwareReader _reader; + final PathProvidingAssetReader _original; + + _PathProvidingDelayAwareReader(this._reader, this._original); + + @override + Future canRead(AssetId id) { + return _reader.canRead(id); + } + + @override + Stream findAssets(Glob glob, {String? package}) { + return _reader.findAssets(glob, package: package); + } + + @override + String pathTo(AssetId id) { + return _original.pathTo(id); + } + + @override + Future> readAsBytes(AssetId id) { + return _reader.readAsBytes(id); + } + + @override + Future readAsString(AssetId id, {Encoding encoding = utf8}) { + return _reader.readAsString(id, encoding: encoding); + } +} From 5ed6cebc3ab7299138569c7a9be7609cba6c4a72 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 23 Dec 2022 14:13:09 +0100 Subject: [PATCH 12/14] Revert "Fix symlink test" This reverts commit b93d5343486874400d227692daf4d927320a85b3. --- build_runner_core/lib/src/asset/writer.dart | 46 +-------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/build_runner_core/lib/src/asset/writer.dart b/build_runner_core/lib/src/asset/writer.dart index 1c3f32e3b..f5b41f051 100644 --- a/build_runner_core/lib/src/asset/writer.dart +++ b/build_runner_core/lib/src/asset/writer.dart @@ -44,17 +44,7 @@ class DelayedAssetWriter implements RunnerAssetWriter { /// Obtain a [RunnerAssetReader] capable of reading pending writes of this /// writer (before they are persisted with [onBuildComplete]). RunnerAssetReader reader(RunnerAssetReader delegate, String rootPackage) { - final reader = _DelayAwareReader(delegate, this, rootPackage); - - if (delegate is PathProvidingAssetReader) { - // `createMergedOutputDirectories` needs a path providing asset reader, so - // keep that interface intact if we're wrapping a reader capable of doing - // that. - return _PathProvidingDelayAwareReader( - reader, delegate as PathProvidingAssetReader); - } else { - return reader; - } + return _DelayAwareReader(delegate, this, rootPackage); } @override @@ -146,37 +136,3 @@ class _DelayAwareReader extends AssetReader implements RunnerAssetReader { return encoding.decode(await readAsBytes(id)); } } - -class _PathProvidingDelayAwareReader extends AssetReader - with RunnerAssetReader - implements PathProvidingAssetReader { - final _DelayAwareReader _reader; - final PathProvidingAssetReader _original; - - _PathProvidingDelayAwareReader(this._reader, this._original); - - @override - Future canRead(AssetId id) { - return _reader.canRead(id); - } - - @override - Stream findAssets(Glob glob, {String? package}) { - return _reader.findAssets(glob, package: package); - } - - @override - String pathTo(AssetId id) { - return _original.pathTo(id); - } - - @override - Future> readAsBytes(AssetId id) { - return _reader.readAsBytes(id); - } - - @override - Future readAsString(AssetId id, {Encoding encoding = utf8}) { - return _reader.readAsString(id, encoding: encoding); - } -} From 7646db12029066818d0c35a9cdff1d29aaece510 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 25 Feb 2023 22:34:00 +0100 Subject: [PATCH 13/14] Merge path providing reader into runner reader --- _test_common/lib/in_memory_reader.dart | 8 ++++ build_runner/pubspec.yaml | 2 + .../lib/src/asset/build_cache.dart | 41 +++++++++---------- build_runner_core/lib/src/asset/cache.dart | 39 ++++++++---------- .../lib/src/asset/file_based.dart | 6 ++- build_runner_core/lib/src/asset/reader.dart | 13 ++++-- build_runner_core/lib/src/asset/writer.dart | 8 ++++ .../src/environment/create_merged_dir.dart | 7 ++-- 8 files changed, 72 insertions(+), 52 deletions(-) diff --git a/_test_common/lib/in_memory_reader.dart b/_test_common/lib/in_memory_reader.dart index 4f5776ccb..a840033c5 100644 --- a/_test_common/lib/in_memory_reader.dart +++ b/_test_common/lib/in_memory_reader.dart @@ -14,12 +14,20 @@ class InMemoryRunnerAssetReader extends InMemoryAssetReader final _onCanReadController = StreamController.broadcast(); Stream get onCanRead => _onCanReadController.stream; + @override + bool get supportsFindingAssetPaths => false; + @override Future canRead(AssetId id) { _onCanReadController.add(id); return super.canRead(id); } + @override + String pathTo(AssetId id) { + throw UnsupportedError('Path for asset id $id'); + } + InMemoryRunnerAssetReader( [Map? sourceAssets, String? rootPackage]) : super(sourceAssets: sourceAssets, rootPackage: rootPackage); diff --git a/build_runner/pubspec.yaml b/build_runner/pubspec.yaml index a56ab816b..70cf7f975 100644 --- a/build_runner/pubspec.yaml +++ b/build_runner/pubspec.yaml @@ -63,3 +63,5 @@ dependency_overrides: path: ../build_modules build_web_compilers: path: ../build_web_compilers + build_runner_core: + path: ../build_runner_core diff --git a/build_runner_core/lib/src/asset/build_cache.dart b/build_runner_core/lib/src/asset/build_cache.dart index b3896e08e..abee43390 100644 --- a/build_runner_core/lib/src/asset/build_cache.dart +++ b/build_runner_core/lib/src/asset/build_cache.dart @@ -13,18 +13,18 @@ import 'writer.dart'; /// Wraps an [AssetReader] and translates reads for generated files into reads /// from the build cache directory -class BuildCacheReader implements AssetReader { +class BuildCacheReader implements RunnerAssetReader { final AssetGraph _assetGraph; final AssetReader _delegate; final String _rootPackage; - BuildCacheReader._(this._delegate, this._assetGraph, this._rootPackage); + BuildCacheReader(this._delegate, this._assetGraph, this._rootPackage); - factory BuildCacheReader( - AssetReader delegate, AssetGraph assetGraph, String rootPackage) => - delegate is PathProvidingAssetReader - ? _PathProvidingBuildCacheReader._(delegate, assetGraph, rootPackage) - : BuildCacheReader._(delegate, assetGraph, rootPackage); + @override + bool get supportsFindingAssetPaths { + final delegate = _delegate; + return delegate is RunnerAssetReader && delegate.supportsFindingAssetPaths; + } @override Future canRead(AssetId id) => @@ -44,23 +44,20 @@ class BuildCacheReader implements AssetReader { encoding: encoding); @override - Stream findAssets(Glob glob) => throw UnimplementedError( - 'Asset globbing should be done per phase with the AssetGraph'); -} - -class _PathProvidingBuildCacheReader extends BuildCacheReader - implements PathProvidingAssetReader { - @override - PathProvidingAssetReader get _delegate => - super._delegate as PathProvidingAssetReader; - - _PathProvidingBuildCacheReader._(PathProvidingAssetReader delegate, - AssetGraph assetGraph, String rootPackage) - : super._(delegate, assetGraph, rootPackage); + Stream findAssets(Glob glob, {String? package}) { + throw UnimplementedError( + 'Asset globbing should be done per phase with the AssetGraph'); + } @override - String pathTo(AssetId id) => - _delegate.pathTo(_cacheLocation(id, _assetGraph, _rootPackage)); + String pathTo(AssetId id) { + final delegate = _delegate; + if (delegate is RunnerAssetReader) { + return delegate.pathTo(_cacheLocation(id, _assetGraph, _rootPackage)); + } else { + throw UnsupportedError('Cannot find path to $id'); + } + } } class BuildCacheWriter implements RunnerAssetWriter { diff --git a/build_runner_core/lib/src/asset/cache.dart b/build_runner_core/lib/src/asset/cache.dart index f315eced3..76badf48d 100644 --- a/build_runner_core/lib/src/asset/cache.dart +++ b/build_runner_core/lib/src/asset/cache.dart @@ -18,7 +18,7 @@ import 'reader.dart'; /// Assets are cached until [invalidate] is invoked. /// /// Does not implement [findAssets]. -class CachingAssetReader implements AssetReader { +class CachingAssetReader implements RunnerAssetReader { /// Cached results of [readAsBytes]. final _bytesContentCache = LruCache>( 1024 * 1024, @@ -46,12 +46,13 @@ class CachingAssetReader implements AssetReader { final AssetReader _delegate; - CachingAssetReader._(this._delegate); + CachingAssetReader(this._delegate); - factory CachingAssetReader(AssetReader delegate) => - delegate is PathProvidingAssetReader - ? _PathProvidingCachingAssetReader._(delegate) - : CachingAssetReader._(delegate); + @override + bool get supportsFindingAssetPaths { + final delegate = _delegate; + return delegate is RunnerAssetReader && delegate.supportsFindingAssetPaths; + } @override Future canRead(AssetId id) => @@ -61,9 +62,19 @@ class CachingAssetReader implements AssetReader { Future digest(AssetId id) => _delegate.digest(id); @override - Stream findAssets(Glob glob) => + Stream findAssets(Glob glob, {String? package}) => throw UnimplementedError('unimplemented!'); + @override + String pathTo(AssetId id) { + final delegate = _delegate; + if (delegate is RunnerAssetReader) { + return delegate.pathTo(id); + } else { + throw UnsupportedError('Path to asset'); + } + } + @override Future> readAsBytes(AssetId id, {bool cache = true}) { var cached = _bytesContentCache[id]; @@ -111,17 +122,3 @@ class CachingAssetReader implements AssetReader { } } } - -/// A version of a [CachingAssetReader] that implements -/// [PathProvidingAssetReader]. -class _PathProvidingCachingAssetReader extends CachingAssetReader - implements PathProvidingAssetReader { - @override - PathProvidingAssetReader get _delegate => - super._delegate as PathProvidingAssetReader; - - _PathProvidingCachingAssetReader._(AssetReader delegate) : super._(delegate); - - @override - String pathTo(AssetId id) => _delegate.pathTo(id); -} diff --git a/build_runner_core/lib/src/asset/file_based.dart b/build_runner_core/lib/src/asset/file_based.dart index 4f36f528d..71c4b7bd2 100644 --- a/build_runner_core/lib/src/asset/file_based.dart +++ b/build_runner_core/lib/src/asset/file_based.dart @@ -20,12 +20,14 @@ final _descriptorPool = Pool(32); /// Basic [AssetReader] which uses a [PackageGraph] to look up where to read /// files from disk. -class FileBasedAssetReader extends AssetReader - implements RunnerAssetReader, PathProvidingAssetReader { +class FileBasedAssetReader extends AssetReader implements RunnerAssetReader { final PackageGraph packageGraph; FileBasedAssetReader(this.packageGraph); + @override + bool get supportsFindingAssetPaths => true; + @override Future canRead(AssetId id) => _descriptorPool.withResource(() => _fileFor(id, packageGraph).exists()); diff --git a/build_runner_core/lib/src/asset/reader.dart b/build_runner_core/lib/src/asset/reader.dart index 037e9e2a1..acf35732f 100644 --- a/build_runner_core/lib/src/asset/reader.dart +++ b/build_runner_core/lib/src/asset/reader.dart @@ -15,11 +15,16 @@ import '../asset_graph/node.dart'; import '../util/async.dart'; /// A [RunnerAssetReader] must implement [MultiPackageAssetReader]. -abstract class RunnerAssetReader implements MultiPackageAssetReader {} +abstract class RunnerAssetReader implements MultiPackageAssetReader { + /// Whether this reader supports [pathTo] operations. + bool get supportsFindingAssetPaths => false; -/// An [AssetReader] that can provide actual paths to assets on disk. -abstract class PathProvidingAssetReader implements AssetReader { - String pathTo(AssetId id); + /// Finds the path on the disk for a file-based asset [id]. + /// + /// Throws [UnsupportedError] for readers that can't map assets to files. + String pathTo(AssetId id) { + throw UnsupportedError('Path for asset id'); + } } /// Describes if and how a [SingleStepReader] should read an [AssetId]. diff --git a/build_runner_core/lib/src/asset/writer.dart b/build_runner_core/lib/src/asset/writer.dart index f5b41f051..f150325c9 100644 --- a/build_runner_core/lib/src/asset/writer.dart +++ b/build_runner_core/lib/src/asset/writer.dart @@ -87,6 +87,9 @@ class _DelayAwareReader extends AssetReader implements RunnerAssetReader { _DelayAwareReader(this._delegate, this._delayed, this._rootPackage); + @override + bool get supportsFindingAssetPaths => _delegate.supportsFindingAssetPaths; + @override Future canRead(AssetId id) async { if (_delayed.overlay.containsKey(id)) { @@ -135,4 +138,9 @@ class _DelayAwareReader extends AssetReader implements RunnerAssetReader { Future readAsString(AssetId id, {Encoding encoding = utf8}) async { return encoding.decode(await readAsBytes(id)); } + + @override + String pathTo(AssetId id) { + return _delegate.pathTo(id); + } } diff --git a/build_runner_core/lib/src/environment/create_merged_dir.dart b/build_runner_core/lib/src/environment/create_merged_dir.dart index 0957d6147..ec40c1747 100644 --- a/build_runner_core/lib/src/environment/create_merged_dir.dart +++ b/build_runner_core/lib/src/environment/create_merged_dir.dart @@ -36,7 +36,8 @@ Future createMergedOutputDirectories( AssetReader reader, FinalizedAssetsView finalizedAssetsView, bool outputSymlinksOnly) async { - if (outputSymlinksOnly && reader is! PathProvidingAssetReader) { + if (outputSymlinksOnly && + !(reader is RunnerAssetReader && reader.supportsFindingAssetPaths)) { _logger.severe( 'The current environment does not support symlinks, but symlinks were ' 'requested.'); @@ -254,8 +255,8 @@ Future _writeAsset( if (symlinkOnly) { await Link(_filePathFor(outputDir, outputId)).create( // We assert at the top of `createMergedOutputDirectories` that the - // reader implements this type when requesting symlinks. - (reader as PathProvidingAssetReader).pathTo(id), + // reader supports pathTo when requesting symlinks. + (reader as RunnerAssetReader).pathTo(id), recursive: true); } else { await _writeAsBytes(outputDir, outputId, await reader.readAsBytes(id)); From e885e64dcae41658721a73d021903762570d44bb Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 25 Feb 2023 22:37:51 +0100 Subject: [PATCH 14/14] Add dependency override to build_modules --- build_modules/pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build_modules/pubspec.yaml b/build_modules/pubspec.yaml index c0ca8f02f..3290cdc5c 100644 --- a/build_modules/pubspec.yaml +++ b/build_modules/pubspec.yaml @@ -39,3 +39,5 @@ dev_dependencies: dependency_overrides: build_runner: path: ../build_runner + build_runner_core: + path: ../build_runner_core