Skip to content

Commit 0a157cf

Browse files
committed
Initial validation of build_runner dependency and version
Also change the commands to return the exitCode from build_runner
1 parent 8e92687 commit 0a157cf

File tree

9 files changed

+183
-16
lines changed

9 files changed

+183
-16
lines changed

webdev/bin/webdev.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,21 @@ import 'package:webdev/src/webdev_command_runner.dart';
1212

1313
Future main(List<String> args) async {
1414
try {
15-
await webdevCommandRunner().run(args);
15+
exitCode = await run(args);
1616
} on UsageException catch (e) {
1717
print(yellow.wrap(e.message));
1818
print(' ');
1919
print(e.usage);
2020
exitCode = ExitCode.usage.code;
21+
} on PackageException catch (e) {
22+
print(yellow.wrap('Could not run in the current directory.'));
23+
for (var detail in e.details) {
24+
print(detail.error);
25+
if (detail.description != null) {
26+
print(' ${detail.description}');
27+
}
28+
}
29+
30+
exitCode = ExitCode.config.code;
2131
}
2232
}

webdev/lib/src/command/build_command.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ class BuildCommand extends BuildRunnerCommandBase {
1414
final description = 'Run builders to build a package.';
1515

1616
@override
17-
Future run() => runCore('build');
17+
Future<int> run() => runCore('build');
1818
}

webdev/lib/src/command/build_runner_command_base.dart

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

55
import 'dart:async';
6-
import 'dart:io';
6+
import 'dart:io' hide exitCode, exit;
77
import 'dart:isolate';
88

99
import 'package:args/command_runner.dart';
1010
import 'package:stack_trace/stack_trace.dart';
1111

12+
import '../pubspec.dart';
13+
1214
/// Extend to get a command with the arguments common to all build_runner
1315
/// commands.
14-
abstract class BuildRunnerCommandBase extends Command {
16+
abstract class BuildRunnerCommandBase extends Command<int> {
1517
BuildRunnerCommandBase() {
1618
// TODO(nshahan) Expose more common args passed to build_runner commands.
1719
// build_runner might expose args for use in wrapping scripts like this one.
@@ -25,9 +27,13 @@ abstract class BuildRunnerCommandBase extends Command {
2527
help: 'Enables verbose logging.');
2628
}
2729

28-
Future runCore(String command) async {
30+
Future<int> runCore(String command) async {
31+
await checkPubspecLock();
32+
2933
final arguments = [command]..addAll(argResults.arguments);
3034

35+
var exitCode = 0;
36+
3137
// Heavily inspired by dart-lang/build @ 0c77443dd7
3238
// /build_runner/bin/build_runner.dart#L58-L85
3339
var exitPort = new ReceivePort();
@@ -62,6 +68,8 @@ abstract class BuildRunnerCommandBase extends Command {
6268
await exitPort.first;
6369
await errorListener.cancel();
6470
await exitCodeListener?.cancel();
71+
72+
return exitCode;
6573
}
6674
}
6775

webdev/lib/src/command/serve_command.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ class ServeCommand extends BuildRunnerCommandBase {
3131
}
3232

3333
@override
34-
Future run() => runCore('serve');
34+
Future<int> run() => runCore('serve');
3535
}

webdev/lib/src/pubspec.dart

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (c) 2018, the Dart 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 file.
4+
5+
import 'dart:async';
6+
import 'dart:io';
7+
8+
import 'package:pub_semver/pub_semver.dart';
9+
import 'package:yaml/yaml.dart';
10+
11+
import 'util.dart';
12+
13+
class PackageException implements Exception {
14+
final List<PackageExceptionDetails> details;
15+
16+
PackageException._(this.details);
17+
}
18+
19+
class PackageExceptionDetails {
20+
final String error;
21+
final String description;
22+
23+
const PackageExceptionDetails._(this.error, {this.description});
24+
25+
static const noPubspecLock = const PackageExceptionDetails._(
26+
'`pubspec.lock` does not exist.',
27+
description:
28+
'Run `webdev` in a Dart package directory. Run `pub get` first.');
29+
30+
static const noBuildRunnerDep = const PackageExceptionDetails._(
31+
'A dependency on `build_runner` was not found.',
32+
description:
33+
'You must have a dependency on `build_runner` in `pubspec.yaml`. '
34+
'It can be in either `dependencies` or `dev_dependencies`.');
35+
36+
@override
37+
String toString() => [error, description].join('\n');
38+
}
39+
40+
Future checkPubspecLock() async {
41+
var file = new File('pubspec.lock');
42+
if (!file.existsSync()) {
43+
throw new PackageException._([PackageExceptionDetails.noPubspecLock]);
44+
}
45+
46+
var pubspecLock = loadYaml(await file.readAsString()) as YamlMap;
47+
48+
var packages = pubspecLock['packages'] as YamlMap;
49+
50+
var issues = <PackageExceptionDetails>[];
51+
52+
var buildRunner = packages['build_runner'] as YamlMap;
53+
if (buildRunner == null) {
54+
issues.add(PackageExceptionDetails.noBuildRunnerDep);
55+
} else {
56+
var dependency = buildRunner['dependency'] as String;
57+
if (!dependency.startsWith('direct ')) {
58+
issues.add(PackageExceptionDetails.noBuildRunnerDep);
59+
}
60+
61+
var version = buildRunner['version'] as String;
62+
if (version == null) {
63+
// TODO: warning?
64+
} else {
65+
var buildRunnerVersion = new Version.parse(version);
66+
67+
if (!supportedBuildRunnerVersionRange.allows(buildRunnerVersion)) {
68+
var error = 'The `build_runner` version – $buildRunnerVersion – is not '
69+
'within the supported range – $supportedBuildRunnerVersionRange.';
70+
issues.add(new PackageExceptionDetails._(error));
71+
}
72+
}
73+
74+
var source = buildRunner['source'] as String;
75+
if (source == 'hosted') {
76+
//var description = buildRunner['description'] as YamlMap;
77+
// TODO: check for `{url: https://pub.dartlang.org, name: build_runner}`
78+
// If not, print a warning
79+
} else {
80+
// TODO: print a warning that we're assuming hosted
81+
}
82+
}
83+
84+
// TODO: validate build_web_compilers
85+
//var buldWebCompilers = packages['build_web_compilers'];
86+
87+
if (issues.isNotEmpty) {
88+
throw new PackageException._(issues);
89+
}
90+
}

webdev/lib/src/util.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) 2018, the Dart 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 file.
4+
5+
import 'package:pub_semver/pub_semver.dart';
6+
7+
final supportedBuildRunnerVersionRange = new VersionRange(
8+
min: new Version(0, 8, 0),
9+
includeMin: true,
10+
max: new Version(0, 9, 0),
11+
includeMax: false);

webdev/lib/src/webdev_command_runner.dart

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:async';
6+
57
import 'package:args/command_runner.dart';
68

79
import 'command/build_command.dart';
810
import 'command/serve_command.dart';
911

10-
/// All available top level commands.
11-
CommandRunner webdevCommandRunner() =>
12-
new CommandRunner('webdev', 'A tool to develop Dart web projects.')
13-
..addCommand(new BuildCommand())
14-
..addCommand(new ServeCommand());
12+
export 'pubspec.dart' show PackageException;
13+
14+
Future<int> run(List<String> args) async {
15+
var runner =
16+
new CommandRunner<int>('webdev', 'A tool to develop Dart web projects.')
17+
..addCommand(new BuildCommand())
18+
..addCommand(new ServeCommand());
19+
20+
// In the case of `help`, `null` is returned. Treat that as success.
21+
return await runner.run(args) ?? 0;
22+
}

webdev/pubspec.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ environment:
1010
dependencies:
1111
args: ^1.2.0
1212
io: ^0.3.2+1
13+
pub_semver: ^1.3.2
1314
stack_trace: ^1.9.2
15+
yaml: ^2.1.13
1416

1517
dev_dependencies:
18+
path: ^1.5.1
1619
test: ^0.12.0
20+
test_descriptor: ^1.0.3
1721
test_process: ^1.0.1
1822

1923
executables:

webdev/test/integration_test.dart

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44

55
import 'dart:io';
66

7+
import 'package:path/path.dart' as p;
78
import 'package:test/test.dart';
9+
import 'package:test_descriptor/test_descriptor.dart' as d;
810
import 'package:test_process/test_process.dart';
911

12+
final _webdevBin = p.absolute('bin/webdev.dart');
13+
1014
void main() {
1115
test('README contains help output', () async {
12-
var process = await TestProcess.start('dart', ['bin/webdev.dart']);
16+
var process = await TestProcess.start('dart', [_webdevBin]);
1317

1418
var output = (await process.stdoutStream().join('\n')).trim();
1519

@@ -22,8 +26,7 @@ void main() {
2226
});
2327

2428
test('non-existant commands create errors', () async {
25-
var process =
26-
await TestProcess.start('dart', ['bin/webdev.dart', 'monkey']);
29+
var process = await TestProcess.start('dart', [_webdevBin, 'monkey']);
2730

2831
var output = (await process.stdoutStream().join('\n')).trim();
2932

@@ -33,7 +36,40 @@ void main() {
3336
});
3437

3538
test('should fail in a package without a build_runner dependency', () async {
36-
var process = await TestProcess.start('dart', ['bin/webdev.dart', 'serve']);
37-
await process.shouldExit(255);
39+
var process = await TestProcess.start('dart', [_webdevBin, 'serve']);
40+
var output = (await process.stdoutStream().join('\n')).trim();
41+
42+
expect(output, contains(r'''Could not run in the current directory.
43+
A dependency on `build_runner` was not found.'''));
44+
await process.shouldExit(78);
45+
});
46+
47+
group('should fail when `build_runner` is the wrong version', () {
48+
for (var version in ['0.7.13+1', '0.9.0']) {
49+
test(version, () async {
50+
await d.file('pubspec.lock', '''
51+
# Copy-pasted from a valid run
52+
packages:
53+
build_runner:
54+
dependency: "direct main"
55+
description:
56+
name: build_runner
57+
url: "https://pub.dartlang.org"
58+
source: hosted
59+
version: "$version"
60+
''').create();
61+
62+
var process = await TestProcess.start('dart', [_webdevBin, 'build'],
63+
workingDirectory: d.sandbox);
64+
var output = (await process.stdoutStream().join('\n')).trim();
65+
66+
expect(output, contains('Could not run in the current directory.'));
67+
expect(
68+
output,
69+
contains('The `build_runner` version – $version – '
70+
'is not within the supported range – >=0.8.0 <0.9.0.'));
71+
await process.shouldExit(78);
72+
});
73+
}
3874
});
3975
}

0 commit comments

Comments
 (0)