blob: db54ff1162bcf8d478a402137e55632639140ccc [file] [log] [blame]
// Copyright (c) 2017, 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:async';
import 'dart:io';
import 'package:async/async.dart';
import 'package:build_runner_core/build_runner_core.dart';
import 'package:logging/logging.dart';
import 'asset_change.dart';
import 'node_watcher.dart';
PackageNodeWatcher _default(PackageNode node) => PackageNodeWatcher(node);
/// Allows watching an entire graph of packages to schedule rebuilds.
class PackageGraphWatcher {
// TODO: Consider pulling logging out and providing hooks instead.
final Logger _logger;
final PackageNodeWatcher Function(PackageNode) _strategy;
final PackageGraph _graph;
final _readyCompleter = Completer<void>();
Future<void> get ready => _readyCompleter.future;
bool _isWatching = false;
/// Creates a new watcher for a [PackageGraph].
///
/// May optionally specify a [watch] strategy, otherwise will attempt a
/// reasonable default based on the current platform.
PackageGraphWatcher(
this._graph, {
Logger logger,
PackageNodeWatcher watch(PackageNode node),
}) : _logger = logger ?? Logger('build_runner'),
_strategy = watch ?? _default;
/// Returns a stream of records for assets that changed in the package graph.
Stream<AssetChange> watch() {
assert(!_isWatching);
_isWatching = true;
return LazyStream(
() => logTimedSync(_logger, 'Setting up file watchers', _watch));
}
Stream<AssetChange> _watch() {
final allWatchers = _graph.allPackages.values
.where((node) => node.dependencyType == DependencyType.path)
.map(_strategy)
.toList();
final filteredEvents = allWatchers
.map((w) => w.watch().where(_nestedPathFilter(w.node)))
.toList();
// Asynchronously complete the `_readyCompleter` once all the watchers
// are done.
() async {
await Future.wait(
allWatchers.map((nodeWatcher) => nodeWatcher.watcher.ready));
_readyCompleter.complete();
}();
return StreamGroup.merge(filteredEvents);
}
bool Function(AssetChange) _nestedPathFilter(PackageNode rootNode) {
final ignorePaths = _nestedPaths(rootNode);
return (change) => !ignorePaths.any(change.id.path.startsWith);
}
// Returns a set of all package paths that are "nested" within a node.
//
// This allows the watcher to optimize and avoid duplicate events.
List<String> _nestedPaths(PackageNode rootNode) {
return _graph.allPackages.values
.where((node) {
return node.path.length > rootNode.path.length &&
node.path.startsWith(rootNode.path);
})
.map((node) =>
node.path.substring(rootNode.path.length + 1) +
Platform.pathSeparator)
.toList();
}
}