in build_runner/lib/src/generate/watch_impl.dart [220:377]
Stream<BuildResult> _run(
BuildOptions options,
BuildEnvironment environment,
List<BuilderApplication> builders,
Map<String, Map<String, dynamic>> builderConfigOverrides,
Future until,
{bool isReleaseMode = false}) {
var watcherEnvironment = OverrideableEnvironment(environment,
writer: OnDeleteWriter(environment.writer, _expectedDeletes.add));
var firstBuildCompleter = Completer<BuildResult>();
currentBuild = firstBuildCompleter.future;
var controller = StreamController<BuildResult>();
Future<BuildResult> doBuild(List<List<AssetChange>> changes) async {
var build = _build!;
_logger
..info('${'-' * 72}\n')
..info('Starting Build\n');
var mergedChanges = collectChanges(changes);
_expectedDeletes.clear();
if (!options.skipBuildScriptCheck) {
if (build.buildScriptUpdates!
.hasBeenUpdated(mergedChanges.keys.toSet())) {
_terminateCompleter.complete();
_logger.severe('Terminating builds due to build script update');
return BuildResult(BuildStatus.failure, [],
failureType: FailureType.buildScriptChanged);
}
}
return build.run(mergedChanges,
buildDirs: _buildDirs, buildFilters: _buildFilters);
}
var terminate = Future.any([until, _terminateCompleter.future]).then((_) {
_logger.info('Terminating. No further builds will be scheduled\n');
});
Digest? originalRootPackagesDigest;
Digest? originalRootPackageConfigDigest;
final rootPackagesId = AssetId(packageGraph.root.name, '.packages');
final rootPackageConfigId =
AssetId(packageGraph.root.name, '.dart_tool/package_config.json');
// Start watching files immediately, before the first build is even started.
var graphWatcher = PackageGraphWatcher(packageGraph,
logger: _logger,
watch: (node) =>
PackageNodeWatcher(node, watch: _directoryWatcherFactory));
graphWatcher
.watch()
.asyncMap<AssetChange>((change) {
// Delay any events until the first build is completed.
if (firstBuildCompleter.isCompleted) return change;
return firstBuildCompleter.future.then((_) => change);
})
.asyncMap<AssetChange>((change) {
var id = change.id;
if (id == rootPackagesId || id == rootPackageConfigId) {
var digest = id == rootPackagesId
? originalRootPackagesDigest
: originalRootPackageConfigDigest;
assert(digest != null);
// Kill future builds if the root packages file changes.
//
// We retry the reads for a little bit to handle the case where a
// user runs `pub get` and it hasn't been re-written yet.
return _readOnceExists(id, watcherEnvironment.reader).then((bytes) {
if (md5.convert(bytes) != digest) {
_terminateCompleter.complete();
_logger
.severe('Terminating builds due to package graph update, '
'please restart the build.');
}
return change;
});
} else if (_isBuildYaml(id) ||
_isConfiguredBuildYaml(id) ||
_isPackageBuildYamlOverride(id)) {
controller.add(BuildResult(BuildStatus.failure, [],
failureType: FailureType.buildConfigChanged));
// Kill future builds if the build.yaml files change.
_terminateCompleter.complete();
_logger.severe(
'Terminating builds due to ${id.package}:${id.path} update.');
}
return change;
})
.asyncWhere((change) {
assert(_readerCompleter.isCompleted);
return shouldProcess(
change,
assetGraph!,
options,
_willCreateOutputDirs,
_expectedDeletes,
watcherEnvironment.reader,
);
})
.debounceBuffer(_debounceDelay)
.takeUntil(terminate)
.asyncMapBuffer((changes) => currentBuild = doBuild(changes)
..whenComplete(() => currentBuild = null))
.listen((BuildResult result) {
if (controller.isClosed) return;
controller.add(result);
})
.onDone(() async {
await currentBuild;
await _build?.beforeExit();
if (!controller.isClosed) await controller.close();
_logger.info('Builds finished. Safe to exit\n');
});
// Schedule the actual first build for the future so we can return the
// stream synchronously.
() async {
await logTimedAsync(_logger, 'Waiting for all file watchers to be ready',
() => graphWatcher.ready);
originalRootPackagesDigest = md5
.convert(await watcherEnvironment.reader.readAsBytes(rootPackagesId));
originalRootPackageConfigDigest = md5.convert(
await watcherEnvironment.reader.readAsBytes(rootPackageConfigId));
BuildResult firstBuild;
BuildImpl? build;
try {
build = _build = await BuildImpl.create(
options, watcherEnvironment, builders, builderConfigOverrides,
isReleaseBuild: isReleaseMode);
firstBuild = await build
.run({}, buildDirs: _buildDirs, buildFilters: _buildFilters);
} on CannotBuildException catch (e, s) {
_terminateCompleter.complete();
firstBuild = BuildResult(BuildStatus.failure, []);
_readerCompleter.completeError(e, s);
} on BuildScriptChangedException catch (e, s) {
_terminateCompleter.complete();
firstBuild = BuildResult(BuildStatus.failure, [],
failureType: FailureType.buildScriptChanged);
_readerCompleter.completeError(e, s);
}
if (build != null) {
assert(!_readerCompleter.isCompleted);
_readerCompleter.complete(build.finalizedReader);
}
// It is possible this is already closed if the user kills the process
// early, which results in an exception without this check.
if (!controller.isClosed) controller.add(firstBuild);
firstBuildCompleter.complete(firstBuild);
}();
return controller.stream;
}