Dev dependencies.

BUG=dartbug.com/5358

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

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@19966 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
rnystrom@google.com 2013-03-13 19:40:25 +00:00
parent a2a7fb6b2b
commit 0122b2954e
7 changed files with 405 additions and 140 deletions

View file

@ -25,6 +25,9 @@ class Pubspec {
/// The packages this package depends on.
final List<PackageRef> dependencies;
/// The packages this package depends on when it is the root package.
final List<PackageRef> devDependencies;
/// The environment-related metadata.
final PubspecEnvironment environment;
@ -55,14 +58,15 @@ class Pubspec {
}
}
Pubspec(this.name, this.version, this.dependencies, this.environment,
[Map<String, Object> fields])
Pubspec(this.name, this.version, this.dependencies, this.devDependencies,
this.environment, [Map<String, Object> fields])
: this.fields = fields == null ? {} : fields;
Pubspec.empty()
: name = null,
version = Version.none,
dependencies = <PackageRef>[],
devDependencies = <PackageRef>[],
environment = new PubspecEnvironment(),
fields = {};
@ -105,6 +109,42 @@ class Pubspec {
var dependencies = _parseDependencies(filePath, sources,
parsedPubspec['dependencies']);
var devDependencies = _parseDependencies(filePath, sources,
parsedPubspec['dev_dependencies']);
// Make sure the same package doesn't appear as both a regular and dev
// dependency.
var dependencyNames = dependencies.map((dep) => dep.name).toSet();
var collisions = dependencyNames.intersection(
devDependencies.map((dep) => dep.name).toSet());
if (!collisions.isEmpty) {
var packageNames;
if (collisions.length == 1) {
packageNames = 'Package "${collisions.first}"';
} else {
var names = collisions.toList();
names.sort();
var buffer = new StringBuffer();
buffer.write("Packages ");
for (var i = 0; i < names.length; i++) {
buffer.write('"');
buffer.write(names[i]);
buffer.write('"');
if (i == names.length - 2) {
buffer.write(", ");
} else if (i == names.length - 1) {
buffer.write(", and ");
}
}
packageNames = buffer.toString();
}
throw new FormatException(
'$packageNames cannot appear in both "dependencies" and '
'"dev_dependencies".');
}
var environmentYaml = parsedPubspec['environment'];
var sdkConstraint = VersionConstraint.any;
if (environmentYaml != null) {
@ -169,7 +209,8 @@ class Pubspec {
}
}
return new Pubspec(name, version, dependencies, environment, parsedPubspec);
return new Pubspec(name, version, dependencies, devDependencies,
environment, parsedPubspec);
}
}

View file

@ -30,7 +30,7 @@ class SdkSource extends Source {
var pubspec = new Pubspec.load(id.name, packageDir, systemCache.sources);
// Ignore the pubspec's version, and use the SDK's.
return new Pubspec(id.name, sdk.version, pubspec.dependencies,
pubspec.environment);
pubspec.devDependencies, pubspec.environment);
});
}

View file

@ -283,8 +283,8 @@ class ChangeVersion implements WorkItem {
Version version) {
// If there is no version, it means no package, so no dependencies.
if (version == null) {
return
new Future<Map<String, PackageRef>>.immediate(<String, PackageRef>{});
return new Future<Map<String, PackageRef>>.immediate(
<String, PackageRef>{});
}
var id = new PackageId(package, source, version, description);
@ -293,6 +293,14 @@ class ChangeVersion implements WorkItem {
for (var dependency in pubspec.dependencies) {
dependencies[dependency.name] = dependency;
}
// Include dev dependencies only from the root package.
if (id.isRoot) {
for (var dependency in pubspec.devDependencies) {
dependencies[dependency.name] = dependency;
}
}
return dependencies;
});
}

View file

@ -0,0 +1,116 @@
// Copyright (c) 2013, 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 '../../../pkg/pathos/lib/path.dart' as path;
import 'test_pub.dart';
main() {
initConfig();
integration("includes root package's dev dependencies", () {
dir('foo', [
libDir('foo'),
libPubspec('foo', '0.0.1')
]).scheduleCreate();
dir('bar', [
libDir('bar'),
libPubspec('bar', '0.0.1')
]).scheduleCreate();
dir(appPath, [
pubspec({
"name": "myapp",
"dev_dependencies": {
"foo": {"path": "../foo"},
"bar": {"path": "../bar"},
}
})
]).scheduleCreate();
schedulePub(args: ["install"],
output: new RegExp(r"Dependencies installed!$"));
dir(packagesPath, [
dir("foo", [
file("foo.dart", 'main() => "foo";')
]),
dir("bar", [
file("bar.dart", 'main() => "bar";')
])
]).scheduleValidate();
});
integration("includes dev dependency's transitive dependencies", () {
dir('foo', [
libDir('foo'),
libPubspec('foo', '0.0.1', deps: [
{"path": "../bar"}
])
]).scheduleCreate();
dir('bar', [
libDir('bar'),
libPubspec('bar', '0.0.1')
]).scheduleCreate();
dir(appPath, [
pubspec({
"name": "myapp",
"dev_dependencies": {
"foo": {"path": "../foo"}
}
})
]).scheduleCreate();
schedulePub(args: ["install"],
output: new RegExp(r"Dependencies installed!$"));
dir(packagesPath, [
dir("foo", [
file("foo.dart", 'main() => "foo";')
]),
dir("bar", [
file("bar.dart", 'main() => "bar";')
])
]).scheduleValidate();
});
integration("ignores transitive dependency's dev dependencies", () {
dir('foo', [
libDir('foo'),
pubspec({
"name": "foo",
"version": "0.0.1",
"dev_dependencies": {
"bar": {"path": "../bar"}
}
})
]).scheduleCreate();
dir('bar', [
libDir('bar'),
libPubspec('bar', '0.0.1')
]).scheduleCreate();
dir(appPath, [
pubspec({
"name": "myapp",
"dependencies": {
"foo": {"path": "../foo"}
}
})
]).scheduleCreate();
schedulePub(args: ["install"],
output: new RegExp(r"Dependencies installed!$"));
dir(packagesPath, [
dir("foo", [
file("foo.dart", 'main() => "foo";')
]),
nothing("bar")
]).scheduleValidate();
});
}

View file

@ -25,154 +25,186 @@ class MockSource extends Source {
main() {
initConfig();
group('Pubspec', () {
group('parse()', () {
var sources = new SourceRegistry();
sources.register(new MockSource());
group('parse()', () {
var sources = new SourceRegistry();
sources.register(new MockSource());
expectFormatError(String pubspec) {
expect(() => new Pubspec.parse(null, pubspec, sources),
throwsFormatException);
}
expectFormatError(String pubspec) {
expect(() => new Pubspec.parse(null, pubspec, sources),
throwsFormatException);
}
test("allows a version constraint for dependencies", () {
var pubspec = new Pubspec.parse(null, '''
test("allows a version constraint for dependencies", () {
var pubspec = new Pubspec.parse(null, '''
dependencies:
foo:
mock: ok
version: ">=1.2.3 <3.4.5"
''', sources);
var foo = pubspec.dependencies[0];
expect(foo.name, equals('foo'));
expect(foo.constraint.allows(new Version(1, 2, 3)), isTrue);
expect(foo.constraint.allows(new Version(1, 2, 5)), isTrue);
expect(foo.constraint.allows(new Version(3, 4, 5)), isFalse);
});
var foo = pubspec.dependencies[0];
expect(foo.name, equals('foo'));
expect(foo.constraint.allows(new Version(1, 2, 3)), isTrue);
expect(foo.constraint.allows(new Version(1, 2, 5)), isTrue);
expect(foo.constraint.allows(new Version(3, 4, 5)), isFalse);
});
test("allows an empty dependencies map", () {
var pubspec = new Pubspec.parse(null, '''
test("allows an empty dependencies map", () {
var pubspec = new Pubspec.parse(null, '''
dependencies:
''', sources);
expect(pubspec.dependencies, isEmpty);
});
expect(pubspec.dependencies, isEmpty);
});
test("throws if the description isn't valid", () {
expectFormatError('''
test("allows a version constraint for dev dependencies", () {
var pubspec = new Pubspec.parse(null, '''
dev_dependencies:
foo:
mock: ok
version: ">=1.2.3 <3.4.5"
''', sources);
var foo = pubspec.devDependencies[0];
expect(foo.name, equals('foo'));
expect(foo.constraint.allows(new Version(1, 2, 3)), isTrue);
expect(foo.constraint.allows(new Version(1, 2, 5)), isTrue);
expect(foo.constraint.allows(new Version(3, 4, 5)), isFalse);
});
test("allows an empty dev dependencies map", () {
var pubspec = new Pubspec.parse(null, '''
dev_dependencies:
''', sources);
expect(pubspec.devDependencies, isEmpty);
});
test("throws if a package is in dependencies and dev_dependencies", () {
expectFormatError('''
dependencies:
foo:
mock: ok
dev_dependencies:
foo:
mock: ok
''');
});
test("throws if the description isn't valid", () {
expectFormatError('''
dependencies:
foo:
mock: bad
''');
});
});
test("throws if 'name' is not a string", () {
expectFormatError('name: [not, a, string]');
});
test("throws if 'name' is not a string", () {
expectFormatError('name: [not, a, string]');
});
test("throws if 'homepage' is not a string", () {
expectFormatError('homepage:');
expectFormatError('homepage: [not, a, string]');
});
test("throws if 'homepage' is not a string", () {
expectFormatError('homepage:');
expectFormatError('homepage: [not, a, string]');
});
test("throws if 'homepage' doesn't have an HTTP scheme", () {
new Pubspec.parse(null, 'homepage: http://ok.com', sources);
new Pubspec.parse(null, 'homepage: https://also-ok.com', sources);
test("throws if 'homepage' doesn't have an HTTP scheme", () {
new Pubspec.parse(null, 'homepage: http://ok.com', sources);
new Pubspec.parse(null, 'homepage: https://also-ok.com', sources);
expectFormatError('homepage: ftp://badscheme.com');
expectFormatError('homepage: javascript:alert("!!!")');
expectFormatError('homepage: data:image/png;base64,somedata');
expectFormatError('homepage: no-scheme.com');
});
expectFormatError('homepage: ftp://badscheme.com');
expectFormatError('homepage: javascript:alert("!!!")');
expectFormatError('homepage: data:image/png;base64,somedata');
expectFormatError('homepage: no-scheme.com');
});
test("throws if 'documentation' is not a string", () {
expectFormatError('documentation:');
expectFormatError('documentation: [not, a, string]');
});
test("throws if 'documentation' is not a string", () {
expectFormatError('documentation:');
expectFormatError('documentation: [not, a, string]');
});
test("throws if 'documentation' doesn't have an HTTP scheme", () {
new Pubspec.parse(null, 'documentation: http://ok.com', sources);
new Pubspec.parse(null, 'documentation: https://also-ok.com', sources);
test("throws if 'documentation' doesn't have an HTTP scheme", () {
new Pubspec.parse(null, 'documentation: http://ok.com', sources);
new Pubspec.parse(null, 'documentation: https://also-ok.com', sources);
expectFormatError('documentation: ftp://badscheme.com');
expectFormatError('documentation: javascript:alert("!!!")');
expectFormatError('documentation: data:image/png;base64,somedata');
expectFormatError('documentation: no-scheme.com');
});
expectFormatError('documentation: ftp://badscheme.com');
expectFormatError('documentation: javascript:alert("!!!")');
expectFormatError('documentation: data:image/png;base64,somedata');
expectFormatError('documentation: no-scheme.com');
});
test("throws if 'authors' is not a string or a list of strings", () {
new Pubspec.parse(null, 'authors: ok fine', sources);
new Pubspec.parse(null, 'authors: [also, ok, fine]', sources);
test("throws if 'authors' is not a string or a list of strings", () {
new Pubspec.parse(null, 'authors: ok fine', sources);
new Pubspec.parse(null, 'authors: [also, ok, fine]', sources);
expectFormatError('authors: 123');
expectFormatError('authors: {not: {a: string}}');
expectFormatError('authors: [ok, {not: ok}]');
});
expectFormatError('authors: 123');
expectFormatError('authors: {not: {a: string}}');
expectFormatError('authors: [ok, {not: ok}]');
});
test("throws if 'author' is not a string", () {
new Pubspec.parse(null, 'author: ok fine', sources);
test("throws if 'author' is not a string", () {
new Pubspec.parse(null, 'author: ok fine', sources);
expectFormatError('author: 123');
expectFormatError('author: {not: {a: string}}');
expectFormatError('author: [not, ok]');
});
expectFormatError('author: 123');
expectFormatError('author: {not: {a: string}}');
expectFormatError('author: [not, ok]');
});
test("throws if both 'author' and 'authors' are present", () {
expectFormatError('{author: abe, authors: ted}');
});
test("throws if both 'author' and 'authors' are present", () {
expectFormatError('{author: abe, authors: ted}');
});
test("allows comment-only files", () {
var pubspec = new Pubspec.parse(null, '''
test("allows comment-only files", () {
var pubspec = new Pubspec.parse(null, '''
# No external dependencies yet
# Including for completeness
# ...and hoping the spec expands to include details about author, version, etc
# See http://www.dartlang.org/docs/pub-package-manager/ for details
''', sources);
expect(pubspec.version, equals(Version.none));
expect(pubspec.dependencies, isEmpty);
expect(pubspec.version, equals(Version.none));
expect(pubspec.dependencies, isEmpty);
});
group("environment", () {
test("defaults to any SDK constraint if environment is omitted", () {
var pubspec = new Pubspec.parse(null, '', sources);
expect(pubspec.environment.sdkVersion, equals(VersionConstraint.any));
});
group("environment", () {
test("defaults to any SDK constraint if environment is omitted", () {
var pubspec = new Pubspec.parse(null, '', sources);
expect(pubspec.environment.sdkVersion, equals(VersionConstraint.any));
});
test("allows an empty environment map", () {
var pubspec = new Pubspec.parse(null, '''
test("allows an empty environment map", () {
var pubspec = new Pubspec.parse(null, '''
environment:
''', sources);
expect(pubspec.environment.sdkVersion, equals(VersionConstraint.any));
});
expect(pubspec.environment.sdkVersion, equals(VersionConstraint.any));
});
test("throws if the environment value isn't a map", () {
expectFormatError('''
test("throws if the environment value isn't a map", () {
expectFormatError('''
environment: []
''');
});
});
test("allows a version constraint for the sdk", () {
var pubspec = new Pubspec.parse(null, '''
test("allows a version constraint for the sdk", () {
var pubspec = new Pubspec.parse(null, '''
environment:
sdk: ">=1.2.3 <2.3.4"
''', sources);
expect(pubspec.environment.sdkVersion,
equals(new VersionConstraint.parse(">=1.2.3 <2.3.4")));
});
expect(pubspec.environment.sdkVersion,
equals(new VersionConstraint.parse(">=1.2.3 <2.3.4")));
});
test("throws if the sdk isn't a string", () {
expectFormatError('''
test("throws if the sdk isn't a string", () {
expectFormatError('''
environment:
sdk: []
''');
});
});
test("throws if the sdk isn't a valid version constraint", () {
expectFormatError('''
test("throws if the sdk isn't a valid version constraint", () {
expectFormatError('''
environment:
sdk: "oopies"
''');
});
});
});
});

View file

@ -1173,7 +1173,8 @@ class TarFileDescriptor extends Descriptor {
}
}
/// A descriptor that validates that no file exists with the given name.
/// A descriptor that validates that no file or directory exists with the given
/// name.
class NothingDescriptor extends Descriptor {
NothingDescriptor(String name) : super(name);
@ -1183,7 +1184,7 @@ class NothingDescriptor extends Descriptor {
Future validate(String dir) {
return defer(() {
if (entryExists(path.join(dir, name))) {
throw new TestFailure('File $name in $dir should not exist.');
throw new TestFailure('Entry $name in $dir should not exist.');
}
});
}

View file

@ -7,6 +7,8 @@ library pub_update_test;
import 'dart:async';
import 'dart:io';
import '../../../pkg/unittest/lib/unittest.dart';
import '../../pub/lock_file.dart';
import '../../pub/package.dart';
import '../../pub/pubspec.dart';
@ -16,7 +18,7 @@ import '../../pub/system_cache.dart';
import '../../pub/utils.dart';
import '../../pub/version.dart';
import '../../pub/version_solver.dart';
import '../../../pkg/unittest/lib/unittest.dart';
import 'test_pub.dart';
Matcher noVersion(List<String> packages) {
return predicate((x) {
@ -70,6 +72,8 @@ MockSource source2;
Source versionlessSource;
main() {
initConfig();
testResolve('no dependencies', {
'myapp 0.0.0': {}
}, result: {
@ -369,11 +373,53 @@ main() {
}
}, error: couldNotSolve);
group('dev dependencies', () {
testResolve("includes root package's dev dependencies", {
'myapp 1.0.0': {
'(dev) foo': '1.0.0',
'(dev) bar': '1.0.0'
},
'foo 1.0.0': {},
'bar 1.0.0': {}
}, result: {
'myapp from root': '1.0.0',
'foo': '1.0.0',
'bar': '1.0.0'
});
testResolve("includes dev dependency's transitive dependencies", {
'myapp 1.0.0': {
'(dev) foo': '1.0.0'
},
'foo 1.0.0': {
'bar': '1.0.0'
},
'bar 1.0.0': {}
}, result: {
'myapp from root': '1.0.0',
'foo': '1.0.0',
'bar': '1.0.0'
});
testResolve("ignores transitive dependency's dev dependencies", {
'myapp 1.0.0': {
'foo': '1.0.0'
},
'foo 1.0.0': {
'(dev) bar': '1.0.0'
},
'bar 1.0.0': {}
}, result: {
'myapp from root': '1.0.0',
'foo': '1.0.0'
});
});
}
// TODO(rnystrom): More stuff to test:
// - Depending on a non-existent package.
// - Test that only a certain number requests are sent to the mock source so we
// can keep track of server traffic.
}
testResolve(description, packages, {lockfile, result, Matcher error}) {
test(description, () {
@ -389,34 +435,31 @@ testResolve(description, packages, {lockfile, result, Matcher error}) {
// Build the test package graph.
var root;
packages.forEach((nameVersion, dependencies) {
var parsed = parseSource(nameVersion);
nameVersion = parsed.first;
var source = parsed.last;
var parsed = parseSource(nameVersion, (isDev, nameVersion, source) {
var parts = nameVersion.split(' ');
var name = parts[0];
var version = parts[1];
var parts = nameVersion.split(' ');
var name = parts[0];
var version = parts[1];
var package = source1.mockPackage(name, version, dependencies);
if (name == 'myapp') {
// Don't add the root package to the server, so we can verify that Pub
// doesn't try to look up information about the local package on the
// remote server.
root = package;
} else {
source.addPackage(package);
}
var package = source1.mockPackage(name, version, dependencies);
if (name == 'myapp') {
// Don't add the root package to the server, so we can verify that Pub
// doesn't try to look up information about the local package on the
// remote server.
root = package;
} else {
source.addPackage(package);
}
});
});
// Clean up the expectation.
if (result != null) {
var newResult = {};
result.forEach((name, version) {
var parsed = parseSource(name);
name = parsed.first;
var source = parsed.last;
version = new Version.parse(version);
newResult[name] = new PackageId(name, source, version, name);
parseSource(name, (isDev, name, source) {
version = new Version.parse(version);
newResult[name] = new PackageId(name, source, version, name);
});
});
result = newResult;
}
@ -482,16 +525,24 @@ class MockSource extends Source {
Map dependencyStrings) {
// Build the pubspec dependencies.
var dependencies = <PackageRef>[];
var devDependencies = <PackageRef>[];
dependencyStrings.forEach((name, constraint) {
var parsed = parseSource(name);
var description = parsed.first;
var packageName = description.replaceFirst(new RegExp(r"-[^-]+$"), "");
dependencies.add(new PackageRef(packageName, parsed.last,
new VersionConstraint.parse(constraint), description));
parseSource(name, (isDev, name, source) {
var packageName = name.replaceFirst(new RegExp(r"-[^-]+$"), "");
var ref = new PackageRef(packageName, source,
new VersionConstraint.parse(constraint), name);
if (isDev) {
devDependencies.add(ref);
} else {
dependencies.add(ref);
}
});
});
var pubspec = new Pubspec(
description, new Version.parse(version), dependencies,
description, new Version.parse(version), dependencies, devDependencies,
new PubspecEnvironment());
return new Package.inMemory(pubspec);
}
@ -526,14 +577,30 @@ class MockVersionlessSource extends Source {
}
}
Pair<String, Source> parseSource(String name) {
var match = new RegExp(r"(.*) from (.*)").firstMatch(name);
if (match == null) return new Pair<String, Source>(name, source1);
switch (match[2]) {
case 'mock1': return new Pair<String, Source>(match[1], source1);
case 'mock2': return new Pair<String, Source>(match[1], source2);
case 'root': return new Pair<String, Source>(match[1], null);
case 'versionless':
return new Pair<String, Source>(match[1], versionlessSource);
void parseSource(String description,
callback(bool isDev, String name, Source source)) {
var isDev = false;
if (description.startsWith("(dev) ")) {
description = description.substring("(dev) ".length);
isDev = true;
}
var name = description;
var source = source1;
var sourceNames = {
'mock1': source1,
'mock2': source2,
'root': null,
'versionless': versionlessSource
};
var match = new RegExp(r"(.*) from (.*)").firstMatch(description);
if (match != null) {
name = match[1];
source = sourceNames[match[2]];
}
callback(isDev, name, source);
}