diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md index 60a935106..b9da6a1e5 100644 --- a/webdev/CHANGELOG.md +++ b/webdev/CHANGELOG.md @@ -1,7 +1,9 @@ ## 0.1.4 - Require and use features from `build_runner` 0.8.2. -- Added a `--[no]-release`. + - Added a `--[no]-release` flag. +- Require `build_web_compliers` 0.3.6 when running `serve`. + - Unless the `--no-require-build-web-compilers` flag is provided. ## 0.1.3+1 diff --git a/webdev/README.md b/webdev/README.md index 9cd0d6d57..0a4363f64 100644 --- a/webdev/README.md +++ b/webdev/README.md @@ -24,14 +24,16 @@ $ webdev help serve Run a local web development server and a file system watcher that re-builds on changes. Usage: webdev serve [arguments] [[:]]... --h, --help Print this usage information. --r, --[no-]release Build with release mode defaults for builders. --o, --output A directory to write the result of a build to. Or a mapping from a top-level directory in the package to the directory to write a filtered build output to. For example "web:deploy". --v, --verbose Enables verbose logging. - --hostname Specify the hostname to serve on - (defaults to "localhost") - - --log-requests Enables logging for each request to the server. +-h, --help Print this usage information. +-r, --[no-]release Build with release mode defaults for builders. +-o, --output A directory to write the result of a build to. Or a mapping from a top-level directory in the package to the directory to write a filtered build output to. For example "web:deploy". +-v, --verbose Enables verbose logging. + --hostname Specify the hostname to serve on + (defaults to "localhost") + + --log-requests Enables logging for each request to the server. + --[no-]require-build-web-compilers If a dependency on `build_web_compilers` is required to run. + (defaults to on) Run "webdev help" to see global options. ``` diff --git a/webdev/bin/webdev.dart b/webdev/bin/webdev.dart index 55fd2fa33..75c15d9f5 100644 --- a/webdev/bin/webdev.dart +++ b/webdev/bin/webdev.dart @@ -10,7 +10,6 @@ import 'package:args/command_runner.dart'; import 'package:io/ansi.dart'; import 'package:io/io.dart'; import 'package:webdev/src/webdev_command_runner.dart'; -import 'package:webdev/src/util.dart'; Future main(List args) async { try { diff --git a/webdev/lib/src/command/build_command.dart b/webdev/lib/src/command/build_command.dart index 1d1c64fdd..4d41b14e6 100644 --- a/webdev/lib/src/command/build_command.dart +++ b/webdev/lib/src/command/build_command.dart @@ -16,5 +16,5 @@ class BuildCommand extends CommandBase { final description = 'Run builders to build a package.'; @override - Future run() => runCore('build'); + Future run() => runCore('build', requireBuildWebCompilers: false); } diff --git a/webdev/lib/src/command/command_base.dart b/webdev/lib/src/command/command_base.dart index f5fd1a7ba..c4aee9408 100644 --- a/webdev/lib/src/command/command_base.dart +++ b/webdev/lib/src/command/command_base.dart @@ -62,8 +62,9 @@ abstract class CommandBase extends Command { return arguments; } - Future runCore(String command) async { - await checkPubspecLock(); + Future runCore(String command, + {@required bool requireBuildWebCompilers}) async { + await checkPubspecLock(requireBuildWebCompilers: requireBuildWebCompilers); var buildRunnerScript = await _buildRunnerScript(); diff --git a/webdev/lib/src/command/serve_command.dart b/webdev/lib/src/command/serve_command.dart index 116742f39..74395dee6 100644 --- a/webdev/lib/src/command/serve_command.dart +++ b/webdev/lib/src/command/serve_command.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'command_base.dart'; +const _requireBuildWebCompilers = 'require-build-web-compilers'; + /// Command to execute pub run build_runner serve. class ServeCommand extends CommandBase { @override @@ -27,7 +29,11 @@ class ServeCommand extends CommandBase { ..addFlag('log-requests', defaultsTo: false, negatable: false, - help: 'Enables logging for each request to the server.'); + help: 'Enables logging for each request to the server.') + ..addFlag(_requireBuildWebCompilers, + defaultsTo: true, + negatable: true, + help: 'If a dependency on `build_web_compilers` is required to run.'); } @override @@ -47,5 +53,6 @@ class ServeCommand extends CommandBase { } @override - Future run() => runCore('serve'); + Future run() => runCore('serve', + requireBuildWebCompilers: argResults[_requireBuildWebCompilers] as bool); } diff --git a/webdev/lib/src/pubspec.dart b/webdev/lib/src/pubspec.dart index c866191f6..03bedbb41 100644 --- a/webdev/lib/src/pubspec.dart +++ b/webdev/lib/src/pubspec.dart @@ -5,13 +5,12 @@ import 'dart:async'; import 'dart:io'; +import 'package:meta/meta.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; import 'util.dart'; -final _supportedBuildRunnerVersion = new VersionConstraint.parse('^0.8.2'); - class PackageException implements Exception { final List details; @@ -29,12 +28,14 @@ class PackageExceptionDetails { description: 'Run `$appName` in a Dart package directory. Run `pub get` first.'); - static final noBuildRunnerDep = new PackageExceptionDetails._( - 'You must have a dependency on `build_runner` in `pubspec.yaml`.', - description: ''' + static PackageExceptionDetails missingDep( + String pkgName, VersionConstraint constraint) => + new PackageExceptionDetails._( + 'You must have a dependency on `$pkgName` in `pubspec.yaml`.', + description: ''' # pubspec.yaml dev_dependencies: - build_runner: $_supportedBuildRunnerVersion'''); + $pkgName: $constraint'''); @override String toString() => [error, description].join('\n'); @@ -57,7 +58,7 @@ Future _runPubDeps() async { } } -Future checkPubspecLock() async { +Future checkPubspecLock({@required bool requireBuildWebCompilers}) async { await _runPubDeps(); var pubspecLock = @@ -67,34 +68,45 @@ Future checkPubspecLock() async { var issues = []; - 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); - } + void checkPackage(String pkgName, VersionConstraint constraint) { + var missingDetails = + PackageExceptionDetails.missingDep(pkgName, constraint); - var source = buildRunner['source'] as String; - if (source == 'hosted') { - // NOTE: buildRunner['description'] should be: - // `{url: https://pub.dartlang.org, name: build_runner}` - // If a user is playing around here, they are on their own. - - var version = buildRunner['version'] as String; - var buildRunnerVersion = new Version.parse(version); - if (!_supportedBuildRunnerVersion.allows(buildRunnerVersion)) { - var error = 'The `build_runner` version – $buildRunnerVersion – is not ' - 'within the allowed constraint – $_supportedBuildRunnerVersion.'; - issues.add(new PackageExceptionDetails._(error)); - } + var pkgDataMap = (packages == null) ? null : packages[pkgName] as YamlMap; + if (pkgDataMap == null) { + issues.add(missingDetails); } else { - // NOTE: Intentionally not checking non-hosted dependencies: git, path - // If a user is playing around here, they are on their own. + var dependency = pkgDataMap['dependency'] as String; + if (!dependency.startsWith('direct ')) { + issues.add(missingDetails); + } + + var source = pkgDataMap['source'] as String; + if (source == 'hosted') { + // NOTE: pkgDataMap['description'] should be: + // `{url: https://pub.dartlang.org, name: [pkgName]}` + // If a user is playing around here, they are on their own. + + var version = pkgDataMap['version'] as String; + var pkgVersion = new Version.parse(version); + if (!constraint.allows(pkgVersion)) { + var error = 'The `$pkgName` version – $pkgVersion – is not ' + 'within the allowed constraint – $constraint.'; + issues.add(new PackageExceptionDetails._(error)); + } + } else { + // NOTE: Intentionally not checking non-hosted dependencies: git, path + // If a user is playing around here, they are on their own. + } } } + checkPackage('build_runner', new VersionConstraint.parse('^0.8.2')); + + if (requireBuildWebCompilers) { + checkPackage('build_web_compilers', new VersionConstraint.parse('^0.3.6')); + } + if (issues.isNotEmpty) { throw new PackageException._(issues); } diff --git a/webdev/lib/src/webdev_command_runner.dart b/webdev/lib/src/webdev_command_runner.dart index 5dea49ba4..027010cba 100644 --- a/webdev/lib/src/webdev_command_runner.dart +++ b/webdev/lib/src/webdev_command_runner.dart @@ -11,6 +11,7 @@ import 'command/serve_command.dart'; import 'util.dart'; export 'pubspec.dart' show PackageException; +export 'util.dart' show appName; Future run(List args) async { var runner = diff --git a/webdev/test/integration_test.dart b/webdev/test/integration_test.dart index 5e1e2898a..74abee1f8 100644 --- a/webdev/test/integration_test.dart +++ b/webdev/test/integration_test.dart @@ -23,49 +23,158 @@ void main() { await process.shouldExit(64); }); - test('should fail in a package without a build_runner dependency', () async { - // Running on the `webdev` package directory – which has no dependency on - // build runner. - var process = await runWebDev(['build']); - var output = (await process.stdoutStream().join('\n')).trim(); - - expect(output, contains(r'''webdev could not run for this project. -You must have a dependency on `build_runner` in `pubspec.yaml`.''')); - await process.shouldExit(78); - }); + var invalidRanges = >{ + 'build_runner': ['0.7.13+1', '0.9.0'], + 'build_web_compilers': ['0.3.5', '0.4.0'] + }; + + group('missing dependency on', () { + test('`build_runner` should fail', () async { + await d.file('pubspec.yaml', ''' +name: sample +''').create(); + + await d.file('pubspec.lock', _pubspecLock(runnerVersion: null)).create(); + + await d.file('.packages', ''' +''').create(); + + var process = await runWebDev(['build'], workingDirectory: d.sandbox); - 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.yaml', ''' + await _checkProcessStdout(process, [ + r'''webdev could not run for this project. +You must have a dependency on `build_runner` in `pubspec.yaml`.''' + ]); + await process.shouldExit(78); + }); + + test('`build_web_compilers` should fail', () async { + await d.file('pubspec.yaml', ''' name: sample ''').create(); - await d.file('pubspec.lock', _pubspecLock(version: version)).create(); + await d + .file('pubspec.lock', _pubspecLock(webCompilersVersion: null)) + .create(); - await d.file('.packages', ''' + await d.file('.packages', ''' ''').create(); - var process = await runWebDev(['build'], workingDirectory: d.sandbox); + var process = await runWebDev(['serve'], workingDirectory: d.sandbox); - await expectLater( - process.stdout, emits('webdev could not run for this project.')); - await expectLater( - process.stdout, - emits('The `build_runner` version – $version – ' - 'is not within the allowed constraint – ^0.8.2.')); - await process.shouldExit(78); - }); - } + await _checkProcessStdout(process, [ + r'''webdev could not run for this project. +You must have a dependency on `build_web_compilers` in `pubspec.yaml`.''' + ]); + await process.shouldExit(78); + }); + + test( + '`build_web_compilers` should be ignored with ' + '--no-require-build-web-compilers', () async { + await d.file('pubspec.yaml', ''' +name: sample +''').create(); + + await d + .file('pubspec.lock', _pubspecLock(webCompilersVersion: null)) + .create(); + + await d.file('.packages', ''' +''').create(); + + var process = await runWebDev( + ['serve', '--no-require-build-web-compilers'], + workingDirectory: d.sandbox); + + // Fails w/ an isolate exception instead - since this is a fake package + await _checkProcessStdout(process, [ + 'webdev failed with an unexpected exception.', + // The isolate will fail - broken .packages file + 'Unable to spawn isolate' + ]); + await process.shouldExit(70); + }); + + test('`build_web_compilers` should be ignored when running `build` command', + () async { + await d.file('pubspec.yaml', ''' +name: sample +''').create(); + + await d + .file('pubspec.lock', _pubspecLock(webCompilersVersion: null)) + .create(); + + await d.file('.packages', ''' +''').create(); + + var process = await runWebDev(['build'], workingDirectory: d.sandbox); + + // Fails w/ an isolate exception instead - since this is a fake package + await _checkProcessStdout(process, [ + 'webdev failed with an unexpected exception.', + // The isolate will fail - broken .packages file + 'Unable to spawn isolate' + ]); + await process.shouldExit(70); + }); }); + for (var entry in invalidRanges.entries) { + group('package `${entry.key}` should fail with the wrong version', () { + for (var version in entry.value) { + test(version, () async { + var buildRunnerVersion = _supportedBuildRunnerVersion; + var webCompilersVersion = _supportedWebCompilersVersion; + + String supportedRange; + if (entry.key == 'build_runner') { + buildRunnerVersion = version; + supportedRange = '^0.8.2'; + } else { + assert(entry.key == 'build_web_compilers'); + webCompilersVersion = version; + supportedRange = '^0.3.6'; + } + + await d.file('pubspec.yaml', ''' +name: sample +''').create(); + + await d + .file( + 'pubspec.lock', + _pubspecLock( + runnerVersion: buildRunnerVersion, + webCompilersVersion: webCompilersVersion)) + .create(); + + await d.file('.packages', ''' +''').create(); + + var process = await runWebDev(['serve'], workingDirectory: d.sandbox); + + await _checkProcessStdout(process, [ + 'webdev could not run for this project.', + // See https://github.com/dart-lang/linter/issues/965 + // ignore: prefer_adjacent_string_concatenation + 'The `${entry.key}` version – $version – ' + + 'is not within the allowed constraint – $supportedRange.' + ]); + await process.shouldExit(78); + }); + } + }); + } + test('no pubspec.yaml', () async { var process = await runWebDev(['build'], workingDirectory: d.sandbox); - var output = await process.stdoutStream().join('\n'); - - expect(output, contains('webdev could not run for this project.')); - expect(output, contains('Could not find a file named "pubspec.yaml"')); + await _checkProcessStdout(process, [ + 'webdev could not run for this project.', + 'Could not find a file named "pubspec.yaml"' + ]); await process.shouldExit(78); }); @@ -76,11 +185,10 @@ name: sample var process = await runWebDev(['build'], workingDirectory: d.sandbox); - var output = await process.stdoutStream().join('\n'); - - expect(output, contains('webdev could not run for this project.')); - expect(output, - contains('No pubspec.lock file found, please run "pub get" first.')); + await _checkProcessStdout(process, [ + 'webdev could not run for this project.', + 'No pubspec.lock file found, please run "pub get" first.' + ]); await process.shouldExit(78); }); @@ -93,11 +201,10 @@ name: sample var process = await runWebDev(['build'], workingDirectory: d.sandbox); - var output = await process.stdoutStream().join('\n'); - - expect(output, contains('webdev could not run for this project.')); - expect(output, - contains('No .packages file found, please run "pub get" first.')); + await _checkProcessStdout(process, [ + 'webdev could not run for this project.', + 'No .packages file found, please run "pub get" first.' + ]); await process.shouldExit(78); }); @@ -112,12 +219,11 @@ name: sample var process = await runWebDev(['build'], workingDirectory: d.sandbox); - var output = await process.stdoutStream().join('\n'); - - expect(output, contains('webdev failed with an unexpected exception.')); - - // The isolate will fail - broken .packages file - expect(output, contains('Unable to spawn isolate')); + await _checkProcessStdout(process, [ + 'webdev failed with an unexpected exception.', + // The isolate will fail - broken .packages file + 'Unable to spawn isolate' + ]); await process.shouldExit(70); }); @@ -127,7 +233,7 @@ name: sample await d.file('.packages', '').create(); // Ensure there is a noticeable delta in the creation times - await new Future.delayed(const Duration(milliseconds: 500)); + await new Future.delayed(const Duration(milliseconds: 1100)); await d.file('pubspec.yaml', ''' name: sample @@ -137,14 +243,13 @@ dependencies: var process = await runWebDev(['build'], workingDirectory: d.sandbox); - var output = await process.stdoutStream().join('\n'); - - expect(output, contains('webdev could not run for this project.')); - expect( - output, - contains( - 'The pubspec.yaml file has changed since the pubspec.lock file ' - 'was generated, please run "pub get" again.')); + await _checkProcessStdout(process, [ + 'webdev could not run for this project.', + // See https://github.com/dart-lang/linter/issues/965 + // ignore: prefer_adjacent_string_concatenation + 'The pubspec.yaml file has changed since the pubspec.lock file ' + + 'was generated, please run "pub get" again.' + ]); await process.shouldExit(78); }); @@ -168,16 +273,13 @@ dependencies: process = await runWebDev(args, workingDirectory: exampleDirectory); - var output = await process.stdoutStream().join('\n'); - - expect(output, contains('[INFO] Succeeded')); - - if (!withDDC && !output.contains('with 0 outputs')) { - // If outputs were generated, then dart2js should have been run - expect(output, contains('Running dart2js with'), - reason: 'Should run dart2js during build.'); + var expectedItems = ['[INFO] Succeeded']; + if (!withDDC) { + expectedItems.add(anyOf( + contains('with 0 outputs'), contains('Running dart2js with'))); } + await _checkProcessStdout(process, expectedItems); await process.shouldExit(0); await d.file('main.dart.js', isNotEmpty).validate(); @@ -194,17 +296,53 @@ dependencies: }); } -String _pubspecLock({String version: '0.8.2'}) => ''' +Future _checkProcessStdout(TestProcess process, List items) async { + var output = await process.stdoutStream().join('\n'); + for (var item in items) { + if (item is! Matcher) { + item = contains(item); + } + expect(output, item); + } +} + +const _supportedBuildRunnerVersion = '0.8.2'; +const _supportedWebCompilersVersion = '0.3.6'; + +String _pubspecLock( + {String runnerVersion: _supportedBuildRunnerVersion, + String webCompilersVersion: _supportedWebCompilersVersion}) { + var buffer = new StringBuffer(''' # Copy-pasted from a valid run packages: +'''); + + if (runnerVersion != null) { + buffer.writeln(''' build_runner: - dependency: "direct main" + dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "$version" -'''; + version: "$runnerVersion" +'''); + } + + if (webCompilersVersion != null) { + buffer.writeln(''' + build_web_compilers: + dependency: "direct dev" + description: + name: build_web_compilers + url: "https://pub.dartlang.org" + source: hosted + version: "$webCompilersVersion" +'''); + } + + return buffer.toString(); +} /// Returns an environment map that includes `PUB_ENVIRONMENT`. /// diff --git a/webdev/test/readme_test.dart b/webdev/test/readme_test.dart index a2ca9e13e..3f3e3b8ba 100644 --- a/webdev/test/readme_test.dart +++ b/webdev/test/readme_test.dart @@ -24,6 +24,5 @@ Future _readmeCheck(List args) async { var command = (['webdev']..addAll(args)).join(' '); var expected = '```console\n\$ $command\n$output\n```'; - printOnFailure(expected); expect(readme.readAsStringSync(), contains(expected)); }