Skip to content
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

Add a basic serve mode, which blocks all requests on ongoing builds. #57

Merged
merged 2 commits into from
Feb 22, 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
102 changes: 69 additions & 33 deletions lib/src/generate/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import 'dart:async';
import 'dart:io';

import 'package:logging/logging.dart';
import 'package:shelf/shelf.dart';

import '../asset/cache.dart';
import '../asset/file_based.dart';
import '../asset/reader.dart';
import '../asset/writer.dart';
import '../package_graph/package_graph.dart';
import '../server/server.dart';
import 'build_impl.dart';
import 'build_result.dart';
import 'directory_watcher_factory.dart';
import 'options.dart';
import 'phase.dart';
import 'watch_impl.dart';

Expand Down Expand Up @@ -43,15 +44,13 @@ Future<BuildResult> build(List<List<Phase>> phaseGroups,
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(reader, writer, packageGraph, phaseGroups);
var options = new BuildOptions(
packageGraph: packageGraph,
reader: reader,
writer: writer,
logLevel: logLevel,
onLog: onLog);
var buildImpl = new BuildImpl(options, phaseGroups);

/// Run the build!
var futureResult = buildImpl.runBuild();
Expand All @@ -64,7 +63,7 @@ Future<BuildResult> build(List<List<Phase>> phaseGroups,

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

Expand All @@ -89,28 +88,72 @@ Stream<BuildResult> watch(List<List<Phase>> phaseGroups,
AssetWriter writer,
Level logLevel,
onLog(LogRecord),
Duration debounceDelay: const Duration(milliseconds: 250),
Duration debounceDelay,
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, reader,
writer, packageGraph, phaseGroups);
var options = new BuildOptions(
packageGraph: packageGraph,
reader: reader,
writer: writer,
logLevel: logLevel,
onLog: onLog,
debounceDelay: debounceDelay,
directoryWatcherFactory: directoryWatcherFactory);
var watchImpl = new WatchImpl(options, phaseGroups);

var resultStream = watchImpl.runWatch();

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

return resultStream;
}

/// Same as [watch], except it also provides a server.
///
/// This server will block all requests if a build is current in process.
///
/// By default a static server will be set up to serve [directory] at
/// [address]:[port], but instead a [requestHandler] may be provided for custom
/// behavior.
Stream<BuildResult> serve(List<List<Phase>> phaseGroups,
{PackageGraph packageGraph,
AssetReader reader,
AssetWriter writer,
Level logLevel,
onLog(LogRecord),
Duration debounceDelay,
DirectoryWatcherFactory directoryWatcherFactory,
Stream terminateEventStream,
String directory,
String address,
int port,
Handler requestHandler}) {
var options = new BuildOptions(
packageGraph: packageGraph,
reader: reader,
writer: writer,
logLevel: logLevel,
onLog: onLog,
debounceDelay: debounceDelay,
directoryWatcherFactory: directoryWatcherFactory,
directory: directory,
address: address,
port: port);
var watchImpl = new WatchImpl(options, phaseGroups);

var resultStream = watchImpl.runWatch();
var serverStarted = startServer(watchImpl, options);

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

return resultStream;
Expand All @@ -135,10 +178,3 @@ StreamSubscription _setupTerminateLogic(
});
return terminateListener;
}

StreamSubscription _setupLogging({Level logLevel, onLog(LogRecord)}) {
logLevel ??= Level.INFO;
Logger.root.level = logLevel;
onLog ??= stdout.writeln;
return Logger.root.onRecord.listen(onLog);
}
6 changes: 5 additions & 1 deletion lib/src/generate/build_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import '../package_graph/package_graph.dart';
import 'build_result.dart';
import 'exceptions.dart';
import 'input_set.dart';
import 'options.dart';
import 'phase.dart';

/// Class which manages running builds.
Expand All @@ -42,7 +43,10 @@ class BuildImpl {

bool _isFirstBuild = true;

BuildImpl(this._reader, this._writer, this._packageGraph, this._phaseGroups);
BuildImpl(BuildOptions options, this._phaseGroups)
: _reader = options.reader,
_writer = options.writer,
_packageGraph = options.packageGraph;

/// Runs a build
///
Expand Down
69 changes: 69 additions & 0 deletions lib/src/generate/options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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:async';
import 'dart:io';

import 'package:logging/logging.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_static/shelf_static.dart';

import '../asset/cache.dart';
import '../asset/file_based.dart';
import '../asset/reader.dart';
import '../asset/writer.dart';
import '../package_graph/package_graph.dart';
import 'directory_watcher_factory.dart';

/// Manages setting up consistent defaults for all options and build modes.
class BuildOptions {
/// Build mode options.
StreamSubscription logListener;
PackageGraph packageGraph;
AssetReader reader;
AssetWriter writer;

/// Watch mode options.
Duration debounceDelay;
DirectoryWatcherFactory directoryWatcherFactory;

/// Server options.
int port;
String address;
String directory;
Handler requestHandler;

BuildOptions(
{this.debounceDelay,
this.directoryWatcherFactory,
Level logLevel,
onLog(LogRecord),
this.packageGraph,
this.reader,
this.writer,
this.directory,
this.address,
this.port,
this.requestHandler}) {
/// Set up logging
logLevel ??= Level.INFO;
Logger.root.level = logLevel;
onLog ??= stdout.writeln;
logListener = Logger.root.onRecord.listen(onLog);

/// Set up other defaults.
address ??= 'localhost';
directory ??= '.';
port ??= 8000;
requestHandler ??= createStaticHandler(directory,
defaultDocument: 'index.html', listDirectories: true);
debounceDelay ??= const Duration(milliseconds: 250);
packageGraph ??= new PackageGraph.forThisPackage();
var cache = new AssetCache();
reader ??=
new CachedAssetReader(cache, new FileBasedAssetReader(packageGraph));
writer ??=
new CachedAssetWriter(cache, new FileBasedAssetWriter(packageGraph));
directoryWatcherFactory ??= defaultDirectoryWatcherFactory;
}
}
19 changes: 8 additions & 11 deletions lib/src/generate/watch_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import 'package:path/path.dart' as path;
import 'package:watcher/watcher.dart';

import '../asset/id.dart';
import '../asset/reader.dart';
import '../asset/writer.dart';
import '../asset_graph/graph.dart';
import '../asset_graph/node.dart';
import '../package_graph/package_graph.dart';
import 'build_impl.dart';
import 'build_result.dart';
import 'directory_watcher_factory.dart';
import 'options.dart';
import 'phase.dart';

/// Watches all inputs for changes, and uses a [BuildImpl] to rerun builds as
Expand Down Expand Up @@ -47,6 +47,7 @@ class WatchImpl {

/// A future that completes when the current build is done.
Future _currentBuild;
Future get currentBuild => _currentBuild;

/// Whether or not another build is scheduled.
bool _nextBuildScheduled;
Expand All @@ -60,16 +61,12 @@ class WatchImpl {
/// Whether we are in the process of terminating.
bool _terminating = false;

WatchImpl(
this._directoryWatcherFactory,
this._debounceDelay,
AssetReader reader,
AssetWriter writer,
PackageGraph packageGraph,
List<List<Phase>> phaseGroups)
: _packageGraph = packageGraph,
_writer = writer,
_buildImpl = new BuildImpl(reader, writer, packageGraph, phaseGroups);
WatchImpl(BuildOptions options, List<List<Phase>> phaseGroups)
: _directoryWatcherFactory = options.directoryWatcherFactory,
_debounceDelay = options.debounceDelay,
_writer = options.writer,
_packageGraph = options.packageGraph,
_buildImpl = new BuildImpl(options, phaseGroups);

/// Completes after the current build is done, and stops further builds from
/// happening.
Expand Down
48 changes: 48 additions & 0 deletions lib/src/server/server.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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:async';
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_static/shelf_static.dart';

import '../generate/options.dart';
import '../generate/watch_impl.dart';

/// The actual [HttpServer] in use.
Future<HttpServer> _futureServer;

/// Public for testing purposes only :(. This file is not directly exported
/// though so it is effectively package private.
Handler blockingHandler;

/// Starts a server which blocks on any ongoing builds.
Future<HttpServer> startServer(WatchImpl watchImpl, BuildOptions options) {
if (_futureServer != null) {
throw new StateError('Server already running.');
}

try {
blockingHandler = (Request request) async {
if (watchImpl.currentBuild != null) await watchImpl.currentBuild;
return options.requestHandler(request);
};
_futureServer = serve(blockingHandler, options.address, options.port);
return _futureServer;
} catch (e, s) {
stderr.writeln('Error setting up server: $e\n\n$s');
return new Future.value(null);
}
}

Future stopServer() {
if (_futureServer == null) {
throw new StateError('Server not running.');
}
return _futureServer.then((server) {
server.close();
_futureServer = null;
blockingHandler = null;
});
}
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ dependencies:
logging: ^0.11.2
glob: ^1.1.0
path: ^1.1.0
shelf: ^0.6.5
shelf_static: ^0.2.3
source_maps: '>=0.9.4 <0.11.0'
source_span: '>=1.0.0 <2.0.0'
stack_trace: ^1.6.0
Expand Down
15 changes: 15 additions & 0 deletions test/common/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'in_memory_writer.dart';

export 'assets.dart';
export 'copy_builder.dart';
export 'fake_watcher.dart';
export 'file_combiner_builder.dart';
export 'in_memory_reader.dart';
export 'in_memory_writer.dart';
Expand Down Expand Up @@ -51,3 +52,17 @@ void checkOutputs(Map<String, String> outputs, BuildResult result,
reason: 'Unexpected outputs found `$remainingOutputIds`.');
}
}

Future<BuildResult> nextResult(results) {
var done = new Completer();
var startingLength = results.length;
() async {
while (results.length == startingLength) {
await wait(10);
}
expect(results.length, startingLength + 1,
reason: 'Got two build results but only expected one');
done.complete(results.last);
}();
return done.future;
}
9 changes: 8 additions & 1 deletion test/common/copy_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@ class CopyBuilder implements Builder {
/// asset.
final AssetId copyFromAsset;

/// No `build` step will complete until this future completes. It may be
/// re-assigned in between builds.
Future blockUntil;

CopyBuilder(
{this.numCopies: 1,
this.extension: 'copy',
this.outputPackage,
this.copyFromAsset});
this.copyFromAsset,
this.blockUntil});

Future build(BuildStep buildStep) async {
if (blockUntil != null) await blockUntil;

var ids = declareOutputs(buildStep.input.id);
for (var id in ids) {
var content = copyFromAsset == null
Expand Down
Loading