Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(171)

Side by Side Diff: pkg/fletchc/lib/src/worker/developer.dart

Issue 1659163007: Rename fletch -> dartino (Closed) Base URL: https://github.com/dartino/sdk.git@master
Patch Set: address comments Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE.md file.
4
5 library fletchc.worker.developer;
6
7 import 'dart:async' show
8 Future,
9 Stream,
10 StreamController,
11 Timer;
12
13 import 'dart:convert' show
14 JSON,
15 JsonEncoder,
16 UTF8;
17
18 import 'dart:io' show
19 Directory,
20 File,
21 FileSystemEntity,
22 InternetAddress,
23 Platform,
24 Process,
25 Socket,
26 SocketException;
27
28 import 'package:sdk_library_metadata/libraries.dart' show
29 Category;
30
31 import 'package:sdk_services/sdk_services.dart' show
32 OutputService,
33 SDKServices;
34
35 import 'package:fletch_agent/agent_connection.dart' show
36 AgentConnection,
37 AgentException,
38 VmData;
39
40 import 'package:fletch_agent/messages.dart' show
41 AGENT_DEFAULT_PORT,
42 MessageDecodeException;
43
44 import 'package:mdns/mdns.dart' show
45 MDnsClient,
46 ResourceRecord,
47 RRType;
48
49 import 'package:path/path.dart' show
50 join;
51
52 import '../../vm_commands.dart' show
53 VmCommandCode,
54 ConnectionError,
55 Debugging,
56 HandShakeResult,
57 ProcessBacktrace,
58 ProcessBacktraceRequest,
59 ProcessRun,
60 ProcessSpawnForMain,
61 SessionEnd,
62 WriteSnapshotResult;
63
64 import '../../program_info.dart' show
65 Configuration,
66 ProgramInfo,
67 ProgramInfoBinary,
68 ProgramInfoJson,
69 buildProgramInfo;
70
71 import '../hub/session_manager.dart' show
72 FletchVm,
73 SessionState,
74 Sessions;
75
76 import '../hub/client_commands.dart' show
77 ClientCommandCode,
78 handleSocketErrors;
79
80 import '../verbs/infrastructure.dart' show
81 ClientCommand,
82 CommandSender,
83 DiagnosticKind,
84 FletchCompiler,
85 FletchDelta,
86 IncrementalCompiler,
87 WorkerConnection,
88 IsolatePool,
89 Session,
90 SharedTask,
91 StreamIterator,
92 throwFatalError;
93
94 import '../../incremental/fletchc_incremental.dart' show
95 IncrementalCompilationFailed,
96 IncrementalMode,
97 parseIncrementalMode,
98 unparseIncrementalMode;
99
100 export '../../incremental/fletchc_incremental.dart' show
101 IncrementalMode;
102
103 import '../../fletch_compiler.dart' show fletchDeviceType;
104
105 import '../hub/exit_codes.dart' as exit_codes;
106
107 import '../../fletch_system.dart' show
108 FletchFunction,
109 FletchSystem;
110
111 import '../../bytecodes.dart' show
112 Bytecode,
113 MethodEnd;
114
115 import '../diagnostic.dart' show
116 throwInternalError;
117
118 import '../guess_configuration.dart' show
119 executable,
120 fletchVersion,
121 guessFletchVm;
122
123 import '../device_type.dart' show
124 DeviceType,
125 parseDeviceType,
126 unParseDeviceType;
127
128 export '../device_type.dart' show
129 DeviceType;
130
131 import '../please_report_crash.dart' show
132 pleaseReportCrash;
133
134 import '../../debug_state.dart' as debug show
135 RemoteObject,
136 BackTrace;
137
138 typedef Future<Null> ClientEventHandler(Session session);
139
140 Uri configFileUri;
141
142 Future<Socket> connect(
143 String host,
144 int port,
145 DiagnosticKind kind,
146 String socketDescription,
147 SessionState state) async {
148 // We are using .catchError rather than try/catch because we have seen
149 // incorrect stack traces using the latter.
150 Socket socket = await Socket.connect(host, port).catchError(
151 (SocketException error) {
152 String message = error.message;
153 if (error.osError != null) {
154 message = error.osError.message;
155 }
156 throwFatalError(kind, address: '$host:$port', message: message);
157 }, test: (e) => e is SocketException);
158 handleSocketErrors(socket, socketDescription, log: (String info) {
159 state.log("Connected to TCP $socketDescription $info");
160 });
161 return socket;
162 }
163
164 Future<AgentConnection> connectToAgent(SessionState state) async {
165 // TODO(wibling): need to make sure the agent is running.
166 assert(state.settings.deviceAddress != null);
167 String host = state.settings.deviceAddress.host;
168 int agentPort = state.settings.deviceAddress.port;
169 Socket socket = await connect(
170 host, agentPort, DiagnosticKind.socketAgentConnectError,
171 "agentSocket", state);
172 return new AgentConnection(socket);
173 }
174
175 /// Return the result of a function in the context of an open [AgentConnection].
176 ///
177 /// The result is a [Future] of this value.
178 /// This function handles [AgentException] and [MessageDecodeException].
179 Future withAgentConnection(
180 SessionState state,
181 Future f(AgentConnection connection)) async {
182 AgentConnection connection = await connectToAgent(state);
183 try {
184 return await f(connection);
185 } on AgentException catch (error) {
186 throwFatalError(
187 DiagnosticKind.socketAgentReplyError,
188 address: '${connection.socket.remoteAddress.host}:'
189 '${connection.socket.remotePort}',
190 message: error.message);
191 } on MessageDecodeException catch (error) {
192 throwFatalError(
193 DiagnosticKind.socketAgentReplyError,
194 address: '${connection.socket.remoteAddress.host}:'
195 '${connection.socket.remotePort}',
196 message: error.message);
197 } finally {
198 disconnectFromAgent(connection);
199 }
200 }
201
202 void disconnectFromAgent(AgentConnection connection) {
203 assert(connection.socket != null);
204 connection.socket.close();
205 }
206
207 Future<Null> checkAgentVersion(Uri base, SessionState state) async {
208 String deviceFletchVersion = await withAgentConnection(state,
209 (connection) => connection.fletchVersion());
210 Uri packageFile = await lookForAgentPackage(base, version: fletchVersion);
211 String fixit;
212 if (packageFile != null) {
213 fixit = "Try running\n"
214 " 'fletch x-upgrade agent in session ${state.name}'.";
215 } else {
216 fixit = "Try downloading a matching SDK and running\n"
217 " 'fletch x-upgrade agent in session ${state.name}'\n"
218 "from the SDK's root directory.";
219 }
220
221 if (fletchVersion != deviceFletchVersion) {
222 throwFatalError(DiagnosticKind.agentVersionMismatch,
223 userInput: fletchVersion,
224 additionalUserInput: deviceFletchVersion,
225 fixit: fixit);
226 }
227 }
228
229 Future<Null> startAndAttachViaAgent(Uri base, SessionState state) async {
230 // TODO(wibling): integrate with the FletchVm class, e.g. have a
231 // AgentFletchVm and LocalFletchVm that both share the same interface
232 // where the former is interacting with the agent.
233 await checkAgentVersion(base, state);
234 VmData vmData = await withAgentConnection(state,
235 (connection) => connection.startVm());
236 state.fletchAgentVmId = vmData.id;
237 String host = state.settings.deviceAddress.host;
238 await attachToVm(host, vmData.port, state);
239 await state.session.disableVMStandardOutput();
240 }
241
242 Future<Null> startAndAttachDirectly(SessionState state, Uri base) async {
243 String fletchVmPath = state.compilerHelper.fletchVm.toFilePath();
244 state.fletchVm = await FletchVm.start(fletchVmPath, workingDirectory: base);
245 await attachToVm(state.fletchVm.host, state.fletchVm.port, state);
246 await state.session.disableVMStandardOutput();
247 }
248
249 Future<Null> attachToVm(String host, int port, SessionState state) async {
250 Socket socket = await connect(
251 host, port, DiagnosticKind.socketVmConnectError, "vmSocket", state);
252
253 Session session = new Session(socket, state.compiler, state.stdoutSink,
254 state.stderrSink, null);
255
256 // Perform handshake with VM which validates that VM and compiler
257 // have the same versions.
258 HandShakeResult handShakeResult = await session.handShake(fletchVersion);
259 if (handShakeResult == null) {
260 throwFatalError(DiagnosticKind.handShakeFailed, address: '$host:$port');
261 }
262 if (!handShakeResult.success) {
263 throwFatalError(DiagnosticKind.versionMismatch,
264 address: '$host:$port',
265 userInput: fletchVersion,
266 additionalUserInput: handShakeResult.version);
267 }
268
269 // Enable debugging to be able to communicate with VM when there
270 // are errors.
271 await session.runCommand(const Debugging());
272
273 state.session = session;
274 }
275
276 Future<int> compile(
277 Uri script,
278 SessionState state,
279 Uri base,
280 {bool analyzeOnly: false,
281 bool fatalIncrementalFailures: false}) async {
282 IncrementalCompiler compiler = state.compiler;
283 if (!compiler.isProductionModeEnabled) {
284 state.resetCompiler();
285 }
286 Uri firstScript = state.script;
287 List<FletchDelta> previousResults = state.compilationResults;
288
289 FletchDelta newResult;
290 try {
291 if (analyzeOnly) {
292 state.resetCompiler();
293 state.log("Analyzing '$script'");
294 return await compiler.analyze(script, base);
295 } else if (previousResults.isEmpty) {
296 state.script = script;
297 await compiler.compile(script, base);
298 newResult = compiler.computeInitialDelta();
299 } else {
300 try {
301 state.log("Compiling difference from $firstScript to $script");
302 newResult = await compiler.compileUpdates(
303 previousResults.last.system, <Uri, Uri>{firstScript: script},
304 logTime: state.log, logVerbose: state.log);
305 } on IncrementalCompilationFailed catch (error) {
306 state.log(error);
307 state.resetCompiler();
308 if (fatalIncrementalFailures) {
309 print(error);
310 state.log(
311 "Aborting compilation due to --fatal-incremental-failures...");
312 return exit_codes.INCREMENTAL_COMPILER_FAILED;
313 }
314 state.log("Attempting full compile...");
315 state.script = script;
316 await compiler.compile(script, base);
317 newResult = compiler.computeInitialDelta();
318 }
319 }
320 } catch (error, stackTrace) {
321 pleaseReportCrash(error, stackTrace);
322 return exit_codes.COMPILER_EXITCODE_CRASH;
323 }
324 if (newResult == null) {
325 return exit_codes.DART_VM_EXITCODE_COMPILE_TIME_ERROR;
326 }
327
328 state.addCompilationResult(newResult);
329
330 state.log("Compiled '$script' to ${newResult.commands.length} commands");
331
332 return 0;
333 }
334
335 Future<Settings> readSettings(Uri uri) async {
336 if (await new File.fromUri(uri).exists()) {
337 String jsonLikeData = await new File.fromUri(uri).readAsString();
338 return parseSettings(jsonLikeData, uri);
339 } else {
340 return null;
341 }
342 }
343
344 Future<Uri> findFile(Uri cwd, String fileName) async {
345 Uri uri = cwd.resolve(fileName);
346 while (true) {
347 if (await new File.fromUri(uri).exists()) return uri;
348 if (uri.pathSegments.length <= 1) return null;
349 uri = uri.resolve('../$fileName');
350 }
351 }
352
353 Future<Settings> createSettings(
354 String sessionName,
355 Uri uri,
356 Uri cwd,
357 Uri configFileUri,
358 CommandSender commandSender,
359 StreamIterator<ClientCommand> commandIterator) async {
360 bool userProvidedSettings = uri != null;
361 if (!userProvidedSettings) {
362 // Try to find a $sessionName.fletch-settings file starting from the current
363 // working directory and walking up its parent directories.
364 uri = await findFile(cwd, '$sessionName.fletch-settings');
365
366 // If no $sessionName.fletch-settings file is found, try to find the
367 // settings template file (in the SDK or git repo) by looking for a
368 // .fletch-settings file starting from the fletch executable's directory
369 // and walking up its parent directory chain.
370 if (uri == null) {
371 uri = await findFile(executable, '.fletch-settings');
372 if (uri != null) print('Using template settings file $uri');
373 }
374 }
375
376 Settings settings = new Settings.empty();
377 if (uri != null) {
378 String jsonLikeData = await new File.fromUri(uri).readAsString();
379 settings = parseSettings(jsonLikeData, uri);
380 }
381 if (userProvidedSettings) return settings;
382
383 // TODO(wibling): get rid of below special handling of the sessions 'remote'
384 // and 'local' and come up with a fletch project concept that can contain
385 // these settings.
386 Uri packagesUri;
387 Address address;
388 switch (sessionName) {
389 case "remote":
390 uri = configFileUri.resolve("remote.fletch-settings");
391 Settings remoteSettings = await readSettings(uri);
392 if (remoteSettings != null) return remoteSettings;
393 packagesUri = executable.resolve("fletch-sdk.packages");
394 address = await readAddressFromUser(commandSender, commandIterator);
395 if (address == null) {
396 // Assume user aborted data entry.
397 return settings;
398 }
399 break;
400
401 case "local":
402 uri = configFileUri.resolve("local.fletch-settings");
403 Settings localSettings = await readSettings(uri);
404 if (localSettings != null) return localSettings;
405 // TODO(ahe): Use mock packages here.
406 packagesUri = executable.resolve("fletch-sdk.packages");
407 break;
408
409 default:
410 return settings;
411 }
412
413 if (!await new File.fromUri(packagesUri).exists()) {
414 packagesUri = null;
415 }
416 settings = settings.copyWith(packages: packagesUri, deviceAddress: address);
417 print("Created settings file '$uri'");
418 await new File.fromUri(uri).writeAsString(
419 "${const JsonEncoder.withIndent(' ').convert(settings)}\n");
420 return settings;
421 }
422
423 Future<Address> readAddressFromUser(
424 CommandSender commandSender,
425 StreamIterator<ClientCommand> commandIterator) async {
426 String message = "Please enter IP address of remote device "
427 "(press Enter to search for devices):";
428 commandSender.sendStdout(message);
429 // The list of devices found by running discovery.
430 List<InternetAddress> devices = <InternetAddress>[];
431 while (await commandIterator.moveNext()) {
432 ClientCommand command = commandIterator.current;
433 switch (command.code) {
434 case ClientCommandCode.Stdin:
435 if (command.data.length == 0) {
436 // TODO(ahe): It may be safe to return null here, but we need to
437 // check how this interacts with the debugger's InputHandler.
438 throwInternalError("Unexpected end of input");
439 }
440 // TODO(ahe): This assumes that the user's input arrives as one
441 // message. It is relatively safe to assume this for a normal terminal
442 // session because we use canonical input processing (Unix line
443 // buffering), but it doesn't work in general. So we should fix that.
444 String line = UTF8.decode(command.data).trim();
445 if (line.isEmpty && devices.isEmpty) {
446 commandSender.sendStdout("\n");
447 // [discoverDevices] will print out the list of device with their
448 // IP address, hostname, and agent version.
449 devices = await discoverDevices(prefixWithNumber: true);
450 if (devices.isEmpty) {
451 commandSender.sendStdout(
452 "Couldn't find Fletch capable devices\n");
453 commandSender.sendStdout(message);
454 } else {
455 if (devices.length == 1) {
456 commandSender.sendStdout("\n");
457 commandSender.sendStdout("Press Enter to use this device");
458 } else {
459 commandSender.sendStdout("\n");
460 commandSender.sendStdout(
461 "Found ${devices.length} Fletch capable devices\n");
462 commandSender.sendStdout(
463 "Please enter the number or the IP address of "
464 "the remote device you would like to use "
465 "(press Enter to use the first device): ");
466 }
467 }
468 } else {
469 bool checkedIndex = false;
470 if (devices.length > 0) {
471 if (line.isEmpty) {
472 return new Address(devices[0].address, AGENT_DEFAULT_PORT);
473 }
474 try {
475 checkedIndex = true;
476 int index = int.parse(line);
477 if (1 <= index && index <= devices.length) {
478 return new Address(devices[index - 1].address,
479 AGENT_DEFAULT_PORT);
480 } else {
481 commandSender.sendStdout("Invalid device index $line\n\n");
482 commandSender.sendStdout(message);
483 }
484 } on FormatException {
485 // Ignore FormatException and fall through to parse as IP address.
486 }
487 }
488 if (!checkedIndex) {
489 return parseAddress(line, defaultPort: AGENT_DEFAULT_PORT);
490 }
491 }
492 break;
493
494 default:
495 throwInternalError("Unexpected ${command.code}");
496 return null;
497 }
498 }
499 return null;
500 }
501
502 SessionState createSessionState(
503 String name,
504 Settings settings,
505 {Uri libraryRoot,
506 Uri fletchVm,
507 Uri nativesJson}) {
508 if (settings == null) {
509 settings = const Settings.empty();
510 }
511 List<String> compilerOptions = const bool.fromEnvironment("fletchc-verbose")
512 ? <String>['--verbose'] : <String>[];
513 compilerOptions.addAll(settings.options);
514 Uri packageConfig = settings.packages;
515 if (packageConfig == null) {
516 packageConfig = executable.resolve("fletch-sdk.packages");
517 }
518
519 DeviceType deviceType = settings.deviceType ??
520 parseDeviceType(fletchDeviceType);
521
522 String platform = (deviceType == DeviceType.embedded)
523 ? "fletch_embedded.platform"
524 : "fletch_mobile.platform";
525
526 FletchCompiler compilerHelper = new FletchCompiler(
527 options: compilerOptions,
528 packageConfig: packageConfig,
529 environment: settings.constants,
530 platform: platform,
531 libraryRoot: libraryRoot,
532 fletchVm: fletchVm,
533 nativesJson: nativesJson);
534
535 return new SessionState(
536 name, compilerHelper,
537 compilerHelper.newIncrementalCompiler(settings.incrementalMode),
538 settings);
539 }
540
541 Future runWithDebugger(
542 List<String> commands,
543 Session session,
544 SessionState state) async {
545
546 // Method used to generate the debugger commands if none are specified.
547 Stream<String> inputGenerator() async* {
548 yield 't verbose';
549 yield 'b main';
550 yield 'r';
551 while (!session.terminated) {
552 yield 's';
553 }
554 }
555
556 return commands.isEmpty ?
557 session.debug(inputGenerator(), Uri.base, state, echo: true) :
558 session.debug(
559 new Stream<String>.fromIterable(commands), Uri.base, state,
560 echo: true);
561 }
562
563 Future<int> run(
564 SessionState state,
565 {List<String> testDebuggerCommands,
566 bool terminateDebugger: true}) async {
567 List<FletchDelta> compilationResults = state.compilationResults;
568 Session session = state.session;
569
570 for (FletchDelta delta in compilationResults) {
571 await session.applyDelta(delta);
572 }
573
574 if (testDebuggerCommands != null) {
575 await runWithDebugger(testDebuggerCommands, session, state);
576 return 0;
577 }
578
579 session.silent = true;
580
581 await session.enableDebugger();
582 await session.spawnProcess();
583 var command = await session.debugRun();
584
585 int exitCode = exit_codes.COMPILER_EXITCODE_CRASH;
586 if (command == null) {
587 await session.kill();
588 await session.shutdown();
589 throwInternalError("No command received from Fletch VM");
590 }
591
592 Future printException() async {
593 if (!session.loaded) {
594 print('### process not loaded, cannot print uncaught exception');
595 return;
596 }
597 debug.RemoteObject exception = await session.uncaughtException();
598 if (exception != null) {
599 print(session.exceptionToString(exception));
600 }
601 }
602
603 Future printTrace() async {
604 if (!session.loaded) {
605 print("### process not loaded, cannot print stacktrace and code");
606 return;
607 }
608 debug.BackTrace stackTrace = await session.backTrace();
609 if (stackTrace != null) {
610 print(stackTrace.format());
611 print(stackTrace.list(state));
612 }
613 }
614
615 try {
616 switch (command.code) {
617 case VmCommandCode.UncaughtException:
618 state.log("Uncaught error");
619 exitCode = exit_codes.DART_VM_EXITCODE_UNCAUGHT_EXCEPTION;
620 await printException();
621 await printTrace();
622 // TODO(ahe): Need to continue to unwind stack.
623 break;
624 case VmCommandCode.ProcessCompileTimeError:
625 state.log("Compile-time error");
626 exitCode = exit_codes.DART_VM_EXITCODE_COMPILE_TIME_ERROR;
627 await printTrace();
628 // TODO(ahe): Continue to unwind stack?
629 break;
630
631 case VmCommandCode.ProcessTerminated:
632 exitCode = 0;
633 break;
634
635 case VmCommandCode.ConnectionError:
636 state.log("Error on connection to Fletch VM: ${command.error}");
637 exitCode = exit_codes.COMPILER_EXITCODE_CONNECTION_ERROR;
638 break;
639
640 default:
641 throwInternalError("Unexpected result from Fletch VM: '$command'");
642 break;
643 }
644 } finally {
645 if (terminateDebugger) {
646 await state.terminateSession();
647 } else {
648 // If the session terminated due to a ConnectionError or the program
649 // finished don't reuse the state's session.
650 if (session.terminated) {
651 state.session = null;
652 }
653 session.silent = false;
654 }
655 };
656
657 return exitCode;
658 }
659
660 Future<int> export(SessionState state,
661 Uri snapshot,
662 {bool binaryProgramInfo: false}) async {
663 List<FletchDelta> compilationResults = state.compilationResults;
664 Session session = state.session;
665 state.session = null;
666
667 for (FletchDelta delta in compilationResults) {
668 await session.applyDelta(delta);
669 }
670
671 var result = await session.writeSnapshot(snapshot.toFilePath());
672 if (result is WriteSnapshotResult) {
673 WriteSnapshotResult snapshotResult = result;
674
675 await session.shutdown();
676
677 ProgramInfo info =
678 buildProgramInfo(compilationResults.last.system, snapshotResult);
679
680 File jsonFile = new File('${snapshot.toFilePath()}.info.json');
681 await jsonFile.writeAsString(ProgramInfoJson.encode(info));
682
683 if (binaryProgramInfo) {
684 File binFile = new File('${snapshot.toFilePath()}.info.bin');
685 await binFile.writeAsBytes(ProgramInfoBinary.encode(info));
686 }
687
688 return 0;
689 } else {
690 assert(result is ConnectionError);
691 print("There was a connection error while writing the snapshot.");
692 return exit_codes.COMPILER_EXITCODE_CONNECTION_ERROR;
693 }
694 }
695
696 Future<int> compileAndAttachToVmThen(
697 CommandSender commandSender,
698 StreamIterator<ClientCommand> commandIterator,
699 SessionState state,
700 Uri script,
701 Uri base,
702 bool waitForVmExit,
703 Future<int> action(),
704 {ClientEventHandler eventHandler}) async {
705 bool startedVmDirectly = false;
706 List<FletchDelta> compilationResults = state.compilationResults;
707 if (compilationResults.isEmpty || script != null) {
708 if (script == null) {
709 throwFatalError(DiagnosticKind.noFileTarget);
710 }
711 int exitCode = await compile(script, state, base);
712 if (exitCode != 0) return exitCode;
713 compilationResults = state.compilationResults;
714 assert(compilationResults != null);
715 }
716
717 Session session = state.session;
718 if (session != null && session.loaded) {
719 // We cannot reuse a session that has already been loaded. Loading
720 // currently implies that some of the code has been run.
721 if (state.explicitAttach) {
722 // If the user explicitly called 'fletch attach' we cannot
723 // create a new VM session since we don't know if the vm is
724 // running locally or remotely and if running remotely there
725 // is no guarantee there is an agent to start a new vm.
726 //
727 // The UserSession is invalid in its current state as the
728 // vm session (aka. session in the code here) has already
729 // been loaded and run some code.
730 throwFatalError(DiagnosticKind.sessionInvalidState,
731 sessionName: state.name);
732 }
733 state.log('Cannot reuse existing VM session, creating new.');
734 await state.terminateSession();
735 session = null;
736 }
737 if (session == null) {
738 if (state.settings.deviceAddress != null) {
739 await startAndAttachViaAgent(base, state);
740 // TODO(wibling): read stdout from agent.
741 } else {
742 startedVmDirectly = true;
743 await startAndAttachDirectly(state, base);
744 state.fletchVm.stdoutLines.listen((String line) {
745 commandSender.sendStdout("$line\n");
746 });
747 state.fletchVm.stderrLines.listen((String line) {
748 commandSender.sendStderr("$line\n");
749 });
750 }
751 session = state.session;
752 assert(session != null);
753 }
754
755 eventHandler ??= defaultClientEventHandler(state, commandIterator);
756 setupClientInOut(state, commandSender, eventHandler);
757
758 int exitCode = exit_codes.COMPILER_EXITCODE_CRASH;
759 try {
760 exitCode = await action();
761 } catch (error, trace) {
762 print(error);
763 if (trace != null) {
764 print(trace);
765 }
766 } finally {
767 if (waitForVmExit && startedVmDirectly) {
768 exitCode = await state.fletchVm.exitCode;
769 }
770 state.detachCommandSender();
771 }
772 return exitCode;
773 }
774
775 void setupClientInOut(
776 SessionState state,
777 CommandSender commandSender,
778 ClientEventHandler eventHandler) {
779 // Forward output going into the state's outputSink using the passed in
780 // commandSender. This typically forwards output to the hub (main isolate)
781 // which forwards it on to stdout of the Fletch C++ client.
782 state.attachCommandSender(commandSender);
783
784 // Start event handling for input passed from the Fletch C++ client.
785 eventHandler(state.session);
786
787 // Let the hub (main isolate) know that event handling has been started.
788 commandSender.sendEventLoopStarted();
789 }
790
791 /// Return a default client event handler bound to the current session's
792 /// commandIterator and state.
793 /// This handler only takes care of signals coming from the client.
794 ClientEventHandler defaultClientEventHandler(
795 SessionState state,
796 StreamIterator<ClientCommand> commandIterator) {
797 return (Session session) async {
798 while (await commandIterator.moveNext()) {
799 ClientCommand command = commandIterator.current;
800 switch (command.code) {
801 case ClientCommandCode.Signal:
802 int signalNumber = command.data;
803 handleSignal(state, signalNumber);
804 break;
805 default:
806 state.log("Unhandled command from client: $command");
807 }
808 }
809 };
810 }
811
812 void handleSignal(SessionState state, int signalNumber) {
813 state.log("Received signal $signalNumber");
814 if (!state.hasRemoteVm && state.fletchVm == null) {
815 // This can happen if a user has attached to a vm using the "attach" verb
816 // in which case we don't forward the signal to the vm.
817 // TODO(wibling): Determine how to interpret the signal for the persistent
818 // process.
819 state.log('Signal $signalNumber ignored. VM was manually attached.');
820 print('Signal $signalNumber ignored. VM was manually attached.');
821 return;
822 }
823 if (state.hasRemoteVm) {
824 signalAgentVm(state, signalNumber);
825 } else {
826 assert(state.fletchVm.process != null);
827 int vmPid = state.fletchVm.process.pid;
828 Process.runSync("kill", ["-$signalNumber", "$vmPid"]);
829 }
830 }
831
832 Future signalAgentVm(SessionState state, int signalNumber) async {
833 await withAgentConnection(state, (connection) {
834 return connection.signalVm(state.fletchAgentVmId, signalNumber);
835 });
836 }
837
838 String extractVersion(Uri uri) {
839 List<String> nameParts = uri.pathSegments.last.split('_');
840 if (nameParts.length != 3 || nameParts[0] != 'fletch-agent') {
841 throwFatalError(DiagnosticKind.upgradeInvalidPackageName);
842 }
843 String version = nameParts[1];
844 // create_debian_packages.py adds a '-1' after the hash in the package name.
845 if (version.endsWith('-1')) {
846 version = version.substring(0, version.length - 2);
847 }
848 return version;
849 }
850
851 /// Try to locate an Fletch agent package file assuming the normal SDK layout
852 /// with SDK base directory [base].
853 ///
854 /// If the parameter [version] is passed, the Uri is only returned, if
855 /// the version matches.
856 Future<Uri> lookForAgentPackage(Uri base, {String version}) async {
857 String platform = "raspberry-pi2";
858 Uri platformUri = base.resolve("platforms/$platform");
859 Directory platformDir = new Directory.fromUri(platformUri);
860
861 // Try to locate the agent package in the SDK for the selected platform.
862 Uri sdkAgentPackage;
863 if (await platformDir.exists()) {
864 for (FileSystemEntity entry in platformDir.listSync()) {
865 Uri uri = entry.uri;
866 String name = uri.pathSegments.last;
867 if (name.startsWith('fletch-agent') &&
868 name.endsWith('.deb') &&
869 (version == null || extractVersion(uri) == version)) {
870 return uri;
871 }
872 }
873 }
874 return null;
875 }
876
877 Future<Uri> readPackagePathFromUser(
878 Uri base,
879 CommandSender commandSender,
880 StreamIterator<ClientCommand> commandIterator) async {
881 Uri sdkAgentPackage = await lookForAgentPackage(base);
882 if (sdkAgentPackage != null) {
883 String path = sdkAgentPackage.toFilePath();
884 commandSender.sendStdout("Found SDK package: $path\n");
885 commandSender.sendStdout("Press Enter to use this package to upgrade "
886 "or enter the path to another package file:\n");
887 } else {
888 commandSender.sendStdout("Please enter the path to the package file "
889 "you want to use:\n");
890 }
891
892 while (await commandIterator.moveNext()) {
893 ClientCommand command = commandIterator.current;
894 switch (command.code) {
895 case ClientCommandCode.Stdin:
896 if (command.data.length == 0) {
897 throwInternalError("Unexpected end of input");
898 }
899 // TODO(karlklose): This assumes that the user's input arrives as one
900 // message. It is relatively safe to assume this for a normal terminal
901 // session because we use canonical input processing (Unix line
902 // buffering), but it doesn't work in general. So we should fix that.
903 String line = UTF8.decode(command.data).trim();
904 if (line.isEmpty) {
905 return sdkAgentPackage;
906 } else {
907 return base.resolve(line);
908 }
909 break;
910
911 default:
912 throwInternalError("Unexpected ${command.code}");
913 return null;
914 }
915 }
916 return null;
917 }
918
919 class Version {
920 final List<int> version;
921 final String label;
922
923 Version(this.version, this.label) {
924 if (version.length != 3) {
925 throw new ArgumentError("version must have three parts");
926 }
927 }
928
929 /// Returns `true` if this version's digits are greater in lexicographical
930 /// order.
931 ///
932 /// We use a function instead of [operator >] because [label] is not used
933 /// in the comparison, but it is used in [operator ==].
934 bool isGreaterThan(Version other) {
935 for (int part = 0; part < 3; ++part) {
936 if (version[part] < other.version[part]) {
937 return false;
938 }
939 if (version[part] > other.version[part]) {
940 return true;
941 }
942 }
943 return false;
944 }
945
946 bool operator ==(other) {
947 return other is Version &&
948 version[0] == other.version[0] &&
949 version[1] == other.version[1] &&
950 version[2] == other.version[2] &&
951 label == other.label;
952 }
953
954 int get hashCode {
955 return 3 * version[0] +
956 5 * version[1] +
957 7 * version[2] +
958 13 * label.hashCode;
959 }
960
961 /// Check if this version is a bleeding edge version.
962 bool get isEdgeVersion => label == null ? false : label.startsWith('edge.');
963
964 /// Check if this version is a dev version.
965 bool get isDevVersion => label == null ? false : label.startsWith('dev.');
966
967 String toString() {
968 String labelPart = label == null ? '' : '-$label';
969 return '${version[0]}.${version[1]}.${version[2]}$labelPart';
970 }
971 }
972
973 Version parseVersion(String text) {
974 List<String> labelParts = text.split('-');
975 if (labelParts.length > 2) {
976 throw new ArgumentError('Not a version: $text.');
977 }
978 List<String> digitParts = labelParts[0].split('.');
979 if (digitParts.length != 3) {
980 throw new ArgumentError('Not a version: $text.');
981 }
982 List<int> digits = digitParts.map(int.parse).toList();
983 return new Version(digits, labelParts.length == 2 ? labelParts[1] : null);
984 }
985
986 Future<int> upgradeAgent(
987 CommandSender commandSender,
988 StreamIterator<ClientCommand> commandIterator,
989 SessionState state,
990 Uri base,
991 Uri packageUri) async {
992 if (state.settings.deviceAddress == null) {
993 throwFatalError(DiagnosticKind.noAgentFound);
994 }
995
996 while (packageUri == null) {
997 packageUri =
998 await readPackagePathFromUser(base, commandSender, commandIterator);
999 }
1000
1001 if (!await new File.fromUri(packageUri).exists()) {
1002 print('File not found: $packageUri');
1003 return 1;
1004 }
1005
1006 Version version = parseVersion(extractVersion(packageUri));
1007
1008 Version existingVersion = parseVersion(
1009 await withAgentConnection(state,
1010 (connection) => connection.fletchVersion()));
1011
1012 if (existingVersion == version) {
1013 print('Target device is already at $version');
1014 return 0;
1015 }
1016
1017 print("Attempting to upgrade device from "
1018 "$existingVersion to $version");
1019
1020 if (existingVersion.isGreaterThan(version)) {
1021 commandSender.sendStdout("The existing version is greater than the "
1022 "version you want to use to upgrade.\n"
1023 "Please confirm this operation by typing 'yes' "
1024 "(press Enter to abort): ");
1025 Confirm: while (await commandIterator.moveNext()) {
1026 ClientCommand command = commandIterator.current;
1027 switch (command.code) {
1028 case ClientCommandCode.Stdin:
1029 if (command.data.length == 0) {
1030 throwInternalError("Unexpected end of input");
1031 }
1032 String line = UTF8.decode(command.data).trim();
1033 if (line.isEmpty) {
1034 commandSender.sendStdout("Upgrade aborted\n");
1035 return 0;
1036 } else if (line.trim().toLowerCase() == "yes") {
1037 break Confirm;
1038 }
1039 break;
1040
1041 default:
1042 throwInternalError("Unexpected ${command.code}");
1043 return null;
1044 }
1045 }
1046 }
1047
1048 List<int> data = await new File.fromUri(packageUri).readAsBytes();
1049 print("Sending package to fletch agent");
1050 await withAgentConnection(state,
1051 (connection) => connection.upgradeAgent(version.toString(), data));
1052 print("Transfer complete, waiting for the Fletch agent to restart. "
1053 "This can take a few seconds.");
1054
1055 Version newVersion;
1056 int remainingTries = 20;
1057 // Wait for the agent to come back online to verify the version.
1058 while (--remainingTries > 0) {
1059 await new Future.delayed(const Duration(seconds: 1));
1060 try {
1061 // TODO(karlklose): this functionality should be shared with connect.
1062 Socket socket = await Socket.connect(
1063 state.settings.deviceAddress.host,
1064 state.settings.deviceAddress.port);
1065 handleSocketErrors(socket, "pollAgentVersion", log: (String info) {
1066 state.log("Connected to TCP waitForAgentUpgrade $info");
1067 });
1068 AgentConnection connection = new AgentConnection(socket);
1069 newVersion = parseVersion(await connection.fletchVersion());
1070 disconnectFromAgent(connection);
1071 if (newVersion != existingVersion) {
1072 break;
1073 }
1074 } on SocketException catch (e) {
1075 // Ignore this error and keep waiting.
1076 }
1077 }
1078
1079 if (newVersion == existingVersion) {
1080 print("Failed to upgrade: the device is still at the old version.");
1081 print("Try running x-upgrade again. "
1082 "If the upgrade fails again, try rebooting the device.");
1083 return 1;
1084 } else if (newVersion == null) {
1085 print("Could not connect to Fletch agent after upgrade.");
1086 print("Try running 'fletch show devices' later to see if it has been"
1087 " restarted. If the device does not show up, try rebooting it.");
1088 return 1;
1089 } else {
1090 print("Upgrade successful.");
1091 }
1092
1093 return 0;
1094 }
1095
1096 Future<int> downloadTools(
1097 CommandSender commandSender,
1098 StreamIterator<ClientCommand> commandIterator,
1099 SessionState state) async {
1100
1101 void throwUnsupportedPlatform() {
1102 throwFatalError(
1103 DiagnosticKind.unsupportedPlatform,
1104 message: Platform.operatingSystem);
1105 }
1106
1107 Future decompressFile(File zipFile, Directory destination) async {
1108 var result;
1109 if (Platform.isLinux) {
1110 result = await Process.run(
1111 "unzip", ["-o", zipFile.path, "-d", destination.path]);
1112 } else if (Platform.isMacOS) {
1113 result = await Process.run(
1114 "ditto", ["-x", "-k", zipFile.path, destination.path]);
1115 } else {
1116 throwUnsupportedPlatform();
1117 }
1118 if (result.exitCode != 0) {
1119 throwInternalError(
1120 "Failed to decompress ${zipFile.path} to ${destination.path}, "
1121 "error = ${result.exitCode}");
1122 }
1123 }
1124
1125 const String gcsRoot = "https://storage.googleapis.com";
1126 String gcsBucket = "fletch-archive";
1127
1128 Future downloadTool(String gcsPath, String zipFile, String toolName) async {
1129 Uri url = Uri.parse("$gcsRoot/$gcsBucket/$gcsPath/$zipFile");
1130 Directory tmpDir = Directory.systemTemp.createTempSync("fletch_download");
1131 File tmpZip = new File(join(tmpDir.path, zipFile));
1132
1133 OutputService outputService =
1134 new OutputService(commandSender.sendStdout, state.log);
1135 SDKServices service = new SDKServices(outputService);
1136 print("Downloading: $toolName");
1137 state.log("Downloading $toolName from $url to $tmpZip");
1138 await service.downloadWithProgress(url, tmpZip);
1139 print(""); // service.downloadWithProgress does not write newline when done.
1140
1141 // In the SDK, the tools directory is at the same level as the
1142 // internal (and bin) directory.
1143 Directory toolsDirectory =
1144 new Directory.fromUri(executable.resolve('../tools'));
1145 state.log("Decompressing ${tmpZip.path} to ${toolsDirectory.path}");
1146 await decompressFile(tmpZip, toolsDirectory);
1147 state.log("Deleting temporary directory ${tmpDir.path}");
1148 await tmpDir.delete(recursive: true);
1149 }
1150
1151 String gcsPath;
1152
1153 Version version = parseVersion(fletchVersion);
1154 if (version.isEdgeVersion) {
1155 print("WARNING: For bleeding edge a fixed image is used.");
1156 // For edge versions download use a well known version for now.
1157 var knownVersion = "0.3.0-edge.3c85dbafe006eb2ce16545aaf3df1352fa7a4500";
1158 gcsBucket = "fletch-temporary";
1159 gcsPath = "channels/be/raw/$knownVersion/sdk";
1160 } else if (version.isDevVersion) {
1161 // TODO(sgjesse): Change this to channels/dev/release at some point.
1162 gcsPath = "channels/dev/raw/$version/sdk";
1163 } else {
1164 print("Stable version not supported. Got version $version.");
1165 }
1166
1167 String osName;
1168 if (Platform.isLinux) {
1169 osName = "linux";
1170 } else if (Platform.isMacOS) {
1171 osName = "mac";
1172 } else {
1173 throwUnsupportedPlatform();
1174 }
1175
1176 String gccArmEmbedded = "gcc-arm-embedded-${osName}.zip";
1177 await downloadTool(gcsPath, gccArmEmbedded, "GCC ARM Embedded toolchain");
1178 String openocd = "openocd-${osName}.zip";
1179 await downloadTool(gcsPath, openocd, "Open On-Chip Debugger (OpenOCD)");
1180
1181 print("Third party tools downloaded");
1182
1183 return 0;
1184 }
1185
1186 Future<WorkerConnection> allocateWorker(IsolatePool pool) async {
1187 WorkerConnection workerConnection =
1188 new WorkerConnection(await pool.getIsolate(exitOnError: false));
1189 await workerConnection.beginSession();
1190 return workerConnection;
1191 }
1192
1193 SharedTask combineTasks(SharedTask task1, SharedTask task2) {
1194 if (task1 == null) return task2;
1195 if (task2 == null) return task1;
1196 return new CombinedTask(task1, task2);
1197 }
1198
1199 class CombinedTask extends SharedTask {
1200 // Keep this class simple, see note in superclass.
1201
1202 final SharedTask task1;
1203
1204 final SharedTask task2;
1205
1206 const CombinedTask(this.task1, this.task2);
1207
1208 Future<int> call(
1209 CommandSender commandSender,
1210 StreamIterator<ClientCommand> commandIterator) {
1211 return invokeCombinedTasks(commandSender, commandIterator, task1, task2);
1212 }
1213 }
1214
1215 Future<int> invokeCombinedTasks(
1216 CommandSender commandSender,
1217 StreamIterator<ClientCommand> commandIterator,
1218 SharedTask task1,
1219 SharedTask task2) async {
1220 int result = await task1(commandSender, commandIterator);
1221 if (result != 0) return result;
1222 return task2(commandSender, commandIterator);
1223 }
1224
1225 Future<String> getAgentVersion(InternetAddress host, int port) async {
1226 Socket socket;
1227 try {
1228 socket = await Socket.connect(host, port);
1229 handleSocketErrors(socket, "getAgentVersionSocket");
1230 } on SocketException catch (e) {
1231 return 'Error: no agent: $e';
1232 }
1233 try {
1234 AgentConnection connection = new AgentConnection(socket);
1235 return await connection.fletchVersion();
1236 } finally {
1237 socket.close();
1238 }
1239 }
1240
1241 Future<List<InternetAddress>> discoverDevices(
1242 {bool prefixWithNumber: false}) async {
1243 const ipV4AddressLength = 'xxx.xxx.xxx.xxx'.length;
1244 print("Looking for Dartino capable devices (will search for 5 seconds)...");
1245 MDnsClient client = new MDnsClient();
1246 await client.start();
1247 List<InternetAddress> result = <InternetAddress>[];
1248 String name = '_dartino_agent._tcp.local';
1249 await for (ResourceRecord ptr in client.lookup(RRType.PTR, name)) {
1250 String domain = ptr.domainName;
1251 await for (ResourceRecord srv in client.lookup(RRType.SRV, domain)) {
1252 String target = srv.target;
1253 await for (ResourceRecord a in client.lookup(RRType.A, target)) {
1254 InternetAddress address = a.address;
1255 if (!address.isLinkLocal) {
1256 result.add(address);
1257 String version = await getAgentVersion(address, AGENT_DEFAULT_PORT);
1258 String prefix = prefixWithNumber ? "${result.length}: " : "";
1259 print("${prefix}Device at "
1260 "${address.address.padRight(ipV4AddressLength + 1)} "
1261 "$target ($version)");
1262 }
1263 }
1264 }
1265 // TODO(karlklose): Verify that we got an A/IP4 result for the PTR result.
1266 // If not, maybe the cache was flushed before access and we need to query
1267 // for the SRV or A type again.
1268 }
1269 client.stop();
1270 return result;
1271 }
1272
1273 void showSessions() {
1274 Sessions.names.forEach(print);
1275 }
1276
1277 Future<int> showSessionSettings() async {
1278 Settings settings = SessionState.current.settings;
1279 Uri source = settings.source;
1280 if (source != null) {
1281 // This should output `source.toFilePath()`, but we do it like this to be
1282 // consistent with the format of the [Settings.packages] value.
1283 print('Configured from $source}');
1284 }
1285 settings.toJson().forEach((String key, value) {
1286 print('$key: $value');
1287 });
1288 return 0;
1289 }
1290
1291 Address parseAddress(String address, {int defaultPort: 0}) {
1292 String host;
1293 int port;
1294 List<String> parts = address.split(":");
1295 if (parts.length == 1) {
1296 host = InternetAddress.LOOPBACK_IP_V4.address;
1297 port = int.parse(
1298 parts[0],
1299 onError: (String source) {
1300 host = source;
1301 return defaultPort;
1302 });
1303 } else {
1304 host = parts[0];
1305 port = int.parse(
1306 parts[1],
1307 onError: (String source) {
1308 throwFatalError(
1309 DiagnosticKind.expectedAPortNumber, userInput: source);
1310 });
1311 }
1312 return new Address(host, port);
1313 }
1314
1315 class Address {
1316 final String host;
1317 final int port;
1318
1319 const Address(this.host, this.port);
1320
1321 String toString() => "Address($host, $port)";
1322
1323 String toJson() => "$host:$port";
1324
1325 bool operator ==(other) {
1326 if (other is! Address) return false;
1327 return other.host == host && other.port == port;
1328 }
1329
1330 int get hashCode => host.hashCode ^ port.hashCode;
1331 }
1332
1333 /// See ../verbs/documentation.dart for a definition of this format.
1334 Settings parseSettings(String jsonLikeData, Uri settingsUri) {
1335 String json = jsonLikeData.split("\n")
1336 .where((String line) => !line.trim().startsWith("//")).join("\n");
1337 var userSettings;
1338 try {
1339 userSettings = JSON.decode(json);
1340 } on FormatException catch (e) {
1341 throwFatalError(
1342 DiagnosticKind.settingsNotJson, uri: settingsUri, message: e.message);
1343 }
1344 if (userSettings is! Map) {
1345 throwFatalError(DiagnosticKind.settingsNotAMap, uri: settingsUri);
1346 }
1347 Uri packages;
1348 final List<String> options = <String>[];
1349 final Map<String, String> constants = <String, String>{};
1350 Address deviceAddress;
1351 DeviceType deviceType;
1352 IncrementalMode incrementalMode = IncrementalMode.none;
1353 userSettings.forEach((String key, value) {
1354 switch (key) {
1355 case "packages":
1356 if (value != null) {
1357 if (value is! String) {
1358 throwFatalError(
1359 DiagnosticKind.settingsPackagesNotAString, uri: settingsUri,
1360 userInput: '$value');
1361 }
1362 packages = settingsUri.resolve(value);
1363 }
1364 break;
1365
1366 case "options":
1367 if (value != null) {
1368 if (value is! List) {
1369 throwFatalError(
1370 DiagnosticKind.settingsOptionsNotAList, uri: settingsUri,
1371 userInput: "$value");
1372 }
1373 for (var option in value) {
1374 if (option is! String) {
1375 throwFatalError(
1376 DiagnosticKind.settingsOptionNotAString, uri: settingsUri,
1377 userInput: '$option');
1378 }
1379 if (option.startsWith("-D")) {
1380 throwFatalError(
1381 DiagnosticKind.settingsCompileTimeConstantAsOption,
1382 uri: settingsUri, userInput: '$option');
1383 }
1384 options.add(option);
1385 }
1386 }
1387 break;
1388
1389 case "constants":
1390 if (value != null) {
1391 if (value is! Map) {
1392 throwFatalError(
1393 DiagnosticKind.settingsConstantsNotAMap, uri: settingsUri);
1394 }
1395 value.forEach((String key, value) {
1396 if (value == null) {
1397 // Ignore.
1398 } else if (value is bool || value is int || value is String) {
1399 constants[key] = '$value';
1400 } else {
1401 throwFatalError(
1402 DiagnosticKind.settingsUnrecognizedConstantValue,
1403 uri: settingsUri, userInput: key,
1404 additionalUserInput: '$value');
1405 }
1406 });
1407 }
1408 break;
1409
1410 case "device_address":
1411 if (value != null) {
1412 if (value is! String) {
1413 throwFatalError(
1414 DiagnosticKind.settingsDeviceAddressNotAString,
1415 uri: settingsUri, userInput: '$value');
1416 }
1417 deviceAddress =
1418 parseAddress(value, defaultPort: AGENT_DEFAULT_PORT);
1419 }
1420 break;
1421
1422 case "device_type":
1423 if (value != null) {
1424 if (value is! String) {
1425 throwFatalError(
1426 DiagnosticKind.settingsDeviceTypeNotAString,
1427 uri: settingsUri, userInput: '$value');
1428 }
1429 deviceType = parseDeviceType(value);
1430 if (deviceType == null) {
1431 throwFatalError(
1432 DiagnosticKind.settingsDeviceTypeUnrecognized,
1433 uri: settingsUri, userInput: '$value');
1434 }
1435 }
1436 break;
1437
1438 case "incremental_mode":
1439 if (value != null) {
1440 if (value is! String) {
1441 throwFatalError(
1442 DiagnosticKind.settingsIncrementalModeNotAString,
1443 uri: settingsUri, userInput: '$value');
1444 }
1445 incrementalMode = parseIncrementalMode(value);
1446 if (incrementalMode == null) {
1447 throwFatalError(
1448 DiagnosticKind.settingsIncrementalModeUnrecognized,
1449 uri: settingsUri, userInput: '$value');
1450 }
1451 }
1452 break;
1453
1454 default:
1455 throwFatalError(
1456 DiagnosticKind.settingsUnrecognizedKey, uri: settingsUri,
1457 userInput: key);
1458 break;
1459 }
1460 });
1461 return new Settings.fromSource(settingsUri,
1462 packages, options, constants, deviceAddress, deviceType, incrementalMode);
1463 }
1464
1465 class Settings {
1466 final Uri source;
1467
1468 final Uri packages;
1469
1470 final List<String> options;
1471
1472 final Map<String, String> constants;
1473
1474 final Address deviceAddress;
1475
1476 final DeviceType deviceType;
1477
1478 final IncrementalMode incrementalMode;
1479
1480 const Settings(
1481 this.packages,
1482 this.options,
1483 this.constants,
1484 this.deviceAddress,
1485 this.deviceType,
1486 this.incrementalMode) : source = null;
1487
1488 const Settings.fromSource(
1489 this.source,
1490 this.packages,
1491 this.options,
1492 this.constants,
1493 this.deviceAddress,
1494 this.deviceType,
1495 this.incrementalMode);
1496
1497 const Settings.empty()
1498 : this(null, const <String>[], const <String, String>{}, null, null,
1499 IncrementalMode.none);
1500
1501 Settings copyWith({
1502 Uri packages,
1503 List<String> options,
1504 Map<String, String> constants,
1505 Address deviceAddress,
1506 DeviceType deviceType,
1507 IncrementalMode incrementalMode}) {
1508
1509 if (packages == null) {
1510 packages = this.packages;
1511 }
1512 if (options == null) {
1513 options = this.options;
1514 }
1515 if (constants == null) {
1516 constants = this.constants;
1517 }
1518 if (deviceAddress == null) {
1519 deviceAddress = this.deviceAddress;
1520 }
1521 if (deviceType == null) {
1522 deviceType = this.deviceType;
1523 }
1524 if (incrementalMode == null) {
1525 incrementalMode = this.incrementalMode;
1526 }
1527 return new Settings(
1528 packages,
1529 options,
1530 constants,
1531 deviceAddress,
1532 deviceType,
1533 incrementalMode);
1534 }
1535
1536 String toString() {
1537 return "Settings("
1538 "packages: $packages, "
1539 "options: $options, "
1540 "constants: $constants, "
1541 "device_address: $deviceAddress, "
1542 "device_type: $deviceType, "
1543 "incremental_mode: $incrementalMode)";
1544 }
1545
1546 Map<String, dynamic> toJson() {
1547 Map<String, dynamic> result = <String, dynamic>{};
1548
1549 void addIfNotNull(String name, value) {
1550 if (value != null) {
1551 result[name] = value;
1552 }
1553 }
1554
1555 addIfNotNull("packages", packages == null ? null : "$packages");
1556 addIfNotNull("options", options);
1557 addIfNotNull("constants", constants);
1558 addIfNotNull("device_address", deviceAddress);
1559 addIfNotNull(
1560 "device_type",
1561 deviceType == null ? null : unParseDeviceType(deviceType));
1562 addIfNotNull(
1563 "incremental_mode",
1564 incrementalMode == null
1565 ? null : unparseIncrementalMode(incrementalMode));
1566
1567 return result;
1568 }
1569 }
OLDNEW
« no previous file with comments | « pkg/fletchc/lib/src/verbs/x_upgrade_verb.dart ('k') | pkg/fletchc/lib/src/worker/worker_main.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698