forked from dart-lang/build
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial Build Daemon Support (dart-lang#1949)
* Initial Build Daemon Support
- Loading branch information
Showing
31 changed files
with
2,161 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:build_daemon/constants.dart'; | ||
import 'package:build_daemon/src/daemon.dart'; | ||
import 'package:build_daemon/daemon_builder.dart'; | ||
import 'package:watcher/watcher.dart'; | ||
|
||
/// Entrypoint for the Dart Build Daemon. | ||
Future main(List<String> args) async { | ||
var workingDirectory = args.first; | ||
|
||
var daemon = Daemon(workingDirectory); | ||
|
||
if (!daemon.tryGetLock()) { | ||
if (runningVersion(workingDirectory) == currentVersion) { | ||
print('Daemon is already running.'); | ||
print(readyToConnectLog); | ||
} else { | ||
print(versionSkew); | ||
} | ||
} else { | ||
print('Starting daemon...'); | ||
// TODO(grouma) - Create a real builder for package:build | ||
var builder = DaemonBuilder(); | ||
var watcher = Watcher(workingDirectory); | ||
await daemon.start(builder, watcher.events); | ||
print(readyToConnectLog); | ||
await daemon.onDone; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import 'dart:io'; | ||
import 'dart:math'; | ||
|
||
import 'package:build_daemon/client.dart'; | ||
|
||
void main(List<String> args) async { | ||
BuildDaemonClient client; | ||
var workingDirectory = Directory.current.path; | ||
try { | ||
client = await BuildDaemonClient.connect(workingDirectory); | ||
} on VersionSkew { | ||
print('Version skew. Please disconnect all other clients ' | ||
'before trying to start a new one.'); | ||
exit(1); | ||
} | ||
if (client == null) throw Exception('Error connecting'); | ||
print('Connected to Dart Build Daemon'); | ||
if (Random().nextBool()) { | ||
client.registerBuildTarget('/some/client/path', [r'.*_test\.dart$']); | ||
client.addBuildOptions(['--define=DART_CHECKS=none']); | ||
print('Registered example client target...'); | ||
} else { | ||
client.registerBuildTarget('/some/test/path', []); | ||
print('Registered test target...'); | ||
} | ||
client.buildResults.listen((status) => print('BUILD STATUS: $status')); | ||
client.startBuild(); | ||
await client.finished; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import 'dart:async'; | ||
import 'dart:convert'; | ||
import 'dart:io'; | ||
|
||
import 'package:web_socket_channel/io.dart'; | ||
|
||
import 'constants.dart'; | ||
import 'data/build_options_request.dart'; | ||
import 'data/build_request.dart'; | ||
import 'data/build_status.dart'; | ||
import 'data/build_target_request.dart'; | ||
import 'data/log_to_paths_request.dart'; | ||
import 'data/serializers.dart'; | ||
import 'data/server_log.dart'; | ||
|
||
class BuildDaemonClient { | ||
IOWebSocketChannel _channel; | ||
|
||
final _buildResults = StreamController<BuildResults>.broadcast(); | ||
|
||
final _serverLogStreamController = StreamController<ServerLog>.broadcast(); | ||
|
||
BuildDaemonClient._(); | ||
|
||
Stream<BuildResults> get buildResults => _buildResults.stream; | ||
Future<void> get finished async { | ||
if (_channel != null) await _channel.sink.done; | ||
} | ||
|
||
Stream<ServerLog> get serverLogs => _serverLogStreamController.stream; | ||
|
||
/// Adds a list of build options to be used for all builds. | ||
/// | ||
/// When this client disconnects these options will no longer be used by the | ||
/// build daemon. | ||
void addBuildOptions(Iterable<String> options) { | ||
var request = BuildOptionsRequest((b) => b..options.replace(options)); | ||
_channel.sink.add(jsonEncode(serializers.serialize(request))); | ||
} | ||
|
||
/// Adds paths to write usage logs to. | ||
/// | ||
/// When the client disconnects these files will no longer be logged to. | ||
void logToPaths(Iterable<String> paths) { | ||
var request = LogToPathsRequest((b) => b..paths.replace(paths)); | ||
_channel.sink.add(jsonEncode(serializers.serialize(request))); | ||
} | ||
|
||
/// Registers a build target to be built upon any file change. | ||
/// | ||
/// Changes that match the patterns in [blackListPattern] will be ignored. | ||
void registerBuildTarget(String target, Iterable<String> blackListPattern) { | ||
var request = BuildTargetRequest((b) => b | ||
..target = target | ||
..blackListPattern.replace(blackListPattern)); | ||
_channel.sink.add(jsonEncode(serializers.serialize(request))); | ||
} | ||
|
||
/// Builds all registered targets. | ||
void startBuild() { | ||
var request = BuildRequest(); | ||
_channel.sink.add(jsonEncode(serializers.serialize(request))); | ||
} | ||
|
||
Future<void> _connect(String workingDirectory, int port) async { | ||
_channel = IOWebSocketChannel.connect('ws://localhost:$port'); | ||
_channel.stream.listen(_handleServerMessage) | ||
// TODO(grouma) - Implement proper error handling. | ||
..onError(print); | ||
} | ||
|
||
void _handleServerMessage(dynamic data) { | ||
var message = serializers.deserialize(jsonDecode(data as String)); | ||
if (message is ServerLog) { | ||
_serverLogStreamController.add(message); | ||
} else if (message is BuildResults) { | ||
_buildResults.add(message); | ||
} else { | ||
// In practice we should never reach this state due to the | ||
// deserialize call. | ||
throw StateError( | ||
'Unexpected message from the Dart Build Daemon\n $message'); | ||
} | ||
} | ||
|
||
static Future<BuildDaemonClient> connect(String workingDirectory, | ||
{String daemonCommand = ''}) async { | ||
Process process; | ||
if (daemonCommand.isEmpty) { | ||
process = await Process.start( | ||
'pub', ['run', 'build_daemon', workingDirectory], | ||
mode: ProcessStartMode.detachedWithStdio); | ||
} else { | ||
process = await Process.start(daemonCommand, [workingDirectory], | ||
mode: ProcessStartMode.detachedWithStdio); | ||
} | ||
// Print errors coming from the Dart Build Daemon to help with debugging. | ||
process.stderr.transform(utf8.decoder).listen(print); | ||
var result = await process.stdout | ||
.transform(utf8.decoder) | ||
.transform(const LineSplitter()) | ||
.firstWhere((line) => line == versionSkew || line == readyToConnectLog); | ||
|
||
if (result == versionSkew) { | ||
throw VersionSkew(); | ||
} | ||
|
||
var port = _existingPort(workingDirectory); | ||
var client = BuildDaemonClient._(); | ||
await client._connect(workingDirectory, port); | ||
return client; | ||
} | ||
|
||
static int _existingPort(String workingDirectory) { | ||
var portFile = File('${daemonWorkspace(workingDirectory)}' | ||
'/.dart_build_daemon_port'); | ||
if (!portFile.existsSync()) throw Exception('Unable to read port file.'); | ||
return int.parse(portFile.readAsStringSync()); | ||
} | ||
} | ||
|
||
class VersionSkew extends Error {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import 'dart:io'; | ||
|
||
const readyToConnectLog = 'READY TO CONNECT'; | ||
|
||
const versionSkew = 'DIFFERENT RUNNING VERSION'; | ||
|
||
// TODO(grouma) - use pubspec version when this is open sourced. | ||
const currentVersion = '1.0.0'; | ||
|
||
String daemonWorkspace(String workingDirectory) => | ||
'${Directory.systemTemp.path}/dart_build_daemon/' | ||
'${workingDirectory.replaceAll("/", "_")}'; | ||
|
||
/// Used to ensure that only one instance of this daemon is running at a time. | ||
String lockFilePath(String workingDirectory) => | ||
'${daemonWorkspace(workingDirectory)}/.dart_build_lock'; | ||
|
||
/// Used to signal to clients on what port the running daemon is listening. | ||
String portFilePath(String workingDirectory) => | ||
'${daemonWorkspace(workingDirectory)}/.dart_build_daemon_port'; | ||
|
||
/// Used to signal to clients the current version of the build daemon. | ||
String versionFilePath(String workingDirectory) => | ||
'${daemonWorkspace(workingDirectory)}/.dart_build_daemon_version'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import 'dart:async'; | ||
|
||
import 'data/build_status.dart'; | ||
import 'data/server_log.dart'; | ||
|
||
class DaemonBuilder { | ||
Stream<BuildResults> get builds => Stream.empty(); | ||
|
||
Stream<ServerLog> get logs => Stream.empty(); | ||
|
||
Future<void> build( | ||
Set<String> targets, Set<String> options, Set<String> logToPaths) async {} | ||
|
||
Future<void> stop() async {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import 'package:built_collection/built_collection.dart'; | ||
import 'package:built_value/built_value.dart'; | ||
import 'package:built_value/serializer.dart'; | ||
|
||
part 'build_options_request.g.dart'; | ||
|
||
abstract class BuildOptionsRequest | ||
implements Built<BuildOptionsRequest, BuildOptionsRequestBuilder> { | ||
static Serializer<BuildOptionsRequest> get serializer => | ||
_$buildOptionsRequestSerializer; | ||
|
||
factory BuildOptionsRequest([updates(BuildOptionsRequestBuilder b)]) = | ||
_$BuildOptionsRequest; | ||
|
||
BuildOptionsRequest._(); | ||
|
||
BuiltList<String> get options; | ||
} |
Oops, something went wrong.