| Index: pkg/fletchc/lib/src/worker/developer.dart
|
| diff --git a/pkg/fletchc/lib/src/worker/developer.dart b/pkg/fletchc/lib/src/worker/developer.dart
|
| deleted file mode 100644
|
| index 98ca9081031af6419f79fe4cc96c468e44559cb2..0000000000000000000000000000000000000000
|
| --- a/pkg/fletchc/lib/src/worker/developer.dart
|
| +++ /dev/null
|
| @@ -1,1569 +0,0 @@
|
| -// Copyright (c) 2015, the Dartino 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.md file.
|
| -
|
| -library fletchc.worker.developer;
|
| -
|
| -import 'dart:async' show
|
| - Future,
|
| - Stream,
|
| - StreamController,
|
| - Timer;
|
| -
|
| -import 'dart:convert' show
|
| - JSON,
|
| - JsonEncoder,
|
| - UTF8;
|
| -
|
| -import 'dart:io' show
|
| - Directory,
|
| - File,
|
| - FileSystemEntity,
|
| - InternetAddress,
|
| - Platform,
|
| - Process,
|
| - Socket,
|
| - SocketException;
|
| -
|
| -import 'package:sdk_library_metadata/libraries.dart' show
|
| - Category;
|
| -
|
| -import 'package:sdk_services/sdk_services.dart' show
|
| - OutputService,
|
| - SDKServices;
|
| -
|
| -import 'package:fletch_agent/agent_connection.dart' show
|
| - AgentConnection,
|
| - AgentException,
|
| - VmData;
|
| -
|
| -import 'package:fletch_agent/messages.dart' show
|
| - AGENT_DEFAULT_PORT,
|
| - MessageDecodeException;
|
| -
|
| -import 'package:mdns/mdns.dart' show
|
| - MDnsClient,
|
| - ResourceRecord,
|
| - RRType;
|
| -
|
| -import 'package:path/path.dart' show
|
| - join;
|
| -
|
| -import '../../vm_commands.dart' show
|
| - VmCommandCode,
|
| - ConnectionError,
|
| - Debugging,
|
| - HandShakeResult,
|
| - ProcessBacktrace,
|
| - ProcessBacktraceRequest,
|
| - ProcessRun,
|
| - ProcessSpawnForMain,
|
| - SessionEnd,
|
| - WriteSnapshotResult;
|
| -
|
| -import '../../program_info.dart' show
|
| - Configuration,
|
| - ProgramInfo,
|
| - ProgramInfoBinary,
|
| - ProgramInfoJson,
|
| - buildProgramInfo;
|
| -
|
| -import '../hub/session_manager.dart' show
|
| - FletchVm,
|
| - SessionState,
|
| - Sessions;
|
| -
|
| -import '../hub/client_commands.dart' show
|
| - ClientCommandCode,
|
| - handleSocketErrors;
|
| -
|
| -import '../verbs/infrastructure.dart' show
|
| - ClientCommand,
|
| - CommandSender,
|
| - DiagnosticKind,
|
| - FletchCompiler,
|
| - FletchDelta,
|
| - IncrementalCompiler,
|
| - WorkerConnection,
|
| - IsolatePool,
|
| - Session,
|
| - SharedTask,
|
| - StreamIterator,
|
| - throwFatalError;
|
| -
|
| -import '../../incremental/fletchc_incremental.dart' show
|
| - IncrementalCompilationFailed,
|
| - IncrementalMode,
|
| - parseIncrementalMode,
|
| - unparseIncrementalMode;
|
| -
|
| -export '../../incremental/fletchc_incremental.dart' show
|
| - IncrementalMode;
|
| -
|
| -import '../../fletch_compiler.dart' show fletchDeviceType;
|
| -
|
| -import '../hub/exit_codes.dart' as exit_codes;
|
| -
|
| -import '../../fletch_system.dart' show
|
| - FletchFunction,
|
| - FletchSystem;
|
| -
|
| -import '../../bytecodes.dart' show
|
| - Bytecode,
|
| - MethodEnd;
|
| -
|
| -import '../diagnostic.dart' show
|
| - throwInternalError;
|
| -
|
| -import '../guess_configuration.dart' show
|
| - executable,
|
| - fletchVersion,
|
| - guessFletchVm;
|
| -
|
| -import '../device_type.dart' show
|
| - DeviceType,
|
| - parseDeviceType,
|
| - unParseDeviceType;
|
| -
|
| -export '../device_type.dart' show
|
| - DeviceType;
|
| -
|
| -import '../please_report_crash.dart' show
|
| - pleaseReportCrash;
|
| -
|
| -import '../../debug_state.dart' as debug show
|
| - RemoteObject,
|
| - BackTrace;
|
| -
|
| -typedef Future<Null> ClientEventHandler(Session session);
|
| -
|
| -Uri configFileUri;
|
| -
|
| -Future<Socket> connect(
|
| - String host,
|
| - int port,
|
| - DiagnosticKind kind,
|
| - String socketDescription,
|
| - SessionState state) async {
|
| - // We are using .catchError rather than try/catch because we have seen
|
| - // incorrect stack traces using the latter.
|
| - Socket socket = await Socket.connect(host, port).catchError(
|
| - (SocketException error) {
|
| - String message = error.message;
|
| - if (error.osError != null) {
|
| - message = error.osError.message;
|
| - }
|
| - throwFatalError(kind, address: '$host:$port', message: message);
|
| - }, test: (e) => e is SocketException);
|
| - handleSocketErrors(socket, socketDescription, log: (String info) {
|
| - state.log("Connected to TCP $socketDescription $info");
|
| - });
|
| - return socket;
|
| -}
|
| -
|
| -Future<AgentConnection> connectToAgent(SessionState state) async {
|
| - // TODO(wibling): need to make sure the agent is running.
|
| - assert(state.settings.deviceAddress != null);
|
| - String host = state.settings.deviceAddress.host;
|
| - int agentPort = state.settings.deviceAddress.port;
|
| - Socket socket = await connect(
|
| - host, agentPort, DiagnosticKind.socketAgentConnectError,
|
| - "agentSocket", state);
|
| - return new AgentConnection(socket);
|
| -}
|
| -
|
| -/// Return the result of a function in the context of an open [AgentConnection].
|
| -///
|
| -/// The result is a [Future] of this value.
|
| -/// This function handles [AgentException] and [MessageDecodeException].
|
| -Future withAgentConnection(
|
| - SessionState state,
|
| - Future f(AgentConnection connection)) async {
|
| - AgentConnection connection = await connectToAgent(state);
|
| - try {
|
| - return await f(connection);
|
| - } on AgentException catch (error) {
|
| - throwFatalError(
|
| - DiagnosticKind.socketAgentReplyError,
|
| - address: '${connection.socket.remoteAddress.host}:'
|
| - '${connection.socket.remotePort}',
|
| - message: error.message);
|
| - } on MessageDecodeException catch (error) {
|
| - throwFatalError(
|
| - DiagnosticKind.socketAgentReplyError,
|
| - address: '${connection.socket.remoteAddress.host}:'
|
| - '${connection.socket.remotePort}',
|
| - message: error.message);
|
| - } finally {
|
| - disconnectFromAgent(connection);
|
| - }
|
| -}
|
| -
|
| -void disconnectFromAgent(AgentConnection connection) {
|
| - assert(connection.socket != null);
|
| - connection.socket.close();
|
| -}
|
| -
|
| -Future<Null> checkAgentVersion(Uri base, SessionState state) async {
|
| - String deviceFletchVersion = await withAgentConnection(state,
|
| - (connection) => connection.fletchVersion());
|
| - Uri packageFile = await lookForAgentPackage(base, version: fletchVersion);
|
| - String fixit;
|
| - if (packageFile != null) {
|
| - fixit = "Try running\n"
|
| - " 'fletch x-upgrade agent in session ${state.name}'.";
|
| - } else {
|
| - fixit = "Try downloading a matching SDK and running\n"
|
| - " 'fletch x-upgrade agent in session ${state.name}'\n"
|
| - "from the SDK's root directory.";
|
| - }
|
| -
|
| - if (fletchVersion != deviceFletchVersion) {
|
| - throwFatalError(DiagnosticKind.agentVersionMismatch,
|
| - userInput: fletchVersion,
|
| - additionalUserInput: deviceFletchVersion,
|
| - fixit: fixit);
|
| - }
|
| -}
|
| -
|
| -Future<Null> startAndAttachViaAgent(Uri base, SessionState state) async {
|
| - // TODO(wibling): integrate with the FletchVm class, e.g. have a
|
| - // AgentFletchVm and LocalFletchVm that both share the same interface
|
| - // where the former is interacting with the agent.
|
| - await checkAgentVersion(base, state);
|
| - VmData vmData = await withAgentConnection(state,
|
| - (connection) => connection.startVm());
|
| - state.fletchAgentVmId = vmData.id;
|
| - String host = state.settings.deviceAddress.host;
|
| - await attachToVm(host, vmData.port, state);
|
| - await state.session.disableVMStandardOutput();
|
| -}
|
| -
|
| -Future<Null> startAndAttachDirectly(SessionState state, Uri base) async {
|
| - String fletchVmPath = state.compilerHelper.fletchVm.toFilePath();
|
| - state.fletchVm = await FletchVm.start(fletchVmPath, workingDirectory: base);
|
| - await attachToVm(state.fletchVm.host, state.fletchVm.port, state);
|
| - await state.session.disableVMStandardOutput();
|
| -}
|
| -
|
| -Future<Null> attachToVm(String host, int port, SessionState state) async {
|
| - Socket socket = await connect(
|
| - host, port, DiagnosticKind.socketVmConnectError, "vmSocket", state);
|
| -
|
| - Session session = new Session(socket, state.compiler, state.stdoutSink,
|
| - state.stderrSink, null);
|
| -
|
| - // Perform handshake with VM which validates that VM and compiler
|
| - // have the same versions.
|
| - HandShakeResult handShakeResult = await session.handShake(fletchVersion);
|
| - if (handShakeResult == null) {
|
| - throwFatalError(DiagnosticKind.handShakeFailed, address: '$host:$port');
|
| - }
|
| - if (!handShakeResult.success) {
|
| - throwFatalError(DiagnosticKind.versionMismatch,
|
| - address: '$host:$port',
|
| - userInput: fletchVersion,
|
| - additionalUserInput: handShakeResult.version);
|
| - }
|
| -
|
| - // Enable debugging to be able to communicate with VM when there
|
| - // are errors.
|
| - await session.runCommand(const Debugging());
|
| -
|
| - state.session = session;
|
| -}
|
| -
|
| -Future<int> compile(
|
| - Uri script,
|
| - SessionState state,
|
| - Uri base,
|
| - {bool analyzeOnly: false,
|
| - bool fatalIncrementalFailures: false}) async {
|
| - IncrementalCompiler compiler = state.compiler;
|
| - if (!compiler.isProductionModeEnabled) {
|
| - state.resetCompiler();
|
| - }
|
| - Uri firstScript = state.script;
|
| - List<FletchDelta> previousResults = state.compilationResults;
|
| -
|
| - FletchDelta newResult;
|
| - try {
|
| - if (analyzeOnly) {
|
| - state.resetCompiler();
|
| - state.log("Analyzing '$script'");
|
| - return await compiler.analyze(script, base);
|
| - } else if (previousResults.isEmpty) {
|
| - state.script = script;
|
| - await compiler.compile(script, base);
|
| - newResult = compiler.computeInitialDelta();
|
| - } else {
|
| - try {
|
| - state.log("Compiling difference from $firstScript to $script");
|
| - newResult = await compiler.compileUpdates(
|
| - previousResults.last.system, <Uri, Uri>{firstScript: script},
|
| - logTime: state.log, logVerbose: state.log);
|
| - } on IncrementalCompilationFailed catch (error) {
|
| - state.log(error);
|
| - state.resetCompiler();
|
| - if (fatalIncrementalFailures) {
|
| - print(error);
|
| - state.log(
|
| - "Aborting compilation due to --fatal-incremental-failures...");
|
| - return exit_codes.INCREMENTAL_COMPILER_FAILED;
|
| - }
|
| - state.log("Attempting full compile...");
|
| - state.script = script;
|
| - await compiler.compile(script, base);
|
| - newResult = compiler.computeInitialDelta();
|
| - }
|
| - }
|
| - } catch (error, stackTrace) {
|
| - pleaseReportCrash(error, stackTrace);
|
| - return exit_codes.COMPILER_EXITCODE_CRASH;
|
| - }
|
| - if (newResult == null) {
|
| - return exit_codes.DART_VM_EXITCODE_COMPILE_TIME_ERROR;
|
| - }
|
| -
|
| - state.addCompilationResult(newResult);
|
| -
|
| - state.log("Compiled '$script' to ${newResult.commands.length} commands");
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -Future<Settings> readSettings(Uri uri) async {
|
| - if (await new File.fromUri(uri).exists()) {
|
| - String jsonLikeData = await new File.fromUri(uri).readAsString();
|
| - return parseSettings(jsonLikeData, uri);
|
| - } else {
|
| - return null;
|
| - }
|
| -}
|
| -
|
| -Future<Uri> findFile(Uri cwd, String fileName) async {
|
| - Uri uri = cwd.resolve(fileName);
|
| - while (true) {
|
| - if (await new File.fromUri(uri).exists()) return uri;
|
| - if (uri.pathSegments.length <= 1) return null;
|
| - uri = uri.resolve('../$fileName');
|
| - }
|
| -}
|
| -
|
| -Future<Settings> createSettings(
|
| - String sessionName,
|
| - Uri uri,
|
| - Uri cwd,
|
| - Uri configFileUri,
|
| - CommandSender commandSender,
|
| - StreamIterator<ClientCommand> commandIterator) async {
|
| - bool userProvidedSettings = uri != null;
|
| - if (!userProvidedSettings) {
|
| - // Try to find a $sessionName.fletch-settings file starting from the current
|
| - // working directory and walking up its parent directories.
|
| - uri = await findFile(cwd, '$sessionName.fletch-settings');
|
| -
|
| - // If no $sessionName.fletch-settings file is found, try to find the
|
| - // settings template file (in the SDK or git repo) by looking for a
|
| - // .fletch-settings file starting from the fletch executable's directory
|
| - // and walking up its parent directory chain.
|
| - if (uri == null) {
|
| - uri = await findFile(executable, '.fletch-settings');
|
| - if (uri != null) print('Using template settings file $uri');
|
| - }
|
| - }
|
| -
|
| - Settings settings = new Settings.empty();
|
| - if (uri != null) {
|
| - String jsonLikeData = await new File.fromUri(uri).readAsString();
|
| - settings = parseSettings(jsonLikeData, uri);
|
| - }
|
| - if (userProvidedSettings) return settings;
|
| -
|
| - // TODO(wibling): get rid of below special handling of the sessions 'remote'
|
| - // and 'local' and come up with a fletch project concept that can contain
|
| - // these settings.
|
| - Uri packagesUri;
|
| - Address address;
|
| - switch (sessionName) {
|
| - case "remote":
|
| - uri = configFileUri.resolve("remote.fletch-settings");
|
| - Settings remoteSettings = await readSettings(uri);
|
| - if (remoteSettings != null) return remoteSettings;
|
| - packagesUri = executable.resolve("fletch-sdk.packages");
|
| - address = await readAddressFromUser(commandSender, commandIterator);
|
| - if (address == null) {
|
| - // Assume user aborted data entry.
|
| - return settings;
|
| - }
|
| - break;
|
| -
|
| - case "local":
|
| - uri = configFileUri.resolve("local.fletch-settings");
|
| - Settings localSettings = await readSettings(uri);
|
| - if (localSettings != null) return localSettings;
|
| - // TODO(ahe): Use mock packages here.
|
| - packagesUri = executable.resolve("fletch-sdk.packages");
|
| - break;
|
| -
|
| - default:
|
| - return settings;
|
| - }
|
| -
|
| - if (!await new File.fromUri(packagesUri).exists()) {
|
| - packagesUri = null;
|
| - }
|
| - settings = settings.copyWith(packages: packagesUri, deviceAddress: address);
|
| - print("Created settings file '$uri'");
|
| - await new File.fromUri(uri).writeAsString(
|
| - "${const JsonEncoder.withIndent(' ').convert(settings)}\n");
|
| - return settings;
|
| -}
|
| -
|
| -Future<Address> readAddressFromUser(
|
| - CommandSender commandSender,
|
| - StreamIterator<ClientCommand> commandIterator) async {
|
| - String message = "Please enter IP address of remote device "
|
| - "(press Enter to search for devices):";
|
| - commandSender.sendStdout(message);
|
| - // The list of devices found by running discovery.
|
| - List<InternetAddress> devices = <InternetAddress>[];
|
| - while (await commandIterator.moveNext()) {
|
| - ClientCommand command = commandIterator.current;
|
| - switch (command.code) {
|
| - case ClientCommandCode.Stdin:
|
| - if (command.data.length == 0) {
|
| - // TODO(ahe): It may be safe to return null here, but we need to
|
| - // check how this interacts with the debugger's InputHandler.
|
| - throwInternalError("Unexpected end of input");
|
| - }
|
| - // TODO(ahe): This assumes that the user's input arrives as one
|
| - // message. It is relatively safe to assume this for a normal terminal
|
| - // session because we use canonical input processing (Unix line
|
| - // buffering), but it doesn't work in general. So we should fix that.
|
| - String line = UTF8.decode(command.data).trim();
|
| - if (line.isEmpty && devices.isEmpty) {
|
| - commandSender.sendStdout("\n");
|
| - // [discoverDevices] will print out the list of device with their
|
| - // IP address, hostname, and agent version.
|
| - devices = await discoverDevices(prefixWithNumber: true);
|
| - if (devices.isEmpty) {
|
| - commandSender.sendStdout(
|
| - "Couldn't find Fletch capable devices\n");
|
| - commandSender.sendStdout(message);
|
| - } else {
|
| - if (devices.length == 1) {
|
| - commandSender.sendStdout("\n");
|
| - commandSender.sendStdout("Press Enter to use this device");
|
| - } else {
|
| - commandSender.sendStdout("\n");
|
| - commandSender.sendStdout(
|
| - "Found ${devices.length} Fletch capable devices\n");
|
| - commandSender.sendStdout(
|
| - "Please enter the number or the IP address of "
|
| - "the remote device you would like to use "
|
| - "(press Enter to use the first device): ");
|
| - }
|
| - }
|
| - } else {
|
| - bool checkedIndex = false;
|
| - if (devices.length > 0) {
|
| - if (line.isEmpty) {
|
| - return new Address(devices[0].address, AGENT_DEFAULT_PORT);
|
| - }
|
| - try {
|
| - checkedIndex = true;
|
| - int index = int.parse(line);
|
| - if (1 <= index && index <= devices.length) {
|
| - return new Address(devices[index - 1].address,
|
| - AGENT_DEFAULT_PORT);
|
| - } else {
|
| - commandSender.sendStdout("Invalid device index $line\n\n");
|
| - commandSender.sendStdout(message);
|
| - }
|
| - } on FormatException {
|
| - // Ignore FormatException and fall through to parse as IP address.
|
| - }
|
| - }
|
| - if (!checkedIndex) {
|
| - return parseAddress(line, defaultPort: AGENT_DEFAULT_PORT);
|
| - }
|
| - }
|
| - break;
|
| -
|
| - default:
|
| - throwInternalError("Unexpected ${command.code}");
|
| - return null;
|
| - }
|
| - }
|
| - return null;
|
| -}
|
| -
|
| -SessionState createSessionState(
|
| - String name,
|
| - Settings settings,
|
| - {Uri libraryRoot,
|
| - Uri fletchVm,
|
| - Uri nativesJson}) {
|
| - if (settings == null) {
|
| - settings = const Settings.empty();
|
| - }
|
| - List<String> compilerOptions = const bool.fromEnvironment("fletchc-verbose")
|
| - ? <String>['--verbose'] : <String>[];
|
| - compilerOptions.addAll(settings.options);
|
| - Uri packageConfig = settings.packages;
|
| - if (packageConfig == null) {
|
| - packageConfig = executable.resolve("fletch-sdk.packages");
|
| - }
|
| -
|
| - DeviceType deviceType = settings.deviceType ??
|
| - parseDeviceType(fletchDeviceType);
|
| -
|
| - String platform = (deviceType == DeviceType.embedded)
|
| - ? "fletch_embedded.platform"
|
| - : "fletch_mobile.platform";
|
| -
|
| - FletchCompiler compilerHelper = new FletchCompiler(
|
| - options: compilerOptions,
|
| - packageConfig: packageConfig,
|
| - environment: settings.constants,
|
| - platform: platform,
|
| - libraryRoot: libraryRoot,
|
| - fletchVm: fletchVm,
|
| - nativesJson: nativesJson);
|
| -
|
| - return new SessionState(
|
| - name, compilerHelper,
|
| - compilerHelper.newIncrementalCompiler(settings.incrementalMode),
|
| - settings);
|
| -}
|
| -
|
| -Future runWithDebugger(
|
| - List<String> commands,
|
| - Session session,
|
| - SessionState state) async {
|
| -
|
| - // Method used to generate the debugger commands if none are specified.
|
| - Stream<String> inputGenerator() async* {
|
| - yield 't verbose';
|
| - yield 'b main';
|
| - yield 'r';
|
| - while (!session.terminated) {
|
| - yield 's';
|
| - }
|
| - }
|
| -
|
| - return commands.isEmpty ?
|
| - session.debug(inputGenerator(), Uri.base, state, echo: true) :
|
| - session.debug(
|
| - new Stream<String>.fromIterable(commands), Uri.base, state,
|
| - echo: true);
|
| -}
|
| -
|
| -Future<int> run(
|
| - SessionState state,
|
| - {List<String> testDebuggerCommands,
|
| - bool terminateDebugger: true}) async {
|
| - List<FletchDelta> compilationResults = state.compilationResults;
|
| - Session session = state.session;
|
| -
|
| - for (FletchDelta delta in compilationResults) {
|
| - await session.applyDelta(delta);
|
| - }
|
| -
|
| - if (testDebuggerCommands != null) {
|
| - await runWithDebugger(testDebuggerCommands, session, state);
|
| - return 0;
|
| - }
|
| -
|
| - session.silent = true;
|
| -
|
| - await session.enableDebugger();
|
| - await session.spawnProcess();
|
| - var command = await session.debugRun();
|
| -
|
| - int exitCode = exit_codes.COMPILER_EXITCODE_CRASH;
|
| - if (command == null) {
|
| - await session.kill();
|
| - await session.shutdown();
|
| - throwInternalError("No command received from Fletch VM");
|
| - }
|
| -
|
| - Future printException() async {
|
| - if (!session.loaded) {
|
| - print('### process not loaded, cannot print uncaught exception');
|
| - return;
|
| - }
|
| - debug.RemoteObject exception = await session.uncaughtException();
|
| - if (exception != null) {
|
| - print(session.exceptionToString(exception));
|
| - }
|
| - }
|
| -
|
| - Future printTrace() async {
|
| - if (!session.loaded) {
|
| - print("### process not loaded, cannot print stacktrace and code");
|
| - return;
|
| - }
|
| - debug.BackTrace stackTrace = await session.backTrace();
|
| - if (stackTrace != null) {
|
| - print(stackTrace.format());
|
| - print(stackTrace.list(state));
|
| - }
|
| - }
|
| -
|
| - try {
|
| - switch (command.code) {
|
| - case VmCommandCode.UncaughtException:
|
| - state.log("Uncaught error");
|
| - exitCode = exit_codes.DART_VM_EXITCODE_UNCAUGHT_EXCEPTION;
|
| - await printException();
|
| - await printTrace();
|
| - // TODO(ahe): Need to continue to unwind stack.
|
| - break;
|
| - case VmCommandCode.ProcessCompileTimeError:
|
| - state.log("Compile-time error");
|
| - exitCode = exit_codes.DART_VM_EXITCODE_COMPILE_TIME_ERROR;
|
| - await printTrace();
|
| - // TODO(ahe): Continue to unwind stack?
|
| - break;
|
| -
|
| - case VmCommandCode.ProcessTerminated:
|
| - exitCode = 0;
|
| - break;
|
| -
|
| - case VmCommandCode.ConnectionError:
|
| - state.log("Error on connection to Fletch VM: ${command.error}");
|
| - exitCode = exit_codes.COMPILER_EXITCODE_CONNECTION_ERROR;
|
| - break;
|
| -
|
| - default:
|
| - throwInternalError("Unexpected result from Fletch VM: '$command'");
|
| - break;
|
| - }
|
| - } finally {
|
| - if (terminateDebugger) {
|
| - await state.terminateSession();
|
| - } else {
|
| - // If the session terminated due to a ConnectionError or the program
|
| - // finished don't reuse the state's session.
|
| - if (session.terminated) {
|
| - state.session = null;
|
| - }
|
| - session.silent = false;
|
| - }
|
| - };
|
| -
|
| - return exitCode;
|
| -}
|
| -
|
| -Future<int> export(SessionState state,
|
| - Uri snapshot,
|
| - {bool binaryProgramInfo: false}) async {
|
| - List<FletchDelta> compilationResults = state.compilationResults;
|
| - Session session = state.session;
|
| - state.session = null;
|
| -
|
| - for (FletchDelta delta in compilationResults) {
|
| - await session.applyDelta(delta);
|
| - }
|
| -
|
| - var result = await session.writeSnapshot(snapshot.toFilePath());
|
| - if (result is WriteSnapshotResult) {
|
| - WriteSnapshotResult snapshotResult = result;
|
| -
|
| - await session.shutdown();
|
| -
|
| - ProgramInfo info =
|
| - buildProgramInfo(compilationResults.last.system, snapshotResult);
|
| -
|
| - File jsonFile = new File('${snapshot.toFilePath()}.info.json');
|
| - await jsonFile.writeAsString(ProgramInfoJson.encode(info));
|
| -
|
| - if (binaryProgramInfo) {
|
| - File binFile = new File('${snapshot.toFilePath()}.info.bin');
|
| - await binFile.writeAsBytes(ProgramInfoBinary.encode(info));
|
| - }
|
| -
|
| - return 0;
|
| - } else {
|
| - assert(result is ConnectionError);
|
| - print("There was a connection error while writing the snapshot.");
|
| - return exit_codes.COMPILER_EXITCODE_CONNECTION_ERROR;
|
| - }
|
| -}
|
| -
|
| -Future<int> compileAndAttachToVmThen(
|
| - CommandSender commandSender,
|
| - StreamIterator<ClientCommand> commandIterator,
|
| - SessionState state,
|
| - Uri script,
|
| - Uri base,
|
| - bool waitForVmExit,
|
| - Future<int> action(),
|
| - {ClientEventHandler eventHandler}) async {
|
| - bool startedVmDirectly = false;
|
| - List<FletchDelta> compilationResults = state.compilationResults;
|
| - if (compilationResults.isEmpty || script != null) {
|
| - if (script == null) {
|
| - throwFatalError(DiagnosticKind.noFileTarget);
|
| - }
|
| - int exitCode = await compile(script, state, base);
|
| - if (exitCode != 0) return exitCode;
|
| - compilationResults = state.compilationResults;
|
| - assert(compilationResults != null);
|
| - }
|
| -
|
| - Session session = state.session;
|
| - if (session != null && session.loaded) {
|
| - // We cannot reuse a session that has already been loaded. Loading
|
| - // currently implies that some of the code has been run.
|
| - if (state.explicitAttach) {
|
| - // If the user explicitly called 'fletch attach' we cannot
|
| - // create a new VM session since we don't know if the vm is
|
| - // running locally or remotely and if running remotely there
|
| - // is no guarantee there is an agent to start a new vm.
|
| - //
|
| - // The UserSession is invalid in its current state as the
|
| - // vm session (aka. session in the code here) has already
|
| - // been loaded and run some code.
|
| - throwFatalError(DiagnosticKind.sessionInvalidState,
|
| - sessionName: state.name);
|
| - }
|
| - state.log('Cannot reuse existing VM session, creating new.');
|
| - await state.terminateSession();
|
| - session = null;
|
| - }
|
| - if (session == null) {
|
| - if (state.settings.deviceAddress != null) {
|
| - await startAndAttachViaAgent(base, state);
|
| - // TODO(wibling): read stdout from agent.
|
| - } else {
|
| - startedVmDirectly = true;
|
| - await startAndAttachDirectly(state, base);
|
| - state.fletchVm.stdoutLines.listen((String line) {
|
| - commandSender.sendStdout("$line\n");
|
| - });
|
| - state.fletchVm.stderrLines.listen((String line) {
|
| - commandSender.sendStderr("$line\n");
|
| - });
|
| - }
|
| - session = state.session;
|
| - assert(session != null);
|
| - }
|
| -
|
| - eventHandler ??= defaultClientEventHandler(state, commandIterator);
|
| - setupClientInOut(state, commandSender, eventHandler);
|
| -
|
| - int exitCode = exit_codes.COMPILER_EXITCODE_CRASH;
|
| - try {
|
| - exitCode = await action();
|
| - } catch (error, trace) {
|
| - print(error);
|
| - if (trace != null) {
|
| - print(trace);
|
| - }
|
| - } finally {
|
| - if (waitForVmExit && startedVmDirectly) {
|
| - exitCode = await state.fletchVm.exitCode;
|
| - }
|
| - state.detachCommandSender();
|
| - }
|
| - return exitCode;
|
| -}
|
| -
|
| -void setupClientInOut(
|
| - SessionState state,
|
| - CommandSender commandSender,
|
| - ClientEventHandler eventHandler) {
|
| - // Forward output going into the state's outputSink using the passed in
|
| - // commandSender. This typically forwards output to the hub (main isolate)
|
| - // which forwards it on to stdout of the Fletch C++ client.
|
| - state.attachCommandSender(commandSender);
|
| -
|
| - // Start event handling for input passed from the Fletch C++ client.
|
| - eventHandler(state.session);
|
| -
|
| - // Let the hub (main isolate) know that event handling has been started.
|
| - commandSender.sendEventLoopStarted();
|
| -}
|
| -
|
| -/// Return a default client event handler bound to the current session's
|
| -/// commandIterator and state.
|
| -/// This handler only takes care of signals coming from the client.
|
| -ClientEventHandler defaultClientEventHandler(
|
| - SessionState state,
|
| - StreamIterator<ClientCommand> commandIterator) {
|
| - return (Session session) async {
|
| - while (await commandIterator.moveNext()) {
|
| - ClientCommand command = commandIterator.current;
|
| - switch (command.code) {
|
| - case ClientCommandCode.Signal:
|
| - int signalNumber = command.data;
|
| - handleSignal(state, signalNumber);
|
| - break;
|
| - default:
|
| - state.log("Unhandled command from client: $command");
|
| - }
|
| - }
|
| - };
|
| -}
|
| -
|
| -void handleSignal(SessionState state, int signalNumber) {
|
| - state.log("Received signal $signalNumber");
|
| - if (!state.hasRemoteVm && state.fletchVm == null) {
|
| - // This can happen if a user has attached to a vm using the "attach" verb
|
| - // in which case we don't forward the signal to the vm.
|
| - // TODO(wibling): Determine how to interpret the signal for the persistent
|
| - // process.
|
| - state.log('Signal $signalNumber ignored. VM was manually attached.');
|
| - print('Signal $signalNumber ignored. VM was manually attached.');
|
| - return;
|
| - }
|
| - if (state.hasRemoteVm) {
|
| - signalAgentVm(state, signalNumber);
|
| - } else {
|
| - assert(state.fletchVm.process != null);
|
| - int vmPid = state.fletchVm.process.pid;
|
| - Process.runSync("kill", ["-$signalNumber", "$vmPid"]);
|
| - }
|
| -}
|
| -
|
| -Future signalAgentVm(SessionState state, int signalNumber) async {
|
| - await withAgentConnection(state, (connection) {
|
| - return connection.signalVm(state.fletchAgentVmId, signalNumber);
|
| - });
|
| -}
|
| -
|
| -String extractVersion(Uri uri) {
|
| - List<String> nameParts = uri.pathSegments.last.split('_');
|
| - if (nameParts.length != 3 || nameParts[0] != 'fletch-agent') {
|
| - throwFatalError(DiagnosticKind.upgradeInvalidPackageName);
|
| - }
|
| - String version = nameParts[1];
|
| - // create_debian_packages.py adds a '-1' after the hash in the package name.
|
| - if (version.endsWith('-1')) {
|
| - version = version.substring(0, version.length - 2);
|
| - }
|
| - return version;
|
| -}
|
| -
|
| -/// Try to locate an Fletch agent package file assuming the normal SDK layout
|
| -/// with SDK base directory [base].
|
| -///
|
| -/// If the parameter [version] is passed, the Uri is only returned, if
|
| -/// the version matches.
|
| -Future<Uri> lookForAgentPackage(Uri base, {String version}) async {
|
| - String platform = "raspberry-pi2";
|
| - Uri platformUri = base.resolve("platforms/$platform");
|
| - Directory platformDir = new Directory.fromUri(platformUri);
|
| -
|
| - // Try to locate the agent package in the SDK for the selected platform.
|
| - Uri sdkAgentPackage;
|
| - if (await platformDir.exists()) {
|
| - for (FileSystemEntity entry in platformDir.listSync()) {
|
| - Uri uri = entry.uri;
|
| - String name = uri.pathSegments.last;
|
| - if (name.startsWith('fletch-agent') &&
|
| - name.endsWith('.deb') &&
|
| - (version == null || extractVersion(uri) == version)) {
|
| - return uri;
|
| - }
|
| - }
|
| - }
|
| - return null;
|
| -}
|
| -
|
| -Future<Uri> readPackagePathFromUser(
|
| - Uri base,
|
| - CommandSender commandSender,
|
| - StreamIterator<ClientCommand> commandIterator) async {
|
| - Uri sdkAgentPackage = await lookForAgentPackage(base);
|
| - if (sdkAgentPackage != null) {
|
| - String path = sdkAgentPackage.toFilePath();
|
| - commandSender.sendStdout("Found SDK package: $path\n");
|
| - commandSender.sendStdout("Press Enter to use this package to upgrade "
|
| - "or enter the path to another package file:\n");
|
| - } else {
|
| - commandSender.sendStdout("Please enter the path to the package file "
|
| - "you want to use:\n");
|
| - }
|
| -
|
| - while (await commandIterator.moveNext()) {
|
| - ClientCommand command = commandIterator.current;
|
| - switch (command.code) {
|
| - case ClientCommandCode.Stdin:
|
| - if (command.data.length == 0) {
|
| - throwInternalError("Unexpected end of input");
|
| - }
|
| - // TODO(karlklose): This assumes that the user's input arrives as one
|
| - // message. It is relatively safe to assume this for a normal terminal
|
| - // session because we use canonical input processing (Unix line
|
| - // buffering), but it doesn't work in general. So we should fix that.
|
| - String line = UTF8.decode(command.data).trim();
|
| - if (line.isEmpty) {
|
| - return sdkAgentPackage;
|
| - } else {
|
| - return base.resolve(line);
|
| - }
|
| - break;
|
| -
|
| - default:
|
| - throwInternalError("Unexpected ${command.code}");
|
| - return null;
|
| - }
|
| - }
|
| - return null;
|
| -}
|
| -
|
| -class Version {
|
| - final List<int> version;
|
| - final String label;
|
| -
|
| - Version(this.version, this.label) {
|
| - if (version.length != 3) {
|
| - throw new ArgumentError("version must have three parts");
|
| - }
|
| - }
|
| -
|
| - /// Returns `true` if this version's digits are greater in lexicographical
|
| - /// order.
|
| - ///
|
| - /// We use a function instead of [operator >] because [label] is not used
|
| - /// in the comparison, but it is used in [operator ==].
|
| - bool isGreaterThan(Version other) {
|
| - for (int part = 0; part < 3; ++part) {
|
| - if (version[part] < other.version[part]) {
|
| - return false;
|
| - }
|
| - if (version[part] > other.version[part]) {
|
| - return true;
|
| - }
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - bool operator ==(other) {
|
| - return other is Version &&
|
| - version[0] == other.version[0] &&
|
| - version[1] == other.version[1] &&
|
| - version[2] == other.version[2] &&
|
| - label == other.label;
|
| - }
|
| -
|
| - int get hashCode {
|
| - return 3 * version[0] +
|
| - 5 * version[1] +
|
| - 7 * version[2] +
|
| - 13 * label.hashCode;
|
| - }
|
| -
|
| - /// Check if this version is a bleeding edge version.
|
| - bool get isEdgeVersion => label == null ? false : label.startsWith('edge.');
|
| -
|
| - /// Check if this version is a dev version.
|
| - bool get isDevVersion => label == null ? false : label.startsWith('dev.');
|
| -
|
| - String toString() {
|
| - String labelPart = label == null ? '' : '-$label';
|
| - return '${version[0]}.${version[1]}.${version[2]}$labelPart';
|
| - }
|
| -}
|
| -
|
| -Version parseVersion(String text) {
|
| - List<String> labelParts = text.split('-');
|
| - if (labelParts.length > 2) {
|
| - throw new ArgumentError('Not a version: $text.');
|
| - }
|
| - List<String> digitParts = labelParts[0].split('.');
|
| - if (digitParts.length != 3) {
|
| - throw new ArgumentError('Not a version: $text.');
|
| - }
|
| - List<int> digits = digitParts.map(int.parse).toList();
|
| - return new Version(digits, labelParts.length == 2 ? labelParts[1] : null);
|
| -}
|
| -
|
| -Future<int> upgradeAgent(
|
| - CommandSender commandSender,
|
| - StreamIterator<ClientCommand> commandIterator,
|
| - SessionState state,
|
| - Uri base,
|
| - Uri packageUri) async {
|
| - if (state.settings.deviceAddress == null) {
|
| - throwFatalError(DiagnosticKind.noAgentFound);
|
| - }
|
| -
|
| - while (packageUri == null) {
|
| - packageUri =
|
| - await readPackagePathFromUser(base, commandSender, commandIterator);
|
| - }
|
| -
|
| - if (!await new File.fromUri(packageUri).exists()) {
|
| - print('File not found: $packageUri');
|
| - return 1;
|
| - }
|
| -
|
| - Version version = parseVersion(extractVersion(packageUri));
|
| -
|
| - Version existingVersion = parseVersion(
|
| - await withAgentConnection(state,
|
| - (connection) => connection.fletchVersion()));
|
| -
|
| - if (existingVersion == version) {
|
| - print('Target device is already at $version');
|
| - return 0;
|
| - }
|
| -
|
| - print("Attempting to upgrade device from "
|
| - "$existingVersion to $version");
|
| -
|
| - if (existingVersion.isGreaterThan(version)) {
|
| - commandSender.sendStdout("The existing version is greater than the "
|
| - "version you want to use to upgrade.\n"
|
| - "Please confirm this operation by typing 'yes' "
|
| - "(press Enter to abort): ");
|
| - Confirm: while (await commandIterator.moveNext()) {
|
| - ClientCommand command = commandIterator.current;
|
| - switch (command.code) {
|
| - case ClientCommandCode.Stdin:
|
| - if (command.data.length == 0) {
|
| - throwInternalError("Unexpected end of input");
|
| - }
|
| - String line = UTF8.decode(command.data).trim();
|
| - if (line.isEmpty) {
|
| - commandSender.sendStdout("Upgrade aborted\n");
|
| - return 0;
|
| - } else if (line.trim().toLowerCase() == "yes") {
|
| - break Confirm;
|
| - }
|
| - break;
|
| -
|
| - default:
|
| - throwInternalError("Unexpected ${command.code}");
|
| - return null;
|
| - }
|
| - }
|
| - }
|
| -
|
| - List<int> data = await new File.fromUri(packageUri).readAsBytes();
|
| - print("Sending package to fletch agent");
|
| - await withAgentConnection(state,
|
| - (connection) => connection.upgradeAgent(version.toString(), data));
|
| - print("Transfer complete, waiting for the Fletch agent to restart. "
|
| - "This can take a few seconds.");
|
| -
|
| - Version newVersion;
|
| - int remainingTries = 20;
|
| - // Wait for the agent to come back online to verify the version.
|
| - while (--remainingTries > 0) {
|
| - await new Future.delayed(const Duration(seconds: 1));
|
| - try {
|
| - // TODO(karlklose): this functionality should be shared with connect.
|
| - Socket socket = await Socket.connect(
|
| - state.settings.deviceAddress.host,
|
| - state.settings.deviceAddress.port);
|
| - handleSocketErrors(socket, "pollAgentVersion", log: (String info) {
|
| - state.log("Connected to TCP waitForAgentUpgrade $info");
|
| - });
|
| - AgentConnection connection = new AgentConnection(socket);
|
| - newVersion = parseVersion(await connection.fletchVersion());
|
| - disconnectFromAgent(connection);
|
| - if (newVersion != existingVersion) {
|
| - break;
|
| - }
|
| - } on SocketException catch (e) {
|
| - // Ignore this error and keep waiting.
|
| - }
|
| - }
|
| -
|
| - if (newVersion == existingVersion) {
|
| - print("Failed to upgrade: the device is still at the old version.");
|
| - print("Try running x-upgrade again. "
|
| - "If the upgrade fails again, try rebooting the device.");
|
| - return 1;
|
| - } else if (newVersion == null) {
|
| - print("Could not connect to Fletch agent after upgrade.");
|
| - print("Try running 'fletch show devices' later to see if it has been"
|
| - " restarted. If the device does not show up, try rebooting it.");
|
| - return 1;
|
| - } else {
|
| - print("Upgrade successful.");
|
| - }
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -Future<int> downloadTools(
|
| - CommandSender commandSender,
|
| - StreamIterator<ClientCommand> commandIterator,
|
| - SessionState state) async {
|
| -
|
| - void throwUnsupportedPlatform() {
|
| - throwFatalError(
|
| - DiagnosticKind.unsupportedPlatform,
|
| - message: Platform.operatingSystem);
|
| - }
|
| -
|
| - Future decompressFile(File zipFile, Directory destination) async {
|
| - var result;
|
| - if (Platform.isLinux) {
|
| - result = await Process.run(
|
| - "unzip", ["-o", zipFile.path, "-d", destination.path]);
|
| - } else if (Platform.isMacOS) {
|
| - result = await Process.run(
|
| - "ditto", ["-x", "-k", zipFile.path, destination.path]);
|
| - } else {
|
| - throwUnsupportedPlatform();
|
| - }
|
| - if (result.exitCode != 0) {
|
| - throwInternalError(
|
| - "Failed to decompress ${zipFile.path} to ${destination.path}, "
|
| - "error = ${result.exitCode}");
|
| - }
|
| - }
|
| -
|
| - const String gcsRoot = "https://storage.googleapis.com";
|
| - String gcsBucket = "fletch-archive";
|
| -
|
| - Future downloadTool(String gcsPath, String zipFile, String toolName) async {
|
| - Uri url = Uri.parse("$gcsRoot/$gcsBucket/$gcsPath/$zipFile");
|
| - Directory tmpDir = Directory.systemTemp.createTempSync("fletch_download");
|
| - File tmpZip = new File(join(tmpDir.path, zipFile));
|
| -
|
| - OutputService outputService =
|
| - new OutputService(commandSender.sendStdout, state.log);
|
| - SDKServices service = new SDKServices(outputService);
|
| - print("Downloading: $toolName");
|
| - state.log("Downloading $toolName from $url to $tmpZip");
|
| - await service.downloadWithProgress(url, tmpZip);
|
| - print(""); // service.downloadWithProgress does not write newline when done.
|
| -
|
| - // In the SDK, the tools directory is at the same level as the
|
| - // internal (and bin) directory.
|
| - Directory toolsDirectory =
|
| - new Directory.fromUri(executable.resolve('../tools'));
|
| - state.log("Decompressing ${tmpZip.path} to ${toolsDirectory.path}");
|
| - await decompressFile(tmpZip, toolsDirectory);
|
| - state.log("Deleting temporary directory ${tmpDir.path}");
|
| - await tmpDir.delete(recursive: true);
|
| - }
|
| -
|
| - String gcsPath;
|
| -
|
| - Version version = parseVersion(fletchVersion);
|
| - if (version.isEdgeVersion) {
|
| - print("WARNING: For bleeding edge a fixed image is used.");
|
| - // For edge versions download use a well known version for now.
|
| - var knownVersion = "0.3.0-edge.3c85dbafe006eb2ce16545aaf3df1352fa7a4500";
|
| - gcsBucket = "fletch-temporary";
|
| - gcsPath = "channels/be/raw/$knownVersion/sdk";
|
| - } else if (version.isDevVersion) {
|
| - // TODO(sgjesse): Change this to channels/dev/release at some point.
|
| - gcsPath = "channels/dev/raw/$version/sdk";
|
| - } else {
|
| - print("Stable version not supported. Got version $version.");
|
| - }
|
| -
|
| - String osName;
|
| - if (Platform.isLinux) {
|
| - osName = "linux";
|
| - } else if (Platform.isMacOS) {
|
| - osName = "mac";
|
| - } else {
|
| - throwUnsupportedPlatform();
|
| - }
|
| -
|
| - String gccArmEmbedded = "gcc-arm-embedded-${osName}.zip";
|
| - await downloadTool(gcsPath, gccArmEmbedded, "GCC ARM Embedded toolchain");
|
| - String openocd = "openocd-${osName}.zip";
|
| - await downloadTool(gcsPath, openocd, "Open On-Chip Debugger (OpenOCD)");
|
| -
|
| - print("Third party tools downloaded");
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -Future<WorkerConnection> allocateWorker(IsolatePool pool) async {
|
| - WorkerConnection workerConnection =
|
| - new WorkerConnection(await pool.getIsolate(exitOnError: false));
|
| - await workerConnection.beginSession();
|
| - return workerConnection;
|
| -}
|
| -
|
| -SharedTask combineTasks(SharedTask task1, SharedTask task2) {
|
| - if (task1 == null) return task2;
|
| - if (task2 == null) return task1;
|
| - return new CombinedTask(task1, task2);
|
| -}
|
| -
|
| -class CombinedTask extends SharedTask {
|
| - // Keep this class simple, see note in superclass.
|
| -
|
| - final SharedTask task1;
|
| -
|
| - final SharedTask task2;
|
| -
|
| - const CombinedTask(this.task1, this.task2);
|
| -
|
| - Future<int> call(
|
| - CommandSender commandSender,
|
| - StreamIterator<ClientCommand> commandIterator) {
|
| - return invokeCombinedTasks(commandSender, commandIterator, task1, task2);
|
| - }
|
| -}
|
| -
|
| -Future<int> invokeCombinedTasks(
|
| - CommandSender commandSender,
|
| - StreamIterator<ClientCommand> commandIterator,
|
| - SharedTask task1,
|
| - SharedTask task2) async {
|
| - int result = await task1(commandSender, commandIterator);
|
| - if (result != 0) return result;
|
| - return task2(commandSender, commandIterator);
|
| -}
|
| -
|
| -Future<String> getAgentVersion(InternetAddress host, int port) async {
|
| - Socket socket;
|
| - try {
|
| - socket = await Socket.connect(host, port);
|
| - handleSocketErrors(socket, "getAgentVersionSocket");
|
| - } on SocketException catch (e) {
|
| - return 'Error: no agent: $e';
|
| - }
|
| - try {
|
| - AgentConnection connection = new AgentConnection(socket);
|
| - return await connection.fletchVersion();
|
| - } finally {
|
| - socket.close();
|
| - }
|
| -}
|
| -
|
| -Future<List<InternetAddress>> discoverDevices(
|
| - {bool prefixWithNumber: false}) async {
|
| - const ipV4AddressLength = 'xxx.xxx.xxx.xxx'.length;
|
| - print("Looking for Dartino capable devices (will search for 5 seconds)...");
|
| - MDnsClient client = new MDnsClient();
|
| - await client.start();
|
| - List<InternetAddress> result = <InternetAddress>[];
|
| - String name = '_dartino_agent._tcp.local';
|
| - await for (ResourceRecord ptr in client.lookup(RRType.PTR, name)) {
|
| - String domain = ptr.domainName;
|
| - await for (ResourceRecord srv in client.lookup(RRType.SRV, domain)) {
|
| - String target = srv.target;
|
| - await for (ResourceRecord a in client.lookup(RRType.A, target)) {
|
| - InternetAddress address = a.address;
|
| - if (!address.isLinkLocal) {
|
| - result.add(address);
|
| - String version = await getAgentVersion(address, AGENT_DEFAULT_PORT);
|
| - String prefix = prefixWithNumber ? "${result.length}: " : "";
|
| - print("${prefix}Device at "
|
| - "${address.address.padRight(ipV4AddressLength + 1)} "
|
| - "$target ($version)");
|
| - }
|
| - }
|
| - }
|
| - // TODO(karlklose): Verify that we got an A/IP4 result for the PTR result.
|
| - // If not, maybe the cache was flushed before access and we need to query
|
| - // for the SRV or A type again.
|
| - }
|
| - client.stop();
|
| - return result;
|
| -}
|
| -
|
| -void showSessions() {
|
| - Sessions.names.forEach(print);
|
| -}
|
| -
|
| -Future<int> showSessionSettings() async {
|
| - Settings settings = SessionState.current.settings;
|
| - Uri source = settings.source;
|
| - if (source != null) {
|
| - // This should output `source.toFilePath()`, but we do it like this to be
|
| - // consistent with the format of the [Settings.packages] value.
|
| - print('Configured from $source}');
|
| - }
|
| - settings.toJson().forEach((String key, value) {
|
| - print('$key: $value');
|
| - });
|
| - return 0;
|
| -}
|
| -
|
| -Address parseAddress(String address, {int defaultPort: 0}) {
|
| - String host;
|
| - int port;
|
| - List<String> parts = address.split(":");
|
| - if (parts.length == 1) {
|
| - host = InternetAddress.LOOPBACK_IP_V4.address;
|
| - port = int.parse(
|
| - parts[0],
|
| - onError: (String source) {
|
| - host = source;
|
| - return defaultPort;
|
| - });
|
| - } else {
|
| - host = parts[0];
|
| - port = int.parse(
|
| - parts[1],
|
| - onError: (String source) {
|
| - throwFatalError(
|
| - DiagnosticKind.expectedAPortNumber, userInput: source);
|
| - });
|
| - }
|
| - return new Address(host, port);
|
| -}
|
| -
|
| -class Address {
|
| - final String host;
|
| - final int port;
|
| -
|
| - const Address(this.host, this.port);
|
| -
|
| - String toString() => "Address($host, $port)";
|
| -
|
| - String toJson() => "$host:$port";
|
| -
|
| - bool operator ==(other) {
|
| - if (other is! Address) return false;
|
| - return other.host == host && other.port == port;
|
| - }
|
| -
|
| - int get hashCode => host.hashCode ^ port.hashCode;
|
| -}
|
| -
|
| -/// See ../verbs/documentation.dart for a definition of this format.
|
| -Settings parseSettings(String jsonLikeData, Uri settingsUri) {
|
| - String json = jsonLikeData.split("\n")
|
| - .where((String line) => !line.trim().startsWith("//")).join("\n");
|
| - var userSettings;
|
| - try {
|
| - userSettings = JSON.decode(json);
|
| - } on FormatException catch (e) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsNotJson, uri: settingsUri, message: e.message);
|
| - }
|
| - if (userSettings is! Map) {
|
| - throwFatalError(DiagnosticKind.settingsNotAMap, uri: settingsUri);
|
| - }
|
| - Uri packages;
|
| - final List<String> options = <String>[];
|
| - final Map<String, String> constants = <String, String>{};
|
| - Address deviceAddress;
|
| - DeviceType deviceType;
|
| - IncrementalMode incrementalMode = IncrementalMode.none;
|
| - userSettings.forEach((String key, value) {
|
| - switch (key) {
|
| - case "packages":
|
| - if (value != null) {
|
| - if (value is! String) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsPackagesNotAString, uri: settingsUri,
|
| - userInput: '$value');
|
| - }
|
| - packages = settingsUri.resolve(value);
|
| - }
|
| - break;
|
| -
|
| - case "options":
|
| - if (value != null) {
|
| - if (value is! List) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsOptionsNotAList, uri: settingsUri,
|
| - userInput: "$value");
|
| - }
|
| - for (var option in value) {
|
| - if (option is! String) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsOptionNotAString, uri: settingsUri,
|
| - userInput: '$option');
|
| - }
|
| - if (option.startsWith("-D")) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsCompileTimeConstantAsOption,
|
| - uri: settingsUri, userInput: '$option');
|
| - }
|
| - options.add(option);
|
| - }
|
| - }
|
| - break;
|
| -
|
| - case "constants":
|
| - if (value != null) {
|
| - if (value is! Map) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsConstantsNotAMap, uri: settingsUri);
|
| - }
|
| - value.forEach((String key, value) {
|
| - if (value == null) {
|
| - // Ignore.
|
| - } else if (value is bool || value is int || value is String) {
|
| - constants[key] = '$value';
|
| - } else {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsUnrecognizedConstantValue,
|
| - uri: settingsUri, userInput: key,
|
| - additionalUserInput: '$value');
|
| - }
|
| - });
|
| - }
|
| - break;
|
| -
|
| - case "device_address":
|
| - if (value != null) {
|
| - if (value is! String) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsDeviceAddressNotAString,
|
| - uri: settingsUri, userInput: '$value');
|
| - }
|
| - deviceAddress =
|
| - parseAddress(value, defaultPort: AGENT_DEFAULT_PORT);
|
| - }
|
| - break;
|
| -
|
| - case "device_type":
|
| - if (value != null) {
|
| - if (value is! String) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsDeviceTypeNotAString,
|
| - uri: settingsUri, userInput: '$value');
|
| - }
|
| - deviceType = parseDeviceType(value);
|
| - if (deviceType == null) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsDeviceTypeUnrecognized,
|
| - uri: settingsUri, userInput: '$value');
|
| - }
|
| - }
|
| - break;
|
| -
|
| - case "incremental_mode":
|
| - if (value != null) {
|
| - if (value is! String) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsIncrementalModeNotAString,
|
| - uri: settingsUri, userInput: '$value');
|
| - }
|
| - incrementalMode = parseIncrementalMode(value);
|
| - if (incrementalMode == null) {
|
| - throwFatalError(
|
| - DiagnosticKind.settingsIncrementalModeUnrecognized,
|
| - uri: settingsUri, userInput: '$value');
|
| - }
|
| - }
|
| - break;
|
| -
|
| - default:
|
| - throwFatalError(
|
| - DiagnosticKind.settingsUnrecognizedKey, uri: settingsUri,
|
| - userInput: key);
|
| - break;
|
| - }
|
| - });
|
| - return new Settings.fromSource(settingsUri,
|
| - packages, options, constants, deviceAddress, deviceType, incrementalMode);
|
| -}
|
| -
|
| -class Settings {
|
| - final Uri source;
|
| -
|
| - final Uri packages;
|
| -
|
| - final List<String> options;
|
| -
|
| - final Map<String, String> constants;
|
| -
|
| - final Address deviceAddress;
|
| -
|
| - final DeviceType deviceType;
|
| -
|
| - final IncrementalMode incrementalMode;
|
| -
|
| - const Settings(
|
| - this.packages,
|
| - this.options,
|
| - this.constants,
|
| - this.deviceAddress,
|
| - this.deviceType,
|
| - this.incrementalMode) : source = null;
|
| -
|
| - const Settings.fromSource(
|
| - this.source,
|
| - this.packages,
|
| - this.options,
|
| - this.constants,
|
| - this.deviceAddress,
|
| - this.deviceType,
|
| - this.incrementalMode);
|
| -
|
| - const Settings.empty()
|
| - : this(null, const <String>[], const <String, String>{}, null, null,
|
| - IncrementalMode.none);
|
| -
|
| - Settings copyWith({
|
| - Uri packages,
|
| - List<String> options,
|
| - Map<String, String> constants,
|
| - Address deviceAddress,
|
| - DeviceType deviceType,
|
| - IncrementalMode incrementalMode}) {
|
| -
|
| - if (packages == null) {
|
| - packages = this.packages;
|
| - }
|
| - if (options == null) {
|
| - options = this.options;
|
| - }
|
| - if (constants == null) {
|
| - constants = this.constants;
|
| - }
|
| - if (deviceAddress == null) {
|
| - deviceAddress = this.deviceAddress;
|
| - }
|
| - if (deviceType == null) {
|
| - deviceType = this.deviceType;
|
| - }
|
| - if (incrementalMode == null) {
|
| - incrementalMode = this.incrementalMode;
|
| - }
|
| - return new Settings(
|
| - packages,
|
| - options,
|
| - constants,
|
| - deviceAddress,
|
| - deviceType,
|
| - incrementalMode);
|
| - }
|
| -
|
| - String toString() {
|
| - return "Settings("
|
| - "packages: $packages, "
|
| - "options: $options, "
|
| - "constants: $constants, "
|
| - "device_address: $deviceAddress, "
|
| - "device_type: $deviceType, "
|
| - "incremental_mode: $incrementalMode)";
|
| - }
|
| -
|
| - Map<String, dynamic> toJson() {
|
| - Map<String, dynamic> result = <String, dynamic>{};
|
| -
|
| - void addIfNotNull(String name, value) {
|
| - if (value != null) {
|
| - result[name] = value;
|
| - }
|
| - }
|
| -
|
| - addIfNotNull("packages", packages == null ? null : "$packages");
|
| - addIfNotNull("options", options);
|
| - addIfNotNull("constants", constants);
|
| - addIfNotNull("device_address", deviceAddress);
|
| - addIfNotNull(
|
| - "device_type",
|
| - deviceType == null ? null : unParseDeviceType(deviceType));
|
| - addIfNotNull(
|
| - "incremental_mode",
|
| - incrementalMode == null
|
| - ? null : unparseIncrementalMode(incrementalMode));
|
| -
|
| - return result;
|
| - }
|
| -}
|
|
|