diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 45c27d7f5dc..60d96556c81 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -59,6 +59,10 @@ class RunCommand extends RunCommandBase { defaultsTo: true, help: 'Don\'t terminate the \'flutter run\' process after starting the application.'); + // Hidden option to ship all the sources of the current project over to the + // embedder via the DevFS observatory API. + argParser.addFlag('devfs', negatable: false, hide: true); + // Hidden option to enable a benchmarking mode. This will run the given // application, measure the startup time and the app restart time, write the // results out to 'refresh_benchmark.json', and exit. This flag is intended @@ -116,7 +120,8 @@ class RunCommand extends RunCommandBase { RunAndStayResident runner = new RunAndStayResident( deviceForCommand, target: target, - debuggingOptions: options + debuggingOptions: options, + useDevFS: argResults['devfs'] ); return runner.run( diff --git a/packages/flutter_tools/lib/src/observatory.dart b/packages/flutter_tools/lib/src/observatory.dart index 75c850ae772..a86da1f013f 100644 --- a/packages/flutter_tools/lib/src/observatory.dart +++ b/packages/flutter_tools/lib/src/observatory.dart @@ -168,10 +168,8 @@ class Observatory { // Write multiple files into a file system. Future writeDevFSFiles(String fsName, { - String path, List files }) { - assert(path != null); assert(files != null); return sendRequest('_writeDevFSFiles', { @@ -188,8 +186,10 @@ class Observatory { } /// The complete list of a file system. - Future> listDevFSFiles() { - return sendRequest('_listDevFSFiles').then((Response response) { + Future> listDevFSFiles(String fsName) { + return sendRequest('_listDevFSFiles', { + 'fsName': fsName + }).then((Response response) { return response.response['files']; }); } diff --git a/packages/flutter_tools/lib/src/run.dart b/packages/flutter_tools/lib/src/run.dart index 045701ee996..a9995519284 100644 --- a/packages/flutter_tools/lib/src/run.dart +++ b/packages/flutter_tools/lib/src/run.dart @@ -14,6 +14,7 @@ import 'build_info.dart'; import 'commands/build_apk.dart'; import 'commands/install.dart'; import 'commands/trace.dart'; +import 'dart/package_map.dart'; import 'device.dart'; import 'globals.dart'; import 'observatory.dart'; @@ -35,13 +36,15 @@ class RunAndStayResident { this.device, { this.target, this.debuggingOptions, - this.usesTerminalUI: true + this.usesTerminalUI: true, + this.useDevFS: false }); final Device device; final String target; final DebuggingOptions debuggingOptions; final bool usesTerminalUI; + final bool useDevFS; ApplicationPackage _package; String _mainPath; @@ -250,6 +253,8 @@ class RunAndStayResident { } else if (lower == 'q' || code == AnsiTerminal.KEY_F10) { // F10, exit _stopApp(); + } else if (useDevFS && lower == 'd') { + _updateDevFS(); } }); } @@ -288,9 +293,78 @@ class RunAndStayResident { }); } + DevFS devFS; + + Future _updateDevFS() async { + if (devFS == null) { + devFS = new DevFS(Directory.current, observatory); + + try { + await devFS.init(); + } catch (error) { + devFS = null; + printError('Error initing DevFS: $error'); + return null; + } + } + + // Send the root and lib directories. + Directory directory = Directory.current; + _sendFiles(directory, '', _dartFiles(directory.listSync())); + + directory = new Directory('lib'); + _sendFiles(directory, 'lib', _dartFiles(directory.listSync(recursive: true, followLinks: false))); + + // Send the packages. + if (FileSystemEntity.isFileSync(kPackagesFileName)) { + PackageMap packageMap = new PackageMap(kPackagesFileName); + + for (String packageName in packageMap.map.keys) { + Uri uri = packageMap.map[packageName]; + // Ignore self-references. + if (uri.toString() == 'lib/') + continue; + Directory directory = new Directory.fromUri(uri); + if (directory.existsSync()) { + _sendFiles( + directory, + 'packages/$packageName', + _dartFiles(directory.listSync(recursive: true, followLinks: false)) + ); + } + } + } + + try { + await devFS.flush(); + } catch (error) { + printError('Error sending DevFS files: $error'); + } + } + + void _sendFiles(Directory base, String prefix, List files) { + String basePath = base.path; + + for (File file in files) { + String devPath = file.path.substring(basePath.length); + if (devPath.startsWith('/')) + devPath = devPath.substring(1); + devFS.stageFile(prefix.isEmpty ? devPath : '$prefix/$devPath', file); + } + } + + List _dartFiles(List entities) { + return new List.from(entities + .where((FileSystemEntity entity) => entity is File) + .where((File file) => file.path.endsWith('.dart'))); + } + void _printHelp() { String restartText = device.supportsRestart ? ', "r" or F5 to restart the app,' : ''; printStatus('Type "h" or F1 for help$restartText and "q", F10, or ctrl-c to quit.'); + + if (useDevFS) + printStatus('Type "d" to send modified project files to the the client\'s DevFS.'); } void _stopLogger() { @@ -341,3 +415,71 @@ void writeRunBenchmarkFile(Stopwatch startTime, [Stopwatch restartTime]) { new File(benchmarkOut).writeAsStringSync(toPrettyJson(data)); printStatus('Run benchmark written to $benchmarkOut ($data).'); } + +class DevFS { + DevFS(this.directory, this.observatory) { + fsName = path.basename(directory.path); + } + + final Directory directory; + final Observatory observatory; + + String fsName; + Map entries = {}; + + Future init() => observatory.createDevFS(fsName); + + void stageFile(String devPath, File file) { + entries.putIfAbsent(devPath, () => new DevFSFileEntry(devPath, file)); + } + + /// Flush any modified files to the devfs. + Future flush() async { + List toSend = entries.values + .where((DevFSFileEntry entry) => entry.isModified) + .toList(); + + for (DevFSFileEntry entry in toSend) { + printTrace('sending devfs://$fsName/${entry.devPath}'); + entry.updateLastModified(); + } + + Status status = logger.startProgress('Sending ${toSend.length} files...'); + + if (toSend.isEmpty) { + status.stop(showElapsedTime: true); + return; + } + + await observatory.writeDevFSFiles(fsName, files: toSend.map((DevFSFileEntry entry) { + return new _DevFSFile('/${entry.devPath}', entry.file); + }).toList()).whenComplete(() { + status.stop(showElapsedTime: true); + }); + } + + Future> listDevFSFiles() => observatory.listDevFSFiles(fsName); +} + +class DevFSFileEntry { + DevFSFileEntry(this.devPath, this.file); + + String devPath; + File file; + DateTime lastModified; + + bool get isModified => lastModified == null || file.lastModifiedSync().isAfter(lastModified); + + void updateLastModified() { + lastModified = file.lastModifiedSync(); + } +} + +class _DevFSFile extends DevFSFile { + _DevFSFile(String path, this.file) : super(path); + + final File file; + + @override + List getContents() => file.readAsBytesSync(); +}