blob: 72651a19547bc8fc1c70e30b5fc98ae306355fef [file] [log] [blame]
// Copyright (c) 2019, 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:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:async/async.dart';
import 'package:logging/logging.dart';
import '../daemon/app_domain.dart';
import '../daemon/daemon.dart';
import '../daemon/daemon_domain.dart';
import '../logging.dart';
import '../pubspec.dart';
import '../serve/dev_workflow.dart';
import '../serve/utils.dart';
import 'configuration.dart';
import 'shared.dart';
Stream<Map<String, dynamic>> get _stdinCommandStream => stdin
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.where((String line) => line.startsWith('[{') && line.endsWith('}]'))
.map<Map<String, dynamic>>((String line) {
line = line.substring(1, line.length - 1);
return json.decode(line) as Map<String, dynamic>;
});
void _stdoutCommandResponse(Map<String, dynamic> command) {
stdout.writeln('[${json.encode(command)}]');
}
/// A mode for running WebDev from command-line tools.
///
/// Communication happens over STDIO using JSON-RPC.
///
/// This supports a subset of:
/// https://github.com/flutter/flutter/blob/master/packages/flutter_tools/doc/daemon.md
class DaemonCommand extends Command<int> {
@override
final name = 'daemon';
@override
final hidden = true;
@override
String get description =>
'A mode for running WebDev from command-line tools.';
@override
final argParser = ArgParser()
..addMultiOption('launch-app', help: 'The html file to launch in chrome.');
DaemonCommand() {
addSharedArgs(argParser, releaseDefault: false);
}
@override
Future<int> run() async {
final configuration = Configuration.fromArgs(argResults,
defaultConfiguration: Configuration(
launchInChrome: true, debug: true, autoRun: false, release: false));
configureLogWriter(configuration.verbose);
// Validate the pubspec first to ensure we are in a Dart project.
try {
await validatePubspecLock(configuration);
} on PackageException catch (e) {
logWriter(Level.SEVERE, 'Pubspec errors: ', error: '${e.details}');
rethrow;
}
Daemon? daemon;
DevWorkflow? workflow;
var cancelCount = 0;
final cancelSub = StreamGroup.merge([
ProcessSignal.sigint.watch(),
// SIGTERM is not supported on Windows.
Platform.isWindows ? const Stream.empty() : ProcessSignal.sigterm.watch()
]).listen((signal) async {
cancelCount++;
daemon?.shutdown();
if (cancelCount > 1) exit(1);
});
try {
daemon = Daemon(_stdinCommandStream, _stdoutCommandResponse);
final daemonDomain = DaemonDomain(daemon);
configureLogWriter(configuration.verbose, customLogWriter:
(level, message, {loggerName, error, stackTrace, verbose}) {
if (configuration.verbose || level >= Level.INFO) {
daemonDomain.sendEvent('daemon.log', {
'log': formatLog(
level,
message,
loggerName: loggerName,
error: error,
stackTrace: stackTrace,
)
});
}
});
daemon.registerDomain(daemonDomain);
final buildOptions = buildRunnerArgs(configuration);
final extraArgs = argResults?.rest ?? [];
final directoryArgs =
extraArgs.where((arg) => !arg.startsWith('-')).toList();
final targetPorts =
parseDirectoryArgs(directoryArgs, basePort: await findUnusedPort());
validateLaunchApps(configuration.launchApps, targetPorts.keys);
workflow =
await DevWorkflow.start(configuration, buildOptions, targetPorts);
daemon.registerDomain(AppDomain(daemon, workflow.serverManager));
await daemon.onExit;
exitCode = 0;
return 0;
} catch (e) {
daemon?.shutdown();
exitCode = 1;
rethrow;
} finally {
await workflow?.shutDown();
// Only cancel this subscription after all shutdown work has completed.
// https://github.com/dart-lang/sdk/issues/23074.
await cancelSub.cancel();
// For some reason Windows remains open due to what appears to be an
// undrained `stdin`. Feel free to waste some time trying to remove this.
if (Platform.isWindows) exit(exitCode);
}
}
}