Skip to content

added top level watch function #45

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

Merged
merged 4 commits into from
Feb 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions e2e_example/tool/build.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright (c) 2016, 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:io';

import 'package:build/build.dart';
import 'package:build/src/generate/build.dart';
import 'package:build/src/generate/build_result.dart';

import 'package:e2e_example/copy_builder.dart';

Expand All @@ -19,20 +19,8 @@ main() async {
var result = await build([phases]);

if (result.status == BuildStatus.Success) {
print('''
Build Succeeded!

Type: ${result.buildType}
Outputs: ${result.outputs}''');
stdout.writeln(result);
} else {
print('''
Build Failed :(

Type: ${result.buildType}
Outputs: ${result.outputs}

Exception: ${result.exception}
Stack Trace:
${result.stackTrace}''');
stderr.writeln(result);
}
}
26 changes: 26 additions & 0 deletions e2e_example/tool/watch.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2016, 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:io';

import 'package:build/build.dart';

import 'package:e2e_example/copy_builder.dart';

main() async {
/// Builds a full package dependency graph for the current package.
var graph = new PackageGraph.forThisPackage();

/// Give [Builder]s access to a [PackageGraph] so they can choose which
/// packages to run on. This simplifies user code a lot, and helps to mitigate
/// the transitive deps issue.
var phases = CopyBuilder.buildPhases(graph);

await for (var result in watch([phases])) {
if (result.status == BuildStatus.Success) {
stdout.writeln(result);
} else {
stderr.writeln(result);
}
}
}
2 changes: 1 addition & 1 deletion lib/src/asset/cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class CachedAssetReader extends AssetReader {
}

@override
Stream<AssetId> listAssetIds(List<InputSet> inputSets) =>
Stream<AssetId> listAssetIds(Iterable<InputSet> inputSets) =>
_reader.listAssetIds(inputSets);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/asset/reader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ abstract class AssetReader {

Future<bool> hasInput(AssetId id);

Stream<AssetId> listAssetIds(List<InputSet> inputSets);
Stream<AssetId> listAssetIds(Iterable<InputSet> inputSets);
}
108 changes: 99 additions & 9 deletions lib/src/generate/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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:logging/logging.dart';

Expand All @@ -13,7 +14,9 @@ import '../asset_graph/graph.dart';
import '../package_graph/package_graph.dart';
import 'build_impl.dart';
import 'build_result.dart';
import 'directory_watcher_factory.dart';
import 'phase.dart';
import 'watch_impl.dart';

/// Runs all of the [Phases] in [phaseGroups].
///
Expand All @@ -29,28 +32,115 @@ import 'phase.dart';
///
/// Logging may be customized by passing a custom [logLevel] below which logs
/// will be ignored, as well as an [onLog] handler which defaults to [print].
///
/// The [teminateEventStream] is a stream which can send termination events.
/// By default the [ProcessSignal.SIGINT] stream is used. In this mode, it
/// will simply consume the first event and allow the build to continue.
/// Multiple termination events will cause a normal shutdown.
Future<BuildResult> build(List<List<Phase>> phaseGroups,
{PackageGraph packageGraph,
AssetReader reader,
AssetWriter writer,
Level logLevel: Level.ALL,
onLog(LogRecord)}) async {
Logger.root.level = logLevel;
onLog ??= print;
var logListener = Logger.root.onRecord.listen(onLog);
Level logLevel,
onLog(LogRecord),
Stream terminateEventStream}) async {
var logListener = _setupLogging(logLevel: logLevel, onLog: onLog);
packageGraph ??= new PackageGraph.forThisPackage();
var cache = new AssetCache();
reader ??=
new CachedAssetReader(cache, new FileBasedAssetReader(packageGraph));
writer ??=
new CachedAssetWriter(cache, new FileBasedAssetWriter(packageGraph));

var buildImpl = new BuildImpl(
new AssetGraph(), reader, writer, packageGraph, phaseGroups);

/// Run the build!
var result = await new BuildImpl(
new AssetGraph(), reader, writer, packageGraph, phaseGroups)
.runBuild();
var futureResult = buildImpl.runBuild();

await logListener.cancel();
// Stop doing new builds when told to terminate.
var listener = _setupTerminateLogic(terminateEventStream, () {
new Logger('Build').info('Waiting for build to finish...');
return futureResult;
});

var result = await futureResult;
listener.cancel();
logListener.cancel();
return result;
}

/// Same as [build], except it watches the file system and re-runs builds
/// automatically.
///
/// The [debounceDelay] controls how often builds will run. As long as files
/// keep changing with less than that amount of time apart, builds will be put
/// off.
///
/// The [directoryWatcherFactory] allows you to inject a way of creating custom
/// [DirectoryWatcher]s. By default a normal [DirectoryWatcher] will be used.
///
/// The [teminateEventStream] is a stream which can send termination events.
/// By default the [ProcessSignal.SIGINT] stream is used. In this mode, the
/// first event will allow any ongoing builds to finish, and then the program
/// will complete normally. Subsequent events are not handled (and will
/// typically cause a shutdown).
Stream<BuildResult> watch(List<List<Phase>> phaseGroups,
{PackageGraph packageGraph,
AssetReader reader,
AssetWriter writer,
Level logLevel,
onLog(LogRecord),
Duration debounceDelay: const Duration(milliseconds: 250),
DirectoryWatcherFactory directoryWatcherFactory,
Stream terminateEventStream}) {
// We never cancel this listener in watch mode, because we never exit unless
// forced to.
var logListener = _setupLogging(logLevel: logLevel, onLog: onLog);
packageGraph ??= new PackageGraph.forThisPackage();
var cache = new AssetCache();
reader ??=
new CachedAssetReader(cache, new FileBasedAssetReader(packageGraph));
writer ??=
new CachedAssetWriter(cache, new FileBasedAssetWriter(packageGraph));
directoryWatcherFactory ??= defaultDirectoryWatcherFactory;
var watchImpl = new WatchImpl(directoryWatcherFactory, debounceDelay, cache,
new AssetGraph(), reader, writer, packageGraph, phaseGroups);

var resultStream = watchImpl.runWatch();

// Stop doing new builds when told to terminate.
_setupTerminateLogic(terminateEventStream, () async {
await watchImpl.terminate();
logListener.cancel();
});

return resultStream;
}

/// Given [terminateEventStream], call [onTerminate] the first time an event is
/// seen. If a second event is recieved, simply exit.
StreamSubscription _setupTerminateLogic(Stream terminateEventStream,
Future onTerminate()) {
terminateEventStream ??= ProcessSignal.SIGINT.watch();

Choose a reason for hiding this comment

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

Move terminateEventStream logic to a helper

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good call

int numEventsSeen = 0;
var terminateListener;
terminateListener = terminateEventStream.listen((_) {
numEventsSeen++;
if (numEventsSeen == 1) {
onTerminate().then((_) {
terminateListener.cancel();
});
} else {
exit(2);
}
});
return terminateListener;
}

StreamSubscription _setupLogging({Level logLevel, onLog(LogRecord)}) {
logLevel ??= Level.INFO;
Logger.root.level = logLevel;
onLog ??= stdout.writeln;
return Logger.root.onRecord.listen(onLog);
}
16 changes: 12 additions & 4 deletions lib/src/generate/build_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;

import '../asset/asset.dart';
import '../asset/cache.dart';
import '../asset/exceptions.dart';
import '../asset/id.dart';
import '../asset/reader.dart';
Expand All @@ -31,9 +33,10 @@ class BuildImpl {
final List<List<Phase>> _phaseGroups;
final _inputsByPackage = <String, Set<AssetId>>{};
bool _buildRunning = false;
final _logger = new Logger('Build');

BuildImpl(this._assetGraph, this._reader, this._writer, this._packageGraph,
this._phaseGroups);
BuildImpl(this._assetGraph, this._reader, this._writer,
this._packageGraph, this._phaseGroups);

/// Runs a build
///
Expand All @@ -48,12 +51,15 @@ class BuildImpl {
_buildRunning = true;

/// Wait while all inputs are collected.
_logger.info('Initializing inputs');
await _initializeInputsByPackage();

/// Delete all previous outputs!
_logger.info('Deleting previous outputs');
await _deletePreviousOutputs();

/// Run a fresh build.
_logger.info('Running build phases');
var result = await _runPhases();

// Write out the new build_outputs file.
Expand Down Expand Up @@ -112,7 +118,8 @@ class BuildImpl {

groupOutputIds.addAll(outputs);
for (var output in outputs) {
if (tempInputsByPackage[output.package]?.contains(output) == true) {
if (tempInputsByPackage[output.package]?.contains(output) ==
true) {
conflictingOutputs.add(output);
}
}
Expand All @@ -139,10 +146,11 @@ class BuildImpl {
while (!done) {
stdout.write('Delete these files (y/n) (or list them (l))?: ');
var input = stdin.readLineSync();
switch (input) {
switch (input.toLowerCase()) {
case 'y':

Choose a reason for hiding this comment

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

Add handling for upper case (or just switch (input.toLowerCase())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point

stdout.writeln('Deleting files...');
await Future.wait(conflictingOutputs.map((output) {
_inputsByPackage[output.package]?.remove(output);
return _writer.delete(output);
}));
done = true;
Expand Down
20 changes: 20 additions & 0 deletions lib/src/generate/build_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ class BuildResult {
BuildResult(this.status, this.buildType, List<Asset> outputs,
{this.exception, this.stackTrace})
: outputs = new List.unmodifiable(outputs);

@override
String toString() {

Choose a reason for hiding this comment

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

@override

if (status == BuildStatus.Success) {
return '''

Build Succeeded!
Type: ${buildType}
''';
} else {
return '''

Build Failed :(
Type: ${buildType}
Exception: ${exception}
Stack Trace:
${stackTrace}
''';
}
}
}

/// The status of a build.
Expand Down
9 changes: 9 additions & 0 deletions lib/src/generate/directory_watcher_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2016, 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 'package:watcher/watcher.dart';

typedef DirectoryWatcher DirectoryWatcherFactory(String path);

DirectoryWatcher defaultDirectoryWatcherFactory(String path) =>
new DirectoryWatcher(path);
Loading