Add a "pub global run" command.

BUG=https://code.google.com/p/dart/issues/detail?id=18538
R=nweiz@google.com

Review URL: https://codereview.chromium.org//354763006

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@37963 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
rnystrom@google.com 2014-07-02 23:12:35 +00:00
parent 824f4d2ea9
commit d0d175bf69
35 changed files with 537 additions and 214 deletions

View file

@ -25,7 +25,6 @@ import 'command/uploader.dart';
import 'command/version.dart';
import 'entrypoint.dart';
import 'exceptions.dart';
import 'exit_codes.dart' as exit_codes;
import 'log.dart' as log;
import 'global_packages.dart';
import 'system_cache.dart';
@ -68,7 +67,7 @@ abstract class PubCommand {
/// [commands].
static void usageErrorWithCommands(Map<String, PubCommand> commands,
String message) {
throw new UsageException(message, _listCommands(commands));
throw new UsageException(message)..bindUsage(_listCommands(commands));
}
/// Writes [commands] in a nicely formatted list to [buffer].
@ -191,7 +190,10 @@ abstract class PubCommand {
entrypoint = new Entrypoint(path.current, cache);
}
return syncFuture(onRun);
return syncFuture(onRun).catchError((error) {
if (error is UsageException) error.bindUsage(_getUsage());
throw error;
});
}
/// Override this to perform the specific command.
@ -214,13 +216,6 @@ abstract class PubCommand {
log.message('$description\n\n${_getUsage()}');
}
// TODO(rnystrom): Use this in other places handle usage failures.
/// Throw a [UsageException] for a usage error of this command with
/// [message].
void usageError(String message) {
throw new UsageException(message, _getUsage());
}
/// Parses a user-supplied integer [intString] named [name].
///
/// If the parsing fails, prints a usage message and exits.

View file

@ -7,6 +7,7 @@ library pub.command.global;
import '../command.dart';
import 'global_activate.dart';
import 'global_deactivate.dart';
import 'global_run.dart';
/// Handles the `global` pub command.
class GlobalCommand extends PubCommand {
@ -15,6 +16,7 @@ class GlobalCommand extends PubCommand {
final subcommands = {
"activate": new GlobalActivateCommand(),
"deactivate": new GlobalDeactivateCommand()
"deactivate": new GlobalDeactivateCommand(),
"run": new GlobalRunCommand()
};
}

View file

@ -0,0 +1,49 @@
// Copyright (c) 2014, 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.
library pub.command.global_run;
import 'dart:async';
import 'dart:io';
import 'package:barback/barback.dart';
import 'package:path/path.dart' as path;
import 'package:stack_trace/stack_trace.dart';
import '../barback/asset_environment.dart';
import '../command.dart';
import '../entrypoint.dart';
import '../executable.dart';
import '../exit_codes.dart' as exit_codes;
import '../io.dart';
import '../log.dart' as log;
import '../utils.dart';
/// Handles the `global run` pub command.
class GlobalRunCommand extends PubCommand {
bool get requiresEntrypoint => false;
bool get takesArguments => true;
bool get allowTrailingOptions => false;
String get description =>
"Run an executable from a globally activated package.";
String get usage => "pub global run <package> <executable> [args...]";
Future onRun() {
if (commandOptions.rest.isEmpty) {
usageError("Must specify a package and executable to run.");
}
if (commandOptions.rest.length == 1) {
usageError("Must specify an executable to run.");
}
var package = commandOptions.rest[0];
var executable = commandOptions.rest[1];
var args = commandOptions.rest.skip(2);
return globals.find(package).then((entrypoint) {
return runExecutable(entrypoint, package, executable, args);
}).then(flushThenExit);
}
}

View file

@ -36,7 +36,7 @@ class ListPackageDirsCommand extends PubCommand {
// Include the local paths to all locked packages.
var packages = {};
var futures = [];
entrypoint.loadLockFile().packages.forEach((name, package) {
entrypoint.lockFile.packages.forEach((name, package) {
var source = entrypoint.cache.sources[package.source];
futures.add(source.getDirectory(package).then((packageDir) {
packages[name] = path.join(packageDir, "lib");

View file

@ -13,13 +13,12 @@ import 'package:stack_trace/stack_trace.dart';
import '../barback/asset_environment.dart';
import '../command.dart';
import '../executable.dart';
import '../exit_codes.dart' as exit_codes;
import '../io.dart';
import '../log.dart' as log;
import '../utils.dart';
final _arrow = getSpecial('\u2192', '=>');
/// Handles the `run` pub command.
class RunCommand extends PubCommand {
bool get takesArguments => true;
@ -32,109 +31,20 @@ class RunCommand extends PubCommand {
usageError("Must specify an executable to run.");
}
// Unless the user overrides the verbosity, we want to filter out the
// normal pub output shown while loading the environment.
if (log.verbosity == log.Verbosity.NORMAL) {
log.verbosity = log.Verbosity.WARNING;
}
var environment;
var package = entrypoint.root.name;
var rootDir;
var scriptPath;
var args;
return AssetEnvironment.create(entrypoint, BarbackMode.RELEASE,
WatcherType.NONE, useDart2JS: false)
.then((_environment) {
environment = _environment;
var executable = commandOptions.rest[0];
var args = commandOptions.rest.skip(1).toList();
// Show in-progress errors, but not results. Those get handled
// implicitly by getAllAssets().
environment.barback.errors.listen((error) {
log.error(log.red("Build error:\n$error"));
});
// A command like "foo:bar" runs the "bar" script from the "foo" package.
// If there is no colon prefix, default to the root package.
if (executable.contains(":")) {
var components = split1(executable, ":");
package = components[0];
executable = components[1];
}
var script = commandOptions.rest[0];
args = commandOptions.rest.skip(1).toList();
// A command like "foo:bar" runs the "bar" script from the "foo" package.
// If there is no colon prefix, default to the root package.
if (script.contains(":")) {
var components = split1(script, ":");
package = components[0];
script = components[1];
var dep = entrypoint.root.immediateDependencies.firstWhere(
(dep) => dep.name == package, orElse: () => null);
if (dep == null) {
if (environment.graph.packages.containsKey(package)) {
dataError('Package "$package" is not an immediate dependency.\n'
'Cannot run executables in transitive dependencies.');
} else {
dataError('Could not find package "$package". Did you forget to '
'add a dependency?');
}
}
}
// If the command has a path separator, then it's a path relative to the
// root of the package. Otherwise, it's implicitly understood to be in
// "bin".
var parts = path.split(script);
if (parts.length > 1) {
if (package != entrypoint.root.name) {
usageError("Can not run an executable in a subdirectory of a "
"dependency.");
}
scriptPath = "${path.url.joinAll(parts.skip(1))}.dart";
rootDir = parts.first;
} else {
scriptPath = "$script.dart";
rootDir = "bin";
}
if (package == entrypoint.root.name) {
// Serve the entire root-most directory containing the entrypoint. That
// ensures that, for example, things like `import '../../utils.dart';`
// will work from within some deeply nested script.
return environment.serveDirectory(rootDir);
} else {
// For other packages, always use the "bin" directory.
return environment.servePackageBinDirectory(package);
}
}).then((server) {
// Try to make sure the entrypoint script exists (or is generated) before
// we spawn the process to run it.
return environment.barback.getAssetById(
new AssetId(package, path.url.join(rootDir, scriptPath))).then((_) {
// Run in checked mode.
// TODO(rnystrom): Make this configurable.
var mode = "--checked";
args = [mode, server.url.resolve(scriptPath).toString()]..addAll(args);
return Process.start(Platform.executable, args).then((process) {
// Note: we're not using process.std___.pipe(std___) here because
// that prevents pub from also writing to the output streams.
process.stderr.listen(stderr.add);
process.stdout.listen(stdout.add);
stdin.listen(process.stdin.add);
return process.exitCode;
}).then(flushThenExit);
}).catchError((error, stackTrace) {
if (error is! AssetNotFoundException) throw error;
var message = "Could not find ${path.join(rootDir, scriptPath)}";
if (package != entrypoint.root.name) {
message += " in package $package";
}
log.error("$message.");
log.fine(new Chain.forTrace(stackTrace));
return flushThenExit(exit_codes.NO_INPUT);
});
});
return runExecutable(entrypoint, package, executable, args)
.then(flushThenExit);
}
}

View file

@ -43,17 +43,36 @@ class Entrypoint {
/// the network.
final SystemCache cache;
/// The lockfile for the entrypoint.
///
/// If not provided to the entrypoint, it will be laoded lazily from disc.
LockFile _lockFile;
/// Loads the entrypoint from a package at [rootDir].
Entrypoint(String rootDir, SystemCache cache)
: root = new Package.load(null, rootDir, cache.sources),
cache = cache;
// TODO(rnystrom): Make this path configurable.
/// Creates an entrypoint given package and lockfile objects.
Entrypoint.inMemory(this.root, this._lockFile, this.cache);
/// The path to the entrypoint's "packages" directory.
String get packagesDir => path.join(root.dir, 'packages');
/// `true` if the entrypoint package currently has a lock file.
bool get lockFileExists => entryExists(lockFilePath);
bool get lockFileExists => _lockFile != null || entryExists(lockFilePath);
LockFile get lockFile {
if (_lockFile != null) return _lockFile;
if (!lockFileExists) {
_lockFile = new LockFile.empty();
} else {
_lockFile = new LockFile.load(lockFilePath, cache.sources);
}
return _lockFile;
}
/// The path to the entrypoint package's lockfile.
String get lockFilePath => path.join(root.dir, 'pubspec.lock');
@ -73,7 +92,7 @@ class Entrypoint {
Future acquireDependencies({List<String> useLatest, bool isUpgrade: false,
bool dryRun: false}) {
return syncFuture(() {
return resolveVersions(cache.sources, root, lockFile: loadLockFile(),
return resolveVersions(cache.sources, root, lockFile: lockFile,
useLatest: useLatest, upgradeAll: isUpgrade && useLatest.isEmpty);
}).then((result) {
if (!result.succeeded) throw result.error;
@ -111,15 +130,6 @@ class Entrypoint {
return source.get(id, packageDir).then((_) => source.resolveId(id));
}
/// Loads the list of concrete package versions from the `pubspec.lock`, if it
/// exists.
///
/// If it doesn't, this completes to an empty [LockFile].
LockFile loadLockFile() {
if (!lockFileExists) return new LockFile.empty();
return new LockFile.load(lockFilePath, cache.sources);
}
/// Determines whether or not the lockfile is out of date with respect to the
/// pubspec.
///
@ -174,8 +184,6 @@ class Entrypoint {
/// pubspec.
Future _ensureLockFileIsUpToDate() {
return syncFuture(() {
var lockFile = loadLockFile();
// If we don't have a current lock file, we definitely need to install.
if (!_isLockFileUpToDate(lockFile)) {
if (lockFileExists) {
@ -214,7 +222,6 @@ class Entrypoint {
/// and up to date.
Future<PackageGraph> loadPackageGraph() {
return _ensureLockFileIsUpToDate().then((_) {
var lockFile = loadLockFile();
return Future.wait(lockFile.packages.values.map((id) {
var source = cache.sources[id.source];
return source.getDirectory(id)

View file

@ -51,12 +51,23 @@ class SilentException extends WrappedException {
/// A class for command usage exceptions.
class UsageException extends ApplicationException {
/// The command usage information.
final String usage;
String _usage;
UsageException(String message, this.usage)
UsageException(String message)
: super(message);
String toString() => "$message\n\n$usage";
String toString() {
if (_usage == null) return message;
return "$message\n\n$_usage";
}
/// Attach usage information to the exception.
///
/// This is done after the exception is created so that code outside of the
/// command can still generate usage errors.
void bindUsage(String usage) {
_usage = usage;
}
}
/// A class for errors in a command's input data.

View file

@ -0,0 +1,125 @@
// Copyright (c) 2014, 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.
library pub.executable;
import 'dart:async';
import 'dart:io';
import 'package:barback/barback.dart';
import 'package:path/path.dart' as p;
import 'package:stack_trace/stack_trace.dart';
import 'barback/asset_environment.dart';
import 'barback/barback_server.dart';
import 'entrypoint.dart';
import 'exit_codes.dart' as exit_codes;
import 'log.dart' as log;
import 'utils.dart';
/// Runs [executable] from [package] reachable from [entrypoint].
///
/// The executable string is a relative Dart file path using native path
/// separators without a trailing ".dart" extension. It is contained within
/// [package], which should either be the entrypoint package or an immediate
/// dependency of it.
///
/// Arguments from [args] will be passed to the spawned Dart application.
///
/// Returns the exit code of the spawned app.
Future<int> runExecutable(Entrypoint entrypoint, String package,
String executable, Iterable<String> args) {
// If the command has a path separator, then it's a path relative to the
// root of the package. Otherwise, it's implicitly understood to be in
// "bin".
var rootDir = "bin";
var parts = p.split(executable);
if (parts.length > 1) {
if (package != entrypoint.root.name) {
usageError("Cannot run an executable in a subdirectory of a dependency.");
}
rootDir = parts.first;
} else {
executable = p.join("bin", executable);
}
// Unless the user overrides the verbosity, we want to filter out the
// normal pub output shown while loading the environment.
if (log.verbosity == log.Verbosity.NORMAL) {
log.verbosity = log.Verbosity.WARNING;
}
var environment;
return AssetEnvironment.create(entrypoint, BarbackMode.RELEASE,
WatcherType.NONE, useDart2JS: false).then((_environment) {
environment = _environment;
environment.barback.errors.listen((error) {
log.error(log.red("Build error:\n$error"));
});
if (package == entrypoint.root.name) {
// Serve the entire root-most directory containing the entrypoint. That
// ensures that, for example, things like `import '../../utils.dart';`
// will work from within some deeply nested script.
return environment.serveDirectory(rootDir);
}
// Make sure the dependency exists.
var dep = entrypoint.root.immediateDependencies.firstWhere(
(dep) => dep.name == package, orElse: () => null);
if (dep == null) {
if (environment.graph.packages.containsKey(package)) {
dataError('Package "$package" is not an immediate dependency.\n'
'Cannot run executables in transitive dependencies.');
} else {
dataError('Could not find package "$package". Did you forget to '
'add a dependency?');
}
}
// For other packages, always use the "bin" directory.
return environment.servePackageBinDirectory(package);
}).then((server) {
// Try to make sure the entrypoint script exists (or is generated) before
// we spawn the process to run it.
var assetPath = "${p.url.joinAll(p.split(executable))}.dart";
var id = new AssetId(server.package, assetPath);
return environment.barback.getAssetById(id).then((_) {
var vmArgs = [];
// Run in checked mode.
// TODO(rnystrom): Make this configurable.
vmArgs.add("--checked");
// Get the URL of the executable, relative to the server's root directory.
var relativePath = p.url.relative(assetPath,
from: p.url.joinAll(p.split(server.rootDirectory)));
vmArgs.add(server.url.resolve(relativePath).toString());
vmArgs.addAll(args);
return Process.start(Platform.executable, vmArgs).then((process) {
// Note: we're not using process.std___.pipe(std___) here because
// that prevents pub from also writing to the output streams.
process.stderr.listen(stderr.add);
process.stdout.listen(stdout.add);
stdin.listen(process.stdin.add);
return process.exitCode;
});
}).catchError((error, stackTrace) {
if (error is! AssetNotFoundException) throw error;
var message = "Could not find ${log.bold(executable + ".dart")}";
if (package != entrypoint.root.name) {
message += " in package ${log.bold(server.package)}";
}
log.error("$message.");
log.fine(new Chain.forTrace(stackTrace));
return exit_codes.NO_INPUT;
});
});
}

View file

@ -10,6 +10,7 @@ import 'dart:io';
import 'package:path/path.dart' as p;
import 'entrypoint.dart';
import 'io.dart';
import 'lock_file.dart';
import 'log.dart' as log;
@ -50,13 +51,11 @@ class GlobalPackages {
/// Finds the latest version of the hosted package with [name] that matches
/// [constraint] and makes it the active global version.
Future activate(String name, VersionConstraint constraint) {
var lockFilePath = p.join(_directory, name + ".lock");
// See if we already have it activated.
var lockFile;
var currentVersion;
try {
lockFile = new LockFile.load(lockFilePath, cache.sources);
lockFile = new LockFile.load(_getLockFilePath(name), cache.sources);
currentVersion = lockFile.packages[name].version;
// Pull the root package out of the lock file so the solver doesn't see
@ -66,7 +65,7 @@ class GlobalPackages {
log.message("Package ${log.bold(name)} is already active at "
"version ${log.bold(currentVersion)}.");
} on IOException catch (error) {
// If we couldn't read the lock and version file, it's not activated.
// If we couldn't read the lock file, it's not activated.
lockFile = new LockFile.empty();
}
@ -98,7 +97,7 @@ class GlobalPackages {
lockFile.packages[name] = id;
ensureDir(_directory);
writeTextFile(lockFilePath,
writeTextFile(_getLockFilePath(name),
lockFile.serialize(cache.rootDir, cache.sources));
log.message("Activated ${log.bold(package.name)} ${package.version}.");
@ -123,6 +122,35 @@ class GlobalPackages {
}
}
/// Finds the active packge with [name].
///
/// Returns an [Entrypoint] loaded with the active package if found.
Future<Entrypoint> find(String name) {
var lockFile;
var version;
return syncFuture(() {
try {
lockFile = new LockFile.load(_getLockFilePath(name), cache.sources);
version = lockFile.packages[name].version;
} on IOException catch (error) {
// If we couldn't read the lock file, it's not activated.
dataError("No active package ${log.bold(name)}.");
}
}).then((_) {
// Load the package from the cache.
var id = new PackageId(name, _source.name, version, name);
return _source.getDirectory(id);
}).then((dir) {
return new Package.load(name, dir, cache.sources);
}).then((package) {
// Pull the root package out of the lock file so the solver doesn't see
// it.
lockFile.packages.remove(name);
return new Entrypoint.inMemory(package, lockFile, cache);
});
}
/// Picks the best version of [package] to activate that meets [constraint].
///
/// If [version] is not `null`, this tries to maintain that version if
@ -149,4 +177,7 @@ class GlobalPackages {
return versions.last;
});
}
/// Gets the path to the lock file for an activated package with [name].
String _getLockFilePath(name) => p.join(_directory, name + ".lock");
}

View file

@ -845,6 +845,7 @@ void fail(String message, [innerError, StackTrace innerTrace]) {
/// failed because of invalid input data.
///
/// This will report the error and cause pub to exit with [exit_codes.DATA].
void dataError(String message) {
throw new DataException(message);
}
void dataError(String message) => throw new DataException(message);
/// Throw a [UsageException] for a usage error of a command with [message].
void usageError(String message) => throw new UsageException(message);

View file

@ -75,10 +75,9 @@ class DependencyValidator extends Validator {
/// Warn that dependencies should have version constraints.
void _warnAboutNoConstraint(PackageDep dep) {
var lockFile = entrypoint.loadLockFile();
var message = 'Your dependency on "${dep.name}" should have a version '
'constraint.';
var locked = lockFile.packages[dep.name];
var locked = entrypoint.lockFile.packages[dep.name];
if (locked != null) {
message = '$message For example:\n'
'\n'
@ -107,7 +106,7 @@ class DependencyValidator extends Validator {
/// Warn that dependencies should have lower bounds on their constraints.
void _warnAboutNoConstraintLowerBound(PackageDep dep) {
var message = 'Your dependency on "${dep.name}" should have a lower bound.';
var locked = entrypoint.loadLockFile().packages[dep.name];
var locked = entrypoint.lockFile.packages[dep.name];
if (locked != null) {
var constraint;
if (locked.version == (dep.constraint as VersionRange).max) {

View file

@ -0,0 +1,22 @@
// Copyright (c) 2014, 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 '../../../lib/src/exit_codes.dart' as exit_codes;
import '../../test_pub.dart';
main() {
initConfig();
integration('fails if no executable was given', () {
schedulePub(args: ["global", "run", "pkg"],
error: """
Must specify an executable to run.
Usage: pub global run <package> <executable> [args...]
-h, --help Print usage information for this command.
Run "pub help" to see global options.
""",
exitCode: exit_codes.USAGE);
});
}

View file

@ -0,0 +1,22 @@
// Copyright (c) 2014, 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 '../../../lib/src/exit_codes.dart' as exit_codes;
import '../../test_pub.dart';
main() {
initConfig();
integration('fails if no package was given', () {
schedulePub(args: ["global", "run"],
error: """
Must specify a package and executable to run.
Usage: pub global run <package> <executable> [args...]
-h, --help Print usage information for this command.
Run "pub help" to see global options.
""",
exitCode: exit_codes.USAGE);
});
}

View file

@ -0,0 +1,22 @@
// Copyright (c) 2014, 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:path/path.dart' as p;
import '../../../lib/src/exit_codes.dart' as exit_codes;
import '../../descriptor.dart' as d;
import '../../test_pub.dart';
main() {
initConfig();
integration('errors if the script does not exist.', () {
servePackages([packageMap("foo", "1.0.0")]);
schedulePub(args: ["global", "activate", "foo"]);
var pub = pubRun(global: true, args: ["foo", "script"]);
pub.stderr.expect("Could not find ${p.join("bin", "script.dart")}.");
pub.shouldExit(exit_codes.NO_INPUT);
});
}

View file

@ -0,0 +1,26 @@
// Copyright (c) 2014, 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 '../../../lib/src/exit_codes.dart' as exit_codes;
import '../../descriptor.dart' as d;
import '../../test_pub.dart';
main() {
initConfig();
integration('runs a script in an activated package', () {
servePackages([
packageMap("foo", "1.0.0")
], contents: [
d.dir("bin", [
d.file("script.dart", "main(args) => print('ok');")
])
]);
schedulePub(args: ["global", "activate", "foo"]);
var pub = pubRun(global: true, args: ["foo", "script"]);
pub.stdout.expect("ok");
pub.shouldExit();
});
}

View file

@ -0,0 +1,48 @@
// Copyright (c) 2014, 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 '../../descriptor.dart' as d;
import '../../test_pub.dart';
const TRANSFORMER = """
import 'dart:async';
import 'package:barback/barback.dart';
class DartTransformer extends Transformer {
DartTransformer.asPlugin();
String get allowedExtensions => '.in';
void apply(Transform transform) {
transform.addOutput(new Asset.fromString(
new AssetId("foo", "bin/script.dart"),
"void main() => print('generated');"));
}
}
""";
main() {
initConfig();
withBarbackVersions("any", () {
integration('runs a global script generated by a transformer', () {
makeGlobalPackage("foo", "1.0.0", [
d.pubspec({
"name": "foo",
"version": "1.0.0",
"transformers": ["foo/src/transformer"]
}),
d.dir("lib", [d.dir("src", [
d.file("transformer.dart", TRANSFORMER),
d.file("primary.in", "")
])])
], pkg: ['barback']);
var pub = pubRun(global: true, args: ["foo", "script"]);
pub.stdout.expect("generated");
pub.shouldExit();
});
});
}

View file

@ -0,0 +1,20 @@
// Copyright (c) 2014, 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:scheduled_test/scheduled_test.dart';
import '../../../lib/src/exit_codes.dart' as exit_codes;
import '../../descriptor.dart' as d;
import '../../test_pub.dart';
main() {
initConfig();
integration('errors if the package is not activated', () {
servePackages([]);
schedulePub(args: ["global", "run", "foo", "bar"],
error: startsWith("No active package foo."),
exitCode: exit_codes.DATA);
});
}

View file

@ -4,12 +4,12 @@
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
const SCRIPT = """
import 'dart:io';
main() {
print("started");
var line1 = stdin.readLineSync();
print("between");
var line2 = stdin.readLineSync();
@ -30,6 +30,7 @@ main() {
var pub = pubRun(args: ["script"]);
pub.stdout.expect("started");
pub.writeLine("first");
pub.stdout.expect("between");
pub.writeLine("second");

View file

@ -6,7 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart';
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
const SCRIPT = """
import "package:myapp/lib.dart";
@ -69,8 +68,7 @@ main() {
createLockFile('myapp', pkg: ['barback']);
var pub = pubRun(args: ["script"],
transformers: ["myapp/src/transformer"]);
var pub = pubRun(args: ["script"]);
// Note that the info log is only displayed here because the test
// harness runs pub in verbose mode. By default, only the warning would

View file

@ -4,7 +4,6 @@
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
const SCRIPT = """
main() {
@ -51,8 +50,7 @@ main() {
createLockFile('myapp', pkg: ['barback']);
var pub = pubRun(args: ["script"],
transformers: ["myapp/src/transformer"]);
var pub = pubRun(args: ["script"]);
pub.stderr.expect("[Error from Failing]:");
pub.stderr.expect("myapp|bin/script.dart.");

View file

@ -5,7 +5,6 @@
import '../../lib/src/exit_codes.dart' as exit_codes;
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
main() {
initConfig();

View file

@ -22,7 +22,7 @@ main() {
schedulePub(args: ["run", "foo:sub/dir"],
error: """
Can not run an executable in a subdirectory of a dependency.
Cannot run an executable in a subdirectory of a dependency.
Usage: pub run <executable> [args...]
-h, --help Print usage information for this command.

View file

@ -6,7 +6,6 @@ import 'package:path/path.dart' as path;
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
const SCRIPT = r"""
import '../../a.dart';

View file

@ -5,7 +5,6 @@
import '../../lib/src/exit_codes.dart' as exit_codes;
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
main() {
initConfig();

View file

@ -7,7 +7,6 @@ import 'package:path/path.dart' as p;
import '../../lib/src/exit_codes.dart' as exit_codes;
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
main() {
initConfig();

View file

@ -7,7 +7,6 @@ import 'package:path/path.dart' as p;
import '../../lib/src/exit_codes.dart' as exit_codes;
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
main() {
initConfig();

View file

@ -4,7 +4,6 @@
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
const SCRIPT = """
main(List<String> args) {

View file

@ -4,7 +4,6 @@
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
const TRANSFORMER = """
import 'dart:async';
@ -41,8 +40,7 @@ main() {
createLockFile('myapp', pkg: ['barback']);
var pub = pubRun(args: ["script"],
transformers: ["myapp/src/transformer"]);
var pub = pubRun(args: ["script"]);
pub.stdout.expect("generated");
pub.shouldExit();

View file

@ -6,7 +6,6 @@ import 'package:path/path.dart' as path;
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
main() {
initConfig();

View file

@ -4,7 +4,6 @@
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
const SCRIPT = """
import 'dart:io';

View file

@ -4,7 +4,6 @@
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
main() {
initConfig();

View file

@ -4,7 +4,6 @@
import '../descriptor.dart' as d;
import '../test_pub.dart';
import 'utils.dart';
main() {
initConfig();

View file

@ -5,7 +5,6 @@
import '../descriptor.dart' as d;
import '../test_pub.dart';
import '../serve/utils.dart';
import 'utils.dart';
const SCRIPT = """
const TOKEN = "hi";
@ -33,8 +32,7 @@ main() {
createLockFile('myapp', pkg: ['barback']);
var pub = pubRun(args: ["hi"],
transformers: ["myapp/src/transformer"]);
var pub = pubRun(args: ["hi"]);
pub.stdout.expect("(hi, transformed)");
pub.shouldExit();

View file

@ -1,32 +0,0 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS d.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:scheduled_test/scheduled_process.dart';
import 'package:scheduled_test/scheduled_test.dart';
import '../test_pub.dart';
/// Schedules starting the "pub run" process and validates the expected startup
/// output.
///
/// if [transformers] is given, it should contain a list of transformer IDs
/// (like "myapp/src/transformer") and this will validate that the output for
/// loading those is shown.
///
/// Returns the `pub run` process.
ScheduledProcess pubRun({Iterable<String> args,
Iterable<String> transformers}) {
var pub = startPub(args: ["run"]..addAll(args));
// This isn't normally printed, but the pub test infrastructure runs pub in
// verbose mode, which enables this.
pub.stdout.expect(startsWith("Loading source assets"));
if (transformers != null) {
for (var transformer in transformers) {
pub.stdout.expect(startsWith("Loading $transformer transformers"));
}
}
return pub;
}

View file

@ -225,7 +225,11 @@ Map<String, List<Map>> _servedPackages;
/// If [replace] is false, subsequent calls to [servePackages] will add to the
/// set of packages that are being served. Previous packages will continue to be
/// served. Otherwise, the previous packages will no longer be served.
void servePackages(List<Map> pubspecs, {bool replace: false}) {
///
/// If [contents] is given, its contents are added to every served
/// package.
void servePackages(List<Map> pubspecs, {bool replace: false,
Iterable<d.Descriptor> contents}) {
if (_servedPackages == null || _servedPackageDir == null) {
_servedPackages = <String, List<Map>>{};
_servedApiPackageDir = d.dir('packages', []);
@ -246,11 +250,11 @@ void servePackages(List<Map> pubspecs, {bool replace: false}) {
return awaitObject(pubspecs).then((resolvedPubspecs) {
if (replace) _servedPackages.clear();
for (var spec in resolvedPubspecs) {
var name = spec['name'];
var version = spec['version'];
for (var pubspec in resolvedPubspecs) {
var name = pubspec['name'];
var version = pubspec['version'];
var versions = _servedPackages.putIfAbsent(name, () => []);
versions.add(spec);
versions.add(pubspec);
}
_servedApiPackageDir.contents.clear();
@ -273,10 +277,17 @@ void servePackages(List<Map> pubspecs, {bool replace: false}) {
_servedPackageDir.contents.add(d.dir(name, [
d.dir('versions', _servedPackages[name].map((pubspec) {
var version = pubspec['version'];
return d.tar('$version.tar.gz', [
d.file('pubspec.yaml', JSON.encode(pubspec)),
d.libDir(name, '$name $version')
]);
var archiveContents = [
d.file('pubspec.yaml', JSON.encode(pubspec)),
d.libDir(name, '$name $version')
];
if (contents != null) {
archiveContents.addAll(contents);
}
return d.tar('$version.tar.gz', archiveContents);
}))
]));
}
@ -377,6 +388,25 @@ void pubUpgrade({Iterable<String> args, output, error, warning, int exitCode}) {
warning: warning, exitCode: exitCode);
}
/// Schedules starting the "pub [global] run" process and validates the
/// expected startup output.
///
/// If [global] is `true`, this invokes "pub global run", otherwise it does
/// "pub run".
///
/// Returns the `pub run` process.
ScheduledProcess pubRun({bool global: false, Iterable<String> args}) {
var pubArgs = global ? ["global", "run"] : ["run"];
pubArgs.addAll(args);
var pub = startPub(args: pubArgs);
// Loading sources and transformers isn't normally printed, but the pub test
// infrastructure runs pub in verbose mode, which enables this.
pub.stdout.expect(consumeWhile(startsWith("Loading")));
return pub;
}
/// Defines an integration test.
///
/// The [body] should schedule a series of operations which will be run
@ -670,6 +700,42 @@ void ensureGit() {
}
}
/// Schedules activating a global package [package] without running
/// "pub global activate".
///
/// This is useful because global packages must be hosted, but the test hosted
/// server doesn't serve barback. The other parameters here follow
/// [createLockFile].
void makeGlobalPackage(String package, String version,
Iterable<d.Descriptor> contents, {Iterable<String> pkg,
Map<String, String> hosted}) {
// Start the server so we know what port to use in the cache directory name.
servePackages([]);
// Create the package in the hosted cache.
d.hostedCache([
d.dir("$package-$version", contents)
]).create();
var lockFile = _createLockFile(pkg: pkg, hosted: hosted);
// Add the root package to the lockfile.
var id = new PackageId(package, "hosted", new Version.parse(version),
package);
lockFile.packages[package] = id;
// Write the lockfile to the global cache.
var sources = new SourceRegistry();
sources.register(new HostedSource());
sources.register(new PathSource());
d.dir(cachePath, [
d.dir("global_packages", [
d.file("$package.lock", lockFile.serialize(null, sources))
])
]).create();
}
/// Creates a lock file for [package] without running `pub get`.
///
/// [sandbox] is a list of path dependencies to be found in the sandbox
@ -681,6 +747,27 @@ void ensureGit() {
/// hosted packages.
void createLockFile(String package, {Iterable<String> sandbox,
Iterable<String> pkg, Map<String, String> hosted}) {
var lockFile = _createLockFile(sandbox: sandbox, pkg: pkg, hosted: hosted);
var sources = new SourceRegistry();
sources.register(new HostedSource());
sources.register(new PathSource());
d.file(path.join(package, 'pubspec.lock'),
lockFile.serialize(null, sources)).create();
}
/// Creates a lock file for [package] without running `pub get`.
///
/// [sandbox] is a list of path dependencies to be found in the sandbox
/// directory. [pkg] is a list of packages in the Dart repo's "pkg" directory;
/// each package listed here and all its dependencies will be linked to the
/// version in the Dart repo.
///
/// [hosted] is a list of package names to version strings for dependencies on
/// hosted packages.
LockFile _createLockFile({Iterable<String> sandbox,
Iterable<String> pkg, Map<String, String> hosted}) {
var dependencies = {};
if (sandbox != null) {
@ -732,12 +819,7 @@ void createLockFile(String package, {Iterable<String> sandbox,
});
}
var sources = new SourceRegistry();
sources.register(new HostedSource());
sources.register(new PathSource());
d.file(path.join(package, 'pubspec.lock'),
lockFile.serialize(null, sources)).create();
return lockFile;
}
/// Uses [client] as the mock HTTP client for this test.