Skip to content

Commit 73fa54f

Browse files
authored
migrate the build command to use the build daemon (#298)
1 parent 3a75266 commit 73fa54f

File tree

11 files changed

+112
-187
lines changed

11 files changed

+112
-187
lines changed

webdev/lib/src/command/build_command.dart

Lines changed: 61 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -3,85 +3,20 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6-
import 'dart:io';
7-
import 'dart:isolate';
6+
import 'dart:io' show Directory;
87

98
import 'package:args/args.dart';
109
import 'package:args/command_runner.dart';
11-
import 'package:stack_trace/stack_trace.dart';
10+
import 'package:build_daemon/client.dart';
11+
import 'package:build_daemon/data/build_status.dart';
12+
import 'package:build_daemon/data/build_target.dart';
13+
import 'package:logging/logging.dart';
1214

15+
import '../daemon_client.dart';
16+
import '../logging.dart';
1317
import 'configuration.dart';
1418
import 'shared.dart';
1519

16-
const _bootstrapScript = r'''
17-
import 'dart:io';
18-
import 'dart:isolate';
19-
20-
import 'package:build_runner/build_script_generate.dart';
21-
import 'package:path/path.dart' as p;
22-
23-
void main(List<String> args, [SendPort sendPort]) async {
24-
var buildScript = await generateBuildScript();
25-
var scriptFile = new File(scriptLocation)..createSync(recursive: true);
26-
scriptFile.writeAsStringSync(buildScript);
27-
sendPort.send(p.absolute(scriptLocation));
28-
}
29-
''';
30-
const _packagesFileName = '.packages';
31-
32-
Future<Uri> _buildRunnerScript() async {
33-
var packagesFile = File(_packagesFileName);
34-
if (!packagesFile.existsSync()) {
35-
throw FileSystemException(
36-
'A `$_packagesFileName` file does not exist in the target directory.',
37-
packagesFile.absolute.path);
38-
}
39-
40-
var dataUri = Uri.dataFromString(_bootstrapScript);
41-
42-
var messagePort = ReceivePort();
43-
var exitPort = ReceivePort();
44-
var errorPort = ReceivePort();
45-
46-
try {
47-
await Isolate.spawnUri(dataUri, [], messagePort.sendPort,
48-
onExit: exitPort.sendPort,
49-
onError: errorPort.sendPort,
50-
errorsAreFatal: true,
51-
packageConfig: Uri.file(_packagesFileName));
52-
53-
var allErrorsFuture = errorPort.forEach((error) {
54-
var errorList = error as List;
55-
var message = errorList[0] as String;
56-
var stack = StackTrace.fromString(errorList[1] as String);
57-
58-
stderr.writeln(message);
59-
stderr.writeln(stack);
60-
});
61-
62-
var items = await Future.wait([
63-
messagePort.toList(),
64-
allErrorsFuture,
65-
exitPort.first.whenComplete(() {
66-
messagePort.close();
67-
errorPort.close();
68-
})
69-
]);
70-
71-
var messages = items[0] as List;
72-
if (messages.isEmpty) {
73-
throw StateError('An error occurred while bootstrapping.');
74-
}
75-
76-
assert(messages.length == 1);
77-
return Uri.file(messages.single as String);
78-
} finally {
79-
messagePort.close();
80-
exitPort.close();
81-
errorPort.close();
82-
}
83-
}
84-
8520
/// Command to execute pub run build_runner build.
8621
class BuildCommand extends Command<int> {
8722
@override
@@ -98,71 +33,74 @@ class BuildCommand extends Command<int> {
9833
}
9934

10035
@override
101-
Future<int> run() {
36+
Future<int> run() async {
10237
if (argResults.rest.isNotEmpty) {
10338
throw UsageException(
10439
'Arguments were provided that are not supported: '
10540
'"${argResults.rest.join(' ')}".',
10641
argParser.usage);
10742
}
108-
return runCore('build', extraArgs: ['--fail-on-severe']);
109-
}
11043

111-
Future<int> runCore(String command, {List<String> extraArgs}) async {
11244
var configuration = Configuration.fromArgs(argResults);
45+
setVerbosity(configuration.verbose);
11346
var pubspecLock = await readPubspecLock(configuration);
114-
final arguments = [command]
115-
..addAll(extraArgs ?? const [])
116-
..addAll(buildRunnerArgs(pubspecLock, configuration));
117-
118-
stdout.write('Creating build script');
119-
var stopwatch = Stopwatch()..start();
120-
var buildRunnerScript = await _buildRunnerScript();
121-
stdout.writeln(', took ${stopwatch.elapsedMilliseconds}ms');
122-
123-
var exitCode = 0;
124-
125-
// Heavily inspired by dart-lang/build @ 0c77443dd7
126-
// /build_runner/bin/build_runner.dart#L58-L85
127-
var exitPort = ReceivePort();
128-
var errorPort = ReceivePort();
129-
var messagePort = ReceivePort();
130-
var errorListener = errorPort.listen((e) {
131-
stderr.writeln('\n\nYou have hit a bug in build_runner');
132-
stderr.writeln('Please file an issue with reproduction steps at '
133-
'https://github.com/dart-lang/build/issues\n\n');
134-
final error = e[0];
135-
final trace = e[1] as String;
136-
stderr.writeln(error);
137-
stderr.writeln(Trace.parse(trace).terse);
138-
if (exitCode == 0) exitCode = 1;
139-
});
47+
final arguments = buildRunnerArgs(pubspecLock, configuration);
14048

14149
try {
142-
await Isolate.spawnUri(buildRunnerScript, arguments, messagePort.sendPort,
143-
onExit: exitPort.sendPort,
144-
onError: errorPort.sendPort,
145-
automaticPackageResolution: true);
146-
StreamSubscription exitCodeListener;
147-
exitCodeListener = messagePort.listen((isolateExitCode) {
148-
if (isolateExitCode is! int) {
149-
throw StateError(
150-
'Bad response from isolate, expected an exit code but got '
151-
'$isolateExitCode');
50+
logHandler(Level.INFO, 'Connecting to the build daemon...');
51+
var client = await connectClient(
52+
Directory.current.path,
53+
arguments,
54+
(serverLog) {
55+
var recordLevel = levelForLog(serverLog) ?? Level.INFO;
56+
logHandler(recordLevel, trimLevel(recordLevel, serverLog.log));
57+
},
58+
);
59+
OutputLocation outputLocation;
60+
if (configuration.outputPath != null) {
61+
outputLocation = OutputLocation((b) => b
62+
..output = configuration.outputPath
63+
..useSymlinks = false
64+
..hoist = configuration.outputInput.isNotEmpty);
65+
}
66+
client.registerBuildTarget(DefaultBuildTarget((b) => b
67+
..target = configuration.outputInput
68+
..outputLocation = outputLocation?.toBuilder()));
69+
client.startBuild();
70+
var exitCode = 0;
71+
var gotBuildStart = false;
72+
await for (final result in client.buildResults) {
73+
var targetResult = result.results.firstWhere(
74+
(buildResult) => buildResult.target == configuration.outputInput,
75+
orElse: () => null);
76+
if (targetResult == null) continue;
77+
// We ignore any builds that happen before we get a `started` event,
78+
// because those could be stale (from some other client).
79+
gotBuildStart =
80+
gotBuildStart || targetResult.status == BuildStatus.started;
81+
if (!gotBuildStart) continue;
82+
83+
// Shouldn't happen, but being a bit defensive here.
84+
if (targetResult.status == BuildStatus.started) continue;
85+
86+
if (targetResult.status == BuildStatus.failed) {
87+
exitCode = 1;
15288
}
153-
exitCode = isolateExitCode as int;
154-
exitCodeListener.cancel();
155-
exitCodeListener = null;
156-
});
157-
await exitPort.first;
158-
await errorListener.cancel();
159-
await exitCodeListener?.cancel();
16089

90+
if (targetResult.error?.isNotEmpty == true) {
91+
logHandler(Level.SEVERE, targetResult.error);
92+
}
93+
break;
94+
}
95+
await client.close();
16196
return exitCode;
162-
} finally {
163-
exitPort.close();
164-
errorPort.close();
165-
messagePort.close();
97+
} on OptionsSkew catch (_) {
98+
logHandler(
99+
Level.SEVERE,
100+
'Incompatible options with current running build daemon.\n\n'
101+
'Please stop other WebDev instances running in this directory '
102+
'before starting a new instance with these options.\n\n');
103+
return 1;
166104
}
167105
}
168106
}

webdev/lib/src/command/configuration.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import 'package:args/args.dart';
66
import 'package:logging/logging.dart';
77

8+
import '../logging.dart';
89
import '../serve/injected/configuration.dart';
9-
import '../serve/logging.dart';
1010

1111
const autoOption = 'auto';
1212
const chromeDebugPortFlag = 'chrome-debug-port';
@@ -169,12 +169,12 @@ class Configuration {
169169
String outputInput;
170170
if (output != 'NONE') {
171171
var splitOutput = output.split(':');
172-
if (splitOutput.length == 2) {
173-
outputInput = splitOutput.first;
174-
outputPath = splitOutput.last;
175-
} else {
172+
if (splitOutput.length == 1) {
176173
outputInput = '';
177174
outputPath = output;
175+
} else {
176+
outputInput = splitOutput.first;
177+
outputPath = splitOutput.skip(1).join(':');
178178
}
179179
}
180180

webdev/lib/src/command/daemon_command.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import 'package:pedantic/pedantic.dart';
1212
import '../daemon/app_domain.dart';
1313
import '../daemon/daemon.dart';
1414
import '../daemon/daemon_domain.dart';
15+
import '../logging.dart';
1516
import '../serve/dev_workflow.dart';
16-
import '../serve/logging.dart';
1717
import '../serve/utils.dart';
1818
import 'configuration.dart';
1919
import 'shared.dart';
@@ -63,8 +63,7 @@ class DaemonCommand extends Command<int> {
6363
var configuration =
6464
Configuration(launchInChrome: true, debug: true, autoRun: false);
6565
var pubspecLock = await readPubspecLock(configuration);
66-
var buildOptions =
67-
buildRunnerArgs(pubspecLock, configuration, includeOutput: false);
66+
var buildOptions = buildRunnerArgs(pubspecLock, configuration);
6867
var port = await findUnusedPort();
6968
workflow = await DevWorkflow.start(
7069
configuration,

webdev/lib/src/command/serve_command.dart

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import 'dart:io';
88
import 'package:args/args.dart';
99
import 'package:args/command_runner.dart';
1010

11+
import '../logging.dart';
1112
import '../serve/dev_workflow.dart';
12-
import '../serve/logging.dart';
1313
import 'configuration.dart';
1414
import 'shared.dart';
1515

@@ -102,11 +102,10 @@ refresh: Performs a full page refresh.
102102
var pubspecLock = await readPubspecLock(configuration);
103103
// Forward remaining arguments as Build Options to the Daemon.
104104
// This isn't documented. Should it be advertised?
105-
var buildOptions =
106-
buildRunnerArgs(pubspecLock, configuration, includeOutput: false)
107-
..addAll(argResults.rest
108-
.where((arg) => !arg.contains(':') || arg.startsWith('--'))
109-
.toList());
105+
var buildOptions = buildRunnerArgs(pubspecLock, configuration)
106+
..addAll(argResults.rest
107+
.where((arg) => !arg.contains(':') || arg.startsWith('--'))
108+
.toList());
110109
var directoryArgs = argResults.rest
111110
.where((arg) => arg.contains(':') || !arg.startsWith('--'))
112111
.toList();

webdev/lib/src/command/shared.dart

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,12 @@ void addSharedArgs(ArgParser argParser,
4646
/// Parses the provided [Configuration] to return a list of
4747
/// `package:build_runner` appropriate arguments.
4848
List<String> buildRunnerArgs(
49-
PubspecLock pubspecLock, Configuration configuration,
50-
{bool includeOutput}) {
51-
includeOutput ??= true;
49+
PubspecLock pubspecLock, Configuration configuration) {
5250
var arguments = <String>[];
5351
if (configuration.release) {
5452
arguments.add('--$releaseFlag');
5553
}
5654

57-
if (includeOutput &&
58-
configuration.output != null &&
59-
configuration.output != outputNone) {
60-
arguments.addAll(['--$outputFlag', configuration.output]);
61-
}
62-
6355
if (configuration.verbose) {
6456
arguments.add('--$verboseFlag');
6557
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import 'dart:io';
88
import 'package:build_daemon/client.dart';
99
import 'package:build_daemon/constants.dart';
1010
import 'package:build_daemon/data/server_log.dart';
11-
import 'package:webdev/src/util.dart';
11+
12+
import 'util.dart';
1213

1314
/// Connects to the `build_runner` daemon.
1415
Future<BuildDaemonClient> connectClient(String workingDirectory,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:io';
66

77
import 'package:io/ansi.dart';
8+
import 'package:build_daemon/data/server_log.dart';
89
import 'package:logging/logging.dart';
910

1011
var _verbose = false;
@@ -46,3 +47,24 @@ void _colorLog(Level level, String message, {bool verbose}) {
4647
stdout.writeln('');
4748
}
4849
}
50+
51+
/// Trims [level] from [message] if it is prefixed by it.
52+
String trimLevel(Level level, String message) => message.startsWith('[$level]')
53+
? message.replaceFirst('[$level]', '').trimLeft()
54+
: message;
55+
56+
/// Detects if the [ServerLog] contains a [Level] and returns the
57+
/// resulting value.
58+
///
59+
/// If the [ServerLog] does not contain a [Level], null will be returned.
60+
Level levelForLog(ServerLog serverLog) {
61+
var log = serverLog.log;
62+
Level recordLevel;
63+
for (var level in Level.LEVELS) {
64+
if (log.startsWith('[$level]')) {
65+
recordLevel = level;
66+
break;
67+
}
68+
}
69+
return recordLevel;
70+
}

webdev/lib/src/serve/dev_workflow.dart

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,17 @@ import 'package:build_daemon/data/build_target.dart';
1010
import 'package:logging/logging.dart';
1111

1212
import '../command/configuration.dart';
13-
import '../serve/chrome.dart';
14-
import '../serve/daemon_client.dart';
15-
import '../serve/debugger/devtools.dart';
16-
import '../serve/logging.dart';
17-
import '../serve/server_manager.dart';
18-
import '../serve/utils.dart';
19-
import '../serve/webdev_server.dart';
13+
import '../daemon_client.dart';
14+
import '../logging.dart';
15+
import 'chrome.dart';
16+
import 'debugger/devtools.dart';
17+
import 'server_manager.dart';
18+
import 'webdev_server.dart';
2019

2120
Future<BuildDaemonClient> _startBuildDaemon(
22-
String workingDirectory,
23-
List<String> buildOptions,
24-
) async {
25-
logHandler(Level.INFO, 'Connecting to the build daemon...');
21+
String workingDirectory, List<String> buildOptions) async {
2622
try {
23+
logHandler(Level.INFO, 'Connecting to the build daemon...');
2724
return await connectClient(
2825
workingDirectory,
2926
buildOptions,

0 commit comments

Comments
 (0)