blob: 672450a86d81600eb88c0412d6cf009491593981 [file] [log] [blame]
// Copyright (c) 2013, 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.
library pub.barback.sources;
import 'dart:async';
import 'package:barback/barback.dart';
import 'package:path/path.dart' as path;
import 'package:watcher/watcher.dart';
import '../entrypoint.dart';
import '../io.dart';
import '../package.dart';
import '../package_graph.dart';
/// Adds all of the source assets in the provided packages to barback and
/// then watches the public directories for changes.
///
/// [watcherFactory] should return a [DirectoryWatcher] watching the given
/// directory for changes.
///
/// Returns a Future that completes when the sources are loaded and the watchers
/// are active.
Future watchSources(PackageGraph graph, Barback barback,
WatcherType watcherType) {
return Future.wait(graph.packages.values.map((package) {
// If this package comes from a cached source, its contents won't change so
// we don't need to monitor it. `packageId` will be null for the application
// package, since that's not locked.
var packageId = graph.lockFile.packages[package.name];
if (packageId != null &&
graph.entrypoint.cache.sources[packageId.source].shouldCache) {
barback.updateSources(_listAssets(graph.entrypoint, package));
return new Future.value();
}
// Watch the visible package directories for changes.
return Future.wait(_getPublicDirectories(graph.entrypoint, package)
.map((name) {
var subdirectory = path.join(package.dir, name);
if (!dirExists(subdirectory)) return new Future.value();
// TODO(nweiz): close these watchers when [barback] is closed.
var watcher = watcherType.create(subdirectory);
watcher.events.listen((event) {
// Don't watch files symlinked into these directories.
// TODO(rnystrom): If pub gets rid of symlinks, remove this.
var parts = path.split(event.path);
if (parts.contains("packages") || parts.contains("assets")) return;
// Skip ".js" files that were (most likely) compiled from nearby ".dart"
// files. These are created by the Editor's "Run as JavaScript" command
// and are written directly into the package's directory. When pub's
// dart2js transformer then tries to create the same file name, we get
// a build error. To avoid that, just don't consider that file to be a
// source.
// TODO(rnystrom): Remove this when the Editor no longer generates .js
// files. See #15859.
if (event.path.endsWith(".dart.js")) return;
var id = new AssetId(package.name,
path.relative(event.path, from: package.dir));
if (event.type == ChangeType.REMOVE) {
barback.removeSources([id]);
} else {
barback.updateSources([id]);
}
});
return watcher.ready;
})).then((_) {
barback.updateSources(_listAssets(graph.entrypoint, package));
});
}));
}
/// Adds all of the source assets in the provided packages to barback.
void loadSources(PackageGraph graph, Barback barback) {
for (var package in graph.packages.values) {
barback.updateSources(_listAssets(graph.entrypoint, package));
}
}
/// Lists all of the visible files in [package].
///
/// This is the recursive contents of the "asset" and "lib" directories (if
/// present). If [package] is the entrypoint package, it also includes the
/// contents of "web".
List<AssetId> _listAssets(Entrypoint entrypoint, Package package) {
var files = <AssetId>[];
for (var dirPath in _getPublicDirectories(entrypoint, package)) {
var dir = path.join(package.dir, dirPath);
if (!dirExists(dir)) continue;
for (var entry in listDir(dir, recursive: true)) {
// Ignore "packages" symlinks if there.
if (path.split(entry).contains("packages")) continue;
// Skip directories.
if (!fileExists(entry)) continue;
// Skip ".js" files that were (most likely) compiled from nearby ".dart"
// files. These are created by the Editor's "Run as JavaScript" command
// and are written directly into the package's directory. When pub's
// dart2js transformer then tries to create the same file name, we get
// a build error. To avoid that, just don't consider that file to be a
// source.
// TODO(rnystrom): Remove this when the Editor no longer generates .js
// files. See #15859.
if (entry.endsWith(".dart.js")) continue;
var id = new AssetId(package.name,
path.relative(entry, from: package.dir));
files.add(id);
}
}
return files;
}
/// Gets the names of the top-level directories in [package] whose contents
/// should be provided as source assets.
Iterable<String> _getPublicDirectories(Entrypoint entrypoint, Package package) {
var directories = ["asset", "lib"];
if (package.name == entrypoint.root.name) directories.add("web");
return directories;
}
/// An enum describing different modes of constructing a [DirectoryWatcher].
abstract class WatcherType {
/// A watcher that automatically chooses its type based on the operating
/// system.
static const AUTO = const _AutoWatcherType();
/// A watcher that always polls the filesystem for changes.
static const POLLING = const _PollingWatcherType();
/// No directory watcher at all.
static const NONE = const _NoneWatcherType();
/// Creates a new DirectoryWatcher.
DirectoryWatcher create(String directory);
String toString();
}
class _AutoWatcherType implements WatcherType {
const _AutoWatcherType();
DirectoryWatcher create(String directory) =>
new DirectoryWatcher(directory);
String toString() => "auto";
}
class _PollingWatcherType implements WatcherType {
const _PollingWatcherType();
DirectoryWatcher create(String directory) =>
new PollingDirectoryWatcher(directory);
String toString() => "polling";
}
class _NoneWatcherType implements WatcherType {
const _NoneWatcherType();
DirectoryWatcher create(String directory) => null;
String toString() => "none";
}