diff --git a/lib/web_ui/dev/build.dart b/lib/web_ui/dev/build.dart new file mode 100644 index 0000000000000..3b742e1a4116c --- /dev/null +++ b/lib/web_ui/dev/build.dart @@ -0,0 +1,184 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:args/command_runner.dart'; +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; +import 'package:watcher/watcher.dart'; + +import 'environment.dart'; +import 'utils.dart'; + +class BuildCommand extends Command { + BuildCommand() { + argParser + ..addFlag( + 'watch', + abbr: 'w', + help: 'Run the build in watch mode so it rebuilds whenever a change' + 'is made.', + ); + } + + @override + String get name => 'build'; + + @override + String get description => 'Build the Flutter web engine.'; + + bool get isWatchMode => argResults['watch']; + + @override + FutureOr run() async { + final FilePath libPath = FilePath.fromWebUi('lib'); + final Pipeline buildPipeline = Pipeline(steps: [ + gn, + ninja, + ]); + await buildPipeline.start(); + + if (isWatchMode) { + print('Initial build done!'); + print('Watching directory: ${libPath.relativeToCwd}/'); + PipelineWatcher( + dir: libPath.absolute, + pipeline: buildPipeline, + ).start(); + // Return a never-ending future. + return Completer().future; + } else { + return true; + } + } +} + +Future gn() { + print('Running gn...'); + return runProcess( + path.join(environment.flutterDirectory.path, 'tools', 'gn'), + [ + '--unopt', + '--full-dart-sdk', + ], + ); +} + +// TODO(mdebbar): Make the ninja step interruptable in the pipeline. +Future ninja() { + print('Running ninja...'); + return runProcess('ninja', [ + '-C', + environment.hostDebugUnoptDir.path, + ]); +} + +enum PipelineStatus { + idle, + started, + stopping, + stopped, + error, + done, +} + +typedef PipelineStep = Future Function(); + +class Pipeline { + Pipeline({@required this.steps}); + + final Iterable steps; + + Future _currentStepFuture; + + PipelineStatus status = PipelineStatus.idle; + + Future start() async { + status = PipelineStatus.started; + try { + for (PipelineStep step in steps) { + if (status != PipelineStatus.started) { + break; + } + _currentStepFuture = step(); + await _currentStepFuture; + } + status = PipelineStatus.done; + } catch (_) { + status = PipelineStatus.error; + } finally { + _currentStepFuture = null; + } + } + + Future stop() { + status = PipelineStatus.stopping; + return (_currentStepFuture ?? Future.value(null)).then((_) { + status = PipelineStatus.stopped; + }); + } +} + +class PipelineWatcher { + PipelineWatcher({ + @required this.dir, + @required this.pipeline, + }) : watcher = DirectoryWatcher(dir); + + /// The path of the directory to watch for changes. + final String dir; + + /// The pipeline to be executed when an event is fired by the watcher. + final Pipeline pipeline; + + /// Used to watch a directory for any file system changes. + final DirectoryWatcher watcher; + + void start() { + watcher.events.listen(_onEvent); + } + + int _pipelineRunCount = 0; + Timer _scheduledPipeline; + + void _onEvent(WatchEvent event) { + final String relativePath = path.relative(event.path, from: dir); + print('- [${event.type}] ${relativePath}'); + + _pipelineRunCount++; + _scheduledPipeline?.cancel(); + _scheduledPipeline = Timer(const Duration(milliseconds: 100), () { + _scheduledPipeline = null; + _runPipeline(); + }); + } + + void _runPipeline() { + int runCount; + switch (pipeline.status) { + case PipelineStatus.started: + pipeline.stop().then((_) { + runCount = _pipelineRunCount; + pipeline.start().then((_) => _pipelineDone(runCount)); + }); + break; + + case PipelineStatus.stopping: + // We are already trying to stop the pipeline. No need to do anything. + break; + + default: + runCount = _pipelineRunCount; + pipeline.start().then((_) => _pipelineDone(runCount)); + break; + } + } + + void _pipelineDone(int pipelineRunCount) { + if (pipelineRunCount == _pipelineRunCount) { + print('*** Done! ***'); + } + } +} diff --git a/lib/web_ui/dev/felt.dart b/lib/web_ui/dev/felt.dart index c6185ff29ef2d..4b49a7506002b 100644 --- a/lib/web_ui/dev/felt.dart +++ b/lib/web_ui/dev/felt.dart @@ -6,6 +6,7 @@ import 'dart:io' as io; import 'package:args/command_runner.dart'; +import 'build.dart'; import 'licenses.dart'; import 'test_runner.dart'; @@ -14,7 +15,8 @@ CommandRunner runner = CommandRunner( 'Command-line utility for building and testing Flutter web engine.', ) ..addCommand(LicensesCommand()) - ..addCommand(TestsCommand()); + ..addCommand(TestsCommand()) + ..addCommand(BuildCommand()); void main(List args) async { if (args.isEmpty) { diff --git a/lib/web_ui/pubspec.yaml b/lib/web_ui/pubspec.yaml index 8bf8480924b87..27ca873db95bc 100644 --- a/lib/web_ui/pubspec.yaml +++ b/lib/web_ui/pubspec.yaml @@ -15,5 +15,6 @@ dev_dependencies: build_runner: 1.6.5 build_test: 0.10.8 build_web_compilers: 2.1.5 + watcher: 0.9.7+12 web_engine_tester: path: ../../web_sdk/web_engine_tester