Skip to content

Commit 9cfea80

Browse files
authored
Properly handle relative paths from lockfiles (#1754)
We weren't resolving these paths relative to the lockfile location, which caused "pub global run" to fail for some path packages. Closes #1751
1 parent c698e5c commit 9cfea80

File tree

12 files changed

+78
-16
lines changed

12 files changed

+78
-16
lines changed

lib/src/lock_file.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ class LockFile {
154154
var source = sources[sourceName];
155155
var id;
156156
try {
157-
id = source.parseId(name, version, description);
157+
id = source.parseId(name, version, description,
158+
containingPath: filePath);
158159
} on FormatException catch (ex) {
159160
throw new SourceSpanFormatException(
160161
ex.message, spec.nodes['description'].span);

lib/src/source.dart

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,9 @@ abstract class Source {
7676
/// the given [description] is well-formed according to this source, and to
7777
/// give the source a chance to canonicalize the description.
7878
///
79-
/// [containingPath] is the path to the local file (pubspec or lockfile)
80-
/// where this description appears. It may be `null` if the description is
81-
/// coming from some in-memory source (such as pulling down a pubspec from
82-
/// pub.dartlang.org).
79+
/// [containingPath] is the path to the pubspec where this description
80+
/// appears. It may be `null` if the description is coming from some in-memory
81+
/// source (such as pulling down a pubspec from pub.dartlang.org).
8382
///
8483
/// The description in the returned [PackageRef] need bear no resemblance to
8584
/// the original user-provided description.
@@ -92,8 +91,13 @@ abstract class Source {
9291
/// This only accepts descriptions serialized using [serializeDescription]. It
9392
/// should not be used with user-authored descriptions.
9493
///
94+
/// [containingPath] is the path to the lockfile where this description
95+
/// appears. It may be `null` if the description is coming from some in-memory
96+
/// source.
97+
///
9598
/// Throws a [FormatException] if the description is not valid.
96-
PackageId parseId(String name, Version version, description);
99+
PackageId parseId(String name, Version version, description,
100+
{String containingPath});
97101

98102
/// When a [LockFile] is serialized, it uses this method to get the
99103
/// [description] in the right format.

lib/src/source/git.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ class GitSource extends Source {
8181
{"url": description["url"], "ref": ref ?? "HEAD", "path": path ?? "."});
8282
}
8383

84-
PackageId parseId(String name, Version version, description) {
84+
PackageId parseId(String name, Version version, description,
85+
{String containingPath}) {
8586
if (description is! Map) {
8687
throw new FormatException("The description must be a map with a 'url' "
8788
"key.");

lib/src/source/hosted.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ class HostedSource extends Source {
8383
return new PackageRef(name, this, description);
8484
}
8585

86-
PackageId parseId(String name, Version version, description) {
86+
PackageId parseId(String name, Version version, description,
87+
{String containingPath}) {
8788
_parseDescription(description);
8889
return new PackageId(name, this, version, description);
8990
}

lib/src/source/path.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ class PathSource extends Source {
8181
name, this, {"path": description, "relative": isRelative});
8282
}
8383

84-
PackageId parseId(String name, Version version, description) {
84+
PackageId parseId(String name, Version version, description,
85+
{String containingPath}) {
8586
if (description is! Map) {
8687
throw new FormatException("The description must be a map.");
8788
}
@@ -96,6 +97,20 @@ class PathSource extends Source {
9697
"must be a boolean.");
9798
}
9899

100+
// Resolve the path relative to the containing file path.
101+
if (description["relative"]) {
102+
// Relative paths coming from lockfiles that are not on the local file
103+
// system aren't allowed.
104+
if (containingPath == null) {
105+
throw new FormatException('"$description" is a relative path, but this '
106+
'isn\'t a local pubspec.');
107+
}
108+
109+
description = new Map.from(description);
110+
description["path"] =
111+
p.normalize(p.join(p.dirname(containingPath), description["path"]));
112+
}
113+
99114
return new PackageId(name, this, version, description);
100115
}
101116

lib/src/source/sdk.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ class SdkSource extends Source {
3838
return new PackageRef(name, this, description);
3939
}
4040

41-
PackageId parseId(String name, Version version, description) {
41+
PackageId parseId(String name, Version version, description,
42+
{String containingPath}) {
4243
if (description is! String) {
4344
throw new FormatException("The description must be an SDK name.");
4445
}

lib/src/source/unknown.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ class UnknownSource extends Source {
3838
PackageRef parseRef(String name, description, {String containingPath}) =>
3939
new PackageRef(name, this, description);
4040

41-
PackageId parseId(String name, Version version, description) =>
41+
PackageId parseId(String name, Version version, description,
42+
{String containingPath}) =>
4243
new PackageId(name, this, version, description);
4344
}
4445

test/get/path/relative_path_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ main() {
6666
var lockfile = new LockFile.load(lockfilePath, new SourceRegistry());
6767
var description = lockfile.packages["foo"].description;
6868

69-
expect(path.isRelative(description["path"]), isTrue);
69+
expect(description["relative"], isTrue);
70+
expect(description["path"], path.join(d.sandbox, "foo"));
7071
});
7172
}

test/global/activate/path_package_test.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,36 @@ main() {
2222
args: ["global", "activate", "--source", "path", "../foo"],
2323
output: endsWith('Activated foo 1.0.0 at path "$path".'));
2424
});
25+
26+
// Regression test for #1751
27+
test('activates a package at a local path with a relative path dependency',
28+
() async {
29+
await d.dir("foo", [
30+
d.libPubspec("foo", "1.0.0", deps: {
31+
"bar": {"path": "../bar"}
32+
}),
33+
d.dir("bin", [
34+
d.file("foo.dart", """
35+
import 'package:bar/bar.dart';
36+
37+
main() => print(value);
38+
""")
39+
])
40+
]).create();
41+
42+
await d.dir("bar", [
43+
d.libPubspec("bar", "1.0.0"),
44+
d.dir("lib", [d.file("bar.dart", "final value = 'ok';")])
45+
]).create();
46+
47+
var path = canonicalize(p.join(d.sandbox, "foo"));
48+
await runPub(
49+
args: ["global", "activate", "--source", "path", "../foo"],
50+
output: endsWith('Activated foo 1.0.0 at path "$path".'));
51+
52+
await runPub(
53+
args: ["global", "run", "foo"],
54+
output: "ok",
55+
workingDirectory: p.current);
56+
});
2557
}

test/lock_file_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class MockSource extends Source {
2222
return new PackageRef(name, this, description);
2323
}
2424

25-
PackageId parseId(String name, Version version, description) {
25+
PackageId parseId(String name, Version version, description,
26+
{String containingPath}) {
2627
if (!description.endsWith(' desc')) throw new FormatException('Bad');
2728
return new PackageId(name, this, version, description);
2829
}

0 commit comments

Comments
 (0)