Skip to content

Initial validation of build_runner dependency and version #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion webdev/bin/webdev.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,21 @@ import 'package:webdev/src/webdev_command_runner.dart';

Future main(List<String> args) async {
try {
await webdevCommandRunner().run(args);
exitCode = await run(args);
} on UsageException catch (e) {
print(yellow.wrap(e.message));
print(' ');
print(e.usage);
exitCode = ExitCode.usage.code;
} on PackageException catch (e) {
print(yellow.wrap('Could not run in the current directory.'));
for (var detail in e.details) {
print(detail.error);
if (detail.description != null) {
print(' ${detail.description}');
}
}

exitCode = ExitCode.config.code;
}
}
2 changes: 1 addition & 1 deletion webdev/lib/src/command/build_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ class BuildCommand extends BuildRunnerCommandBase {
final description = 'Run builders to build a package.';

@override
Future run() => runCore('build');
Future<int> run() => runCore('build');
}
14 changes: 11 additions & 3 deletions webdev/lib/src/command/build_runner_command_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:io';
import 'dart:io' hide exitCode, exit;
import 'dart:isolate';

import 'package:args/command_runner.dart';
import 'package:stack_trace/stack_trace.dart';

import '../pubspec.dart';

/// Extend to get a command with the arguments common to all build_runner
/// commands.
abstract class BuildRunnerCommandBase extends Command {
abstract class BuildRunnerCommandBase extends Command<int> {
BuildRunnerCommandBase() {
// TODO(nshahan) Expose more common args passed to build_runner commands.
// build_runner might expose args for use in wrapping scripts like this one.
Expand All @@ -25,9 +27,13 @@ abstract class BuildRunnerCommandBase extends Command {
help: 'Enables verbose logging.');
}

Future runCore(String command) async {
Future<int> runCore(String command) async {
await checkPubspecLock();

final arguments = [command]..addAll(argResults.arguments);

var exitCode = 0;

// Heavily inspired by dart-lang/build @ 0c77443dd7
// /build_runner/bin/build_runner.dart#L58-L85
var exitPort = new ReceivePort();
Expand Down Expand Up @@ -62,6 +68,8 @@ abstract class BuildRunnerCommandBase extends Command {
await exitPort.first;
await errorListener.cancel();
await exitCodeListener?.cancel();

return exitCode;
}
}

Expand Down
2 changes: 1 addition & 1 deletion webdev/lib/src/command/serve_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ class ServeCommand extends BuildRunnerCommandBase {
}

@override
Future run() => runCore('serve');
Future<int> run() => runCore('serve');
}
90 changes: 90 additions & 0 deletions webdev/lib/src/pubspec.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. 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 'dart:io';

import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';

import 'util.dart';

class PackageException implements Exception {
final List<PackageExceptionDetails> details;

PackageException._(this.details);
}

class PackageExceptionDetails {
final String error;
final String description;

const PackageExceptionDetails._(this.error, {this.description});

static const noPubspecLock = const PackageExceptionDetails._(
'`pubspec.lock` does not exist.',
description:
'Run `webdev` in a Dart package directory. Run `pub get` first.');

static const noBuildRunnerDep = const PackageExceptionDetails._(
'A dependency on `build_runner` was not found.',
description:
'You must have a dependency on `build_runner` in `pubspec.yaml`. '
'It can be in either `dependencies` or `dev_dependencies`.');

@override
String toString() => [error, description].join('\n');
}

Future checkPubspecLock() async {
var file = new File('pubspec.lock');
if (!file.existsSync()) {
throw new PackageException._([PackageExceptionDetails.noPubspecLock]);
}

var pubspecLock = loadYaml(await file.readAsString()) as YamlMap;

var packages = pubspecLock['packages'] as YamlMap;

var issues = <PackageExceptionDetails>[];

var buildRunner = packages['build_runner'] as YamlMap;
if (buildRunner == null) {
issues.add(PackageExceptionDetails.noBuildRunnerDep);
} else {
var dependency = buildRunner['dependency'] as String;
if (!dependency.startsWith('direct ')) {
issues.add(PackageExceptionDetails.noBuildRunnerDep);
}

var version = buildRunner['version'] as String;
if (version == null) {
// TODO: warning?
} else {
var buildRunnerVersion = new Version.parse(version);

if (!supportedBuildRunnerVersionRange.allows(buildRunnerVersion)) {
var error = 'The `build_runner` version – $buildRunnerVersion – is not '
'within the supported range – $supportedBuildRunnerVersionRange.';
issues.add(new PackageExceptionDetails._(error));
}
}

var source = buildRunner['source'] as String;
if (source == 'hosted') {
//var description = buildRunner['description'] as YamlMap;
// TODO: check for `{url: https://pub.dartlang.org, name: build_runner}`
// If not, print a warning
} else {
// TODO: print a warning that we're assuming hosted
}
}

// TODO: validate build_web_compilers
//var buldWebCompilers = packages['build_web_compilers'];

if (issues.isNotEmpty) {
throw new PackageException._(issues);
}
}
11 changes: 11 additions & 0 deletions webdev/lib/src/util.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:pub_semver/pub_semver.dart';

final supportedBuildRunnerVersionRange = new VersionRange(
min: new Version(0, 8, 0),
includeMin: true,
max: new Version(0, 9, 0),
includeMax: false);
18 changes: 13 additions & 5 deletions webdev/lib/src/webdev_command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@
// for details. 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 'command/build_command.dart';
import 'command/serve_command.dart';

/// All available top level commands.
CommandRunner webdevCommandRunner() =>
new CommandRunner('webdev', 'A tool to develop Dart web projects.')
..addCommand(new BuildCommand())
..addCommand(new ServeCommand());
export 'pubspec.dart' show PackageException;

Future<int> run(List<String> args) async {
var runner =
new CommandRunner<int>('webdev', 'A tool to develop Dart web projects.')
..addCommand(new BuildCommand())
..addCommand(new ServeCommand());

// In the case of `help`, `null` is returned. Treat that as success.
return await runner.run(args) ?? 0;
}
4 changes: 4 additions & 0 deletions webdev/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ environment:
dependencies:
args: ^1.2.0
io: ^0.3.2+1
pub_semver: ^1.3.2
stack_trace: ^1.9.2
yaml: ^2.1.13

dev_dependencies:
path: ^1.5.1
test: ^0.12.0
test_descriptor: ^1.0.3
test_process: ^1.0.1

executables:
Expand Down
46 changes: 41 additions & 5 deletions webdev/test/integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

import 'dart:io';

import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'package:test_process/test_process.dart';

final _webdevBin = p.absolute('bin/webdev.dart');

void main() {
test('README contains help output', () async {
var process = await TestProcess.start('dart', ['bin/webdev.dart']);
var process = await TestProcess.start('dart', [_webdevBin]);

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

Expand All @@ -22,8 +26,7 @@ void main() {
});

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

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

Expand All @@ -33,7 +36,40 @@ void main() {
});

test('should fail in a package without a build_runner dependency', () async {
var process = await TestProcess.start('dart', ['bin/webdev.dart', 'serve']);
await process.shouldExit(255);
var process = await TestProcess.start('dart', [_webdevBin, 'serve']);
var output = (await process.stdoutStream().join('\n')).trim();

expect(output, contains(r'''Could not run in the current directory.
A dependency on `build_runner` was not found.'''));
await process.shouldExit(78);
});

group('should fail when `build_runner` is the wrong version', () {
for (var version in ['0.7.13+1', '0.9.0']) {
test(version, () async {
await d.file('pubspec.lock', '''
# Copy-pasted from a valid run
packages:
build_runner:
dependency: "direct main"
description:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "$version"
''').create();

var process = await TestProcess.start('dart', [_webdevBin, 'build'],
workingDirectory: d.sandbox);
var output = (await process.stdoutStream().join('\n')).trim();

expect(output, contains('Could not run in the current directory.'));
expect(
output,
contains('The `build_runner` version – $version – '
'is not within the supported range – >=0.8.0 <0.9.0.'));
await process.shouldExit(78);
});
}
});
}