Add support for specifying SDK version constraints in pubspecs.

These constraints don't currently do anything, but these patch
gets them being parsed by pub.

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

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17477 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
rnystrom@google.com 2013-01-23 17:41:40 +00:00
parent b1bdb27a12
commit edf7ca2927
7 changed files with 167 additions and 27 deletions

View file

@ -22,11 +22,14 @@ class Pubspec {
/// The packages this package depends on.
final List<PackageRef> dependencies;
/// The environment-related metadata.
final PubspecEnvironment environment;
/// All pubspec fields. This includes the fields from which other properties
/// are derived.
final Map<String, Object> fields;
Pubspec(this.name, this.version, this.dependencies,
Pubspec(this.name, this.version, this.dependencies, this.environment,
[Map<String, Object> fields])
: this.fields = fields == null ? {} : fields;
@ -34,9 +37,10 @@ class Pubspec {
: name = null,
version = Version.none,
dependencies = <PackageRef>[],
environment = new PubspecEnvironment(),
fields = {};
/// Whether or not the pubspec has no contents.
/// Whether or not the pubspec has no contents.
bool get isEmpty =>
name == null && version == Version.none && dependencies.isEmpty;
@ -70,6 +74,26 @@ class Pubspec {
var dependencies = _parseDependencies(sources,
parsedPubspec['dependencies']);
var environmentYaml = parsedPubspec['environment'];
var sdkConstraint = VersionConstraint.any;
if (environmentYaml != null) {
if (environmentYaml is! Map) {
throw new FormatException(
'The pubspec "environment" field should be a map, but was '
'"$environmentYaml".');
}
var sdkYaml = environmentYaml['sdk'];
if (sdkYaml is! String) {
throw new FormatException(
'The "sdk" field of "environment" should be a string, but was '
'"$sdkYaml".');
}
sdkConstraint = new VersionConstraint.parse(sdkYaml);
}
var environment = new PubspecEnvironment(sdkConstraint);
// Even though the pub app itself doesn't use these fields, we validate
// them here so that users find errors early before they try to upload to
// the server:
@ -123,7 +147,7 @@ class Pubspec {
}
}
return new Pubspec(name, version, dependencies, parsedPubspec);
return new Pubspec(name, version, dependencies, environment, parsedPubspec);
}
}
@ -181,3 +205,14 @@ List<PackageRef> _parseDependencies(SourceRegistry sources, yaml) {
return dependencies;
}
/// The environment-related metadata in the pubspec. Corresponds to the data
/// under the "environment:" key in the pubspec.
class PubspecEnvironment {
/// The version constraint specifying which SDK versions this package works
/// with.
final VersionConstraint sdkVersion;
PubspecEnvironment([VersionConstraint sdk])
: sdkVersion = sdk != null ? sdk : VersionConstraint.any;
}

View file

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

View file

@ -110,7 +110,9 @@ class Version implements Comparable, VersionConstraint {
if (other is VersionRange) return other.intersect(this);
// Intersecting two versions only works if they are the same.
if (other is Version) return this == other ? this : const _EmptyVersion();
if (other is Version) {
return this == other ? this : VersionConstraint.empty;
}
throw new ArgumentError(
'Unknown VersionConstraint type $other.');
@ -206,8 +208,11 @@ class Version implements Comparable, VersionConstraint {
/// version that is "2.0.0" or greater. Version objects themselves implement
/// this to match a specific version.
abstract class VersionConstraint {
/// A [VersionConstraint] that allows all versions.
static VersionConstraint any = new VersionRange();
/// A [VersionConstraint] that allows no versions: i.e. the empty set.
factory VersionConstraint.empty() => const _EmptyVersion();
static VersionConstraint empty = const _EmptyVersion();
/// Parses a version constraint. This string is a space-separated series of
/// version parts. Each part can be one of:
@ -333,7 +338,9 @@ class VersionRange implements VersionConstraint {
if (other.isEmpty) return other;
// A range and a Version just yields the version if it's in the range.
if (other is Version) return allows(other) ? other : const _EmptyVersion();
if (other is Version) {
return allows(other) ? other : VersionConstraint.empty;
}
if (other is VersionRange) {
// Intersect the two ranges.
@ -373,13 +380,13 @@ class VersionRange implements VersionConstraint {
if (intersectIncludeMin && intersectIncludeMax) return intersectMin;
// Otherwise, no versions.
return const _EmptyVersion();
return VersionConstraint.empty;
}
if (intersectMin != null && intersectMax != null &&
intersectMin > intersectMax) {
// Non-overlapping ranges, so empty.
return const _EmptyVersion();
return VersionConstraint.empty;
}
// If we got here, there is an actual range.

View file

@ -17,6 +17,7 @@ const _NONE = '\u001b[0m';
/// Pretty Unicode characters!
const _CHECKBOX = '\u2713';
const _BALLOT_X = '\u2717';
const _LAMBDA = '\u03bb';
/// A custom unittest configuration for running the pub tests from the
/// command-line and generating human-friendly output.
@ -70,14 +71,10 @@ class CommandLineConfiguration extends Configuration {
if (stackTrace == null || stackTrace == '') return;
// Parse out each stack entry.
var regexp = new RegExp(r'#\d+\s+(.*) \(file:///([^)]+)\)');
var stack = [];
for (var line in stackTrace.split('\n')) {
if (line.trim() == '') continue;
var match = regexp.firstMatch(line);
if (match == null) throw "Couldn't clean up stack trace line '$line'.";
stack.add(new Pair(match[2], match[1]));
stack.add(new _StackFrame(line));
}
if (stack.length == 0) return;
@ -86,10 +83,12 @@ class CommandLineConfiguration extends Configuration {
var common = 0;
while (true) {
var matching = true;
// TODO(bob): Handle empty stack.
var c = stack[0].first[common];
for (var pair in stack) {
if (pair.first.length <= common || pair.first[common] != c) {
var c;
for (var frame in stack) {
if (frame.isCore) continue;
if (c == null) c = frame.library[common];
if (frame.library.length <= common || frame.library[common] != c) {
matching = false;
break;
}
@ -101,19 +100,18 @@ class CommandLineConfiguration extends Configuration {
// Remove them.
if (common > 0) {
for (var pair in stack) {
pair.first = pair.first.substring(common);
for (var frame in stack) {
if (frame.isCore) continue;
frame.library = frame.library.substring(common);
}
}
// Figure out the longest path so we know how much to pad.
int longest = stack.mappedBy((pair) => pair.first.length).max();
int longest = stack.mappedBy((frame) => frame.location.length).max();
// Print out the stack trace nicely formatted.
for (var pair in stack) {
var path = pair.first;
path = path.replaceFirst(':', ' ');
print(' ${_padLeft(path, longest)} ${pair.last}');
for (var frame in stack) {
print(' ${_padLeft(frame.location, longest)} ${frame.member}');
}
print('');
@ -137,3 +135,44 @@ class CommandLineConfiguration extends Configuration {
return Strings.join(str.split("\n").mappedBy((line) => " $line"), "\n");
}
}
class _StackFrame {
static final fileRegExp = new RegExp(
r'#\d+\s+(.*) \((file:///.+):(\d+):(\d+)\)');
static final coreRegExp = new RegExp(r'#\d+\s+(.*) \((.+):(\d+):(\d+)\)');
/// If `true`, then this stack frame is for a library built into Dart and
/// not a regular file path.
final bool isCore;
/// The path to the library or the library name if a core library.
String library;
/// The line number.
final String line;
/// The column number.
final String column;
/// The member where the error occurred.
final String member;
/// A formatted description of the code location.
String get location => '$library $line:$column';
_StackFrame._(this.isCore, this.library, this.line, this.column, this.member);
factory _StackFrame(String text) {
var match = fileRegExp.firstMatch(text);
var isCore = false;
if (match == null) {
match = coreRegExp.firstMatch(text);
if (match == null) throw "Couldn't parse stack trace line '$text'.";
isCore = true;
}
var member = match[1].replaceAll("<anonymous closure>", _LAMBDA);
return new _StackFrame._(isCore, match[2], match[3], match[4], member);
}
}

View file

@ -5,6 +5,7 @@
library pubspec_test;
import '../../../pkg/unittest/lib/unittest.dart';
import 'test_pub.dart';
import '../../pub/pubspec.dart';
import '../../pub/source.dart';
import '../../pub/source_registry.dart';
@ -112,6 +113,49 @@ dependencies:
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('', sources);
expect(pubspec.environment.sdkVersion, equals(VersionConstraint.any));
});
test("allows an empty environment map", () {
var pubspec = new Pubspec.parse('''
environment:
''', sources);
expect(pubspec.environment.sdkVersion, equals(VersionConstraint.any));
});
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('''
environment:
sdk: ">=1.2.3 <2.3.4"
''', sources);
expect(pubspec.environment.sdkVersion,
equals(new VersionConstraint.parse(">=1.2.3 <2.3.4")));
});
test("throws if the sdk isn't a string", () {
expectFormatError('''
environment:
sdk: []
''');
});
test("throws if the sdk isn't a valid version constraint", () {
expectFormatError('''
environment:
sdk: "oopies"
''');
});
});
});
});
}

View file

@ -495,7 +495,8 @@ class MockSource extends Source {
});
var pubspec = new Pubspec(
description, new Version.parse(version), dependencies);
description, new Version.parse(version), dependencies,
new PubspecEnvironment());
return new Package.inMemory(pubspec);
}

View file

@ -5,6 +5,7 @@
library version_test;
import '../../../pkg/unittest/lib/unittest.dart';
import 'test_pub.dart';
import '../../pub/utils.dart';
import '../../pub/version.dart';
@ -302,8 +303,20 @@ main() {
});
group('VersionConstraint', () {
test('any', () {
expect(VersionConstraint.any.isAny, isTrue);
expect(VersionConstraint.any, allows([
new Version.parse('0.0.0-blah'),
new Version.parse('1.2.3'),
new Version.parse('12345.678.90')]));
});
test('empty', () {
expect(new VersionConstraint.empty().isEmpty, isTrue);
expect(VersionConstraint.empty.isEmpty, isTrue);
expect(VersionConstraint.empty, doesNotAllow([
new Version.parse('0.0.0-blah'),
new Version.parse('1.2.3'),
new Version.parse('12345.678.90')]));
});
group('parse()', () {