Skip to content

Commit 7ab6956

Browse files
authored
Add a build command to felt (flutter#12303)
- The build command supports a `--watch` or `-w` flag to watch for changes and rebuild. Fixes flutter#40392
1 parent 5f3b28d commit 7ab6956

File tree

3 files changed

+188
-1
lines changed

3 files changed

+188
-1
lines changed

lib/web_ui/dev/build.dart

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:args/command_runner.dart';
8+
import 'package:meta/meta.dart';
9+
import 'package:path/path.dart' as path;
10+
import 'package:watcher/watcher.dart';
11+
12+
import 'environment.dart';
13+
import 'utils.dart';
14+
15+
class BuildCommand extends Command<bool> {
16+
BuildCommand() {
17+
argParser
18+
..addFlag(
19+
'watch',
20+
abbr: 'w',
21+
help: 'Run the build in watch mode so it rebuilds whenever a change'
22+
'is made.',
23+
);
24+
}
25+
26+
@override
27+
String get name => 'build';
28+
29+
@override
30+
String get description => 'Build the Flutter web engine.';
31+
32+
bool get isWatchMode => argResults['watch'];
33+
34+
@override
35+
FutureOr<bool> run() async {
36+
final FilePath libPath = FilePath.fromWebUi('lib');
37+
final Pipeline buildPipeline = Pipeline(steps: <PipelineStep>[
38+
gn,
39+
ninja,
40+
]);
41+
await buildPipeline.start();
42+
43+
if (isWatchMode) {
44+
print('Initial build done!');
45+
print('Watching directory: ${libPath.relativeToCwd}/');
46+
PipelineWatcher(
47+
dir: libPath.absolute,
48+
pipeline: buildPipeline,
49+
).start();
50+
// Return a never-ending future.
51+
return Completer<bool>().future;
52+
} else {
53+
return true;
54+
}
55+
}
56+
}
57+
58+
Future<void> gn() {
59+
print('Running gn...');
60+
return runProcess(
61+
path.join(environment.flutterDirectory.path, 'tools', 'gn'),
62+
<String>[
63+
'--unopt',
64+
'--full-dart-sdk',
65+
],
66+
);
67+
}
68+
69+
// TODO(mdebbar): Make the ninja step interruptable in the pipeline.
70+
Future<void> ninja() {
71+
print('Running ninja...');
72+
return runProcess('ninja', <String>[
73+
'-C',
74+
environment.hostDebugUnoptDir.path,
75+
]);
76+
}
77+
78+
enum PipelineStatus {
79+
idle,
80+
started,
81+
stopping,
82+
stopped,
83+
error,
84+
done,
85+
}
86+
87+
typedef PipelineStep = Future<void> Function();
88+
89+
class Pipeline {
90+
Pipeline({@required this.steps});
91+
92+
final Iterable<PipelineStep> steps;
93+
94+
Future<dynamic> _currentStepFuture;
95+
96+
PipelineStatus status = PipelineStatus.idle;
97+
98+
Future<void> start() async {
99+
status = PipelineStatus.started;
100+
try {
101+
for (PipelineStep step in steps) {
102+
if (status != PipelineStatus.started) {
103+
break;
104+
}
105+
_currentStepFuture = step();
106+
await _currentStepFuture;
107+
}
108+
status = PipelineStatus.done;
109+
} catch (_) {
110+
status = PipelineStatus.error;
111+
} finally {
112+
_currentStepFuture = null;
113+
}
114+
}
115+
116+
Future<void> stop() {
117+
status = PipelineStatus.stopping;
118+
return (_currentStepFuture ?? Future<void>.value(null)).then((_) {
119+
status = PipelineStatus.stopped;
120+
});
121+
}
122+
}
123+
124+
class PipelineWatcher {
125+
PipelineWatcher({
126+
@required this.dir,
127+
@required this.pipeline,
128+
}) : watcher = DirectoryWatcher(dir);
129+
130+
/// The path of the directory to watch for changes.
131+
final String dir;
132+
133+
/// The pipeline to be executed when an event is fired by the watcher.
134+
final Pipeline pipeline;
135+
136+
/// Used to watch a directory for any file system changes.
137+
final DirectoryWatcher watcher;
138+
139+
void start() {
140+
watcher.events.listen(_onEvent);
141+
}
142+
143+
int _pipelineRunCount = 0;
144+
Timer _scheduledPipeline;
145+
146+
void _onEvent(WatchEvent event) {
147+
final String relativePath = path.relative(event.path, from: dir);
148+
print('- [${event.type}] ${relativePath}');
149+
150+
_pipelineRunCount++;
151+
_scheduledPipeline?.cancel();
152+
_scheduledPipeline = Timer(const Duration(milliseconds: 100), () {
153+
_scheduledPipeline = null;
154+
_runPipeline();
155+
});
156+
}
157+
158+
void _runPipeline() {
159+
int runCount;
160+
switch (pipeline.status) {
161+
case PipelineStatus.started:
162+
pipeline.stop().then((_) {
163+
runCount = _pipelineRunCount;
164+
pipeline.start().then((_) => _pipelineDone(runCount));
165+
});
166+
break;
167+
168+
case PipelineStatus.stopping:
169+
// We are already trying to stop the pipeline. No need to do anything.
170+
break;
171+
172+
default:
173+
runCount = _pipelineRunCount;
174+
pipeline.start().then((_) => _pipelineDone(runCount));
175+
break;
176+
}
177+
}
178+
179+
void _pipelineDone(int pipelineRunCount) {
180+
if (pipelineRunCount == _pipelineRunCount) {
181+
print('*** Done! ***');
182+
}
183+
}
184+
}

lib/web_ui/dev/felt.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:io' as io;
66

77
import 'package:args/command_runner.dart';
88

9+
import 'build.dart';
910
import 'licenses.dart';
1011
import 'test_runner.dart';
1112

@@ -14,7 +15,8 @@ CommandRunner runner = CommandRunner<bool>(
1415
'Command-line utility for building and testing Flutter web engine.',
1516
)
1617
..addCommand(LicensesCommand())
17-
..addCommand(TestsCommand());
18+
..addCommand(TestsCommand())
19+
..addCommand(BuildCommand());
1820

1921
void main(List<String> args) async {
2022
if (args.isEmpty) {

lib/web_ui/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ dev_dependencies:
1515
build_runner: 1.6.5
1616
build_test: 0.10.8
1717
build_web_compilers: 2.1.5
18+
watcher: 0.9.7+12
1819
web_engine_tester:
1920
path: ../../web_sdk/web_engine_tester

0 commit comments

Comments
 (0)