Support removing dead packages with pub install.

Review URL: https://chromiumcodereview.appspot.com//10867070

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@11356 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
nweiz@google.com 2012-08-25 00:18:48 +00:00
parent af2d533d41
commit 89b1f5ec12
5 changed files with 274 additions and 5 deletions

View file

@ -143,10 +143,12 @@ class Entrypoint {
* [LockFile].
*/
Future _installDependencies(List<PackageId> packageVersions) {
return Futures.wait(packageVersions.map((id) {
if (id.source is RootSource) return new Future.immediate(id);
return install(id);
})).chain(_saveLockFile).chain(_installSelfReference);
return _removeUnusedDependencies(packageVersions).chain((_) {
return Futures.wait(packageVersions.map((id) {
if (id.source is RootSource) return new Future.immediate(id);
return install(id);
}));
}).chain(_saveLockFile).chain(_installSelfReference);
}
/**
@ -179,6 +181,27 @@ class Entrypoint {
return completer.future;
}
/**
* Removes all dependencies that are no longer depended on from the `packages`
* directory. [packageIds] is a list of all packages that are still depended
* on.
*/
Future _removeUnusedDependencies(List<PackageId> packageIds) {
var dependenciesToKeep = packageIds.map((id) => id.name);
return dirExists(path).chain((exists) {
if (exists) return listDir(path);
return new Future.immediate([]);
}).chain((existingDependencies) {
existingDependencies = existingDependencies.map(basename);
var dependenciesToRemove =
new List.from(setMinus(existingDependencies, dependenciesToKeep));
return Futures.wait(dependenciesToRemove.map((dependency) {
return deleteDir(join(path, dependency));
}));
});
}
/**
* Saves a list of concrete package versions to the `pubspec.lock` file.
*/

View file

@ -80,6 +80,16 @@ only(Iterable iter) {
return obj;
}
/**
* Returns a set containing all elements in [minuend] that are not in
* [subtrahend].
*/
Set setMinus(Collection minuend, Collection subtrahend) {
var minuendSet = new Set.from(minuend);
minuendSet.removeAll(subtrahend);
return minuendSet;
}
/**
* Replace each instance of [matcher] in [source] with the return value of [fn].
*/

View file

@ -229,4 +229,104 @@ main() {
run();
});
test("removes a dependency that's been removed from the pubspec", () {
servePackages([
package("foo", "1.0.0"),
package("bar", "1.0.0")
]);
appDir([dependency("foo"), dependency("bar")]).scheduleCreate();
schedulePub(args: ['install'],
output: const RegExp(@"Dependencies installed!$"));
packagesDir({
"foo": "1.0.0",
"bar": "1.0.0"
}).scheduleValidate();
appDir([dependency("foo")]).scheduleCreate();
schedulePub(args: ['install'],
output: const RegExp(@"Dependencies installed!$"));
packagesDir({
"foo": "1.0.0",
"bar": null
}).scheduleValidate();
run();
});
test("removes a transitive dependency that's no longer depended on", () {
servePackages([
package("foo", "1.0.0", [dependency("shared-dep")]),
package("bar", "1.0.0", [
dependency("shared-dep"),
dependency("bar-dep")
]),
package("shared-dep", "1.0.0"),
package("bar-dep", "1.0.0")
]);
appDir([dependency("foo"), dependency("bar")]).scheduleCreate();
schedulePub(args: ['install'],
output: const RegExp(@"Dependencies installed!$"));
packagesDir({
"foo": "1.0.0",
"bar": "1.0.0",
"shared-dep": "1.0.0",
"bar-dep": "1.0.0",
}).scheduleValidate();
appDir([dependency("foo")]).scheduleCreate();
schedulePub(args: ['install'],
output: const RegExp(@"Dependencies installed!$"));
packagesDir({
"foo": "1.0.0",
"bar": null,
"shared-dep": "1.0.0",
"bar-dep": null,
}).scheduleValidate();
run();
});
test("doesn't update dependencies whose constraints have been removed", () {
servePackages([
package("foo", "1.0.0", [dependency("shared-dep")]),
package("bar", "1.0.0", [dependency("shared-dep", "<2.0.0")]),
package("shared-dep", "1.0.0"),
package("shared-dep", "2.0.0")
]);
appDir([dependency("foo"), dependency("bar")]).scheduleCreate();
schedulePub(args: ['install'],
output: const RegExp(@"Dependencies installed!$"));
packagesDir({
"foo": "1.0.0",
"bar": "1.0.0",
"shared-dep": "1.0.0"
}).scheduleValidate();
appDir([dependency("foo")]).scheduleCreate();
schedulePub(args: ['install'],
output: const RegExp(@"Dependencies installed!$"));
packagesDir({
"foo": "1.0.0",
"bar": null,
"shared-dep": "1.0.0"
}).scheduleValidate();
run();
});
}

View file

@ -75,4 +75,104 @@ main() {
run();
});
test("removes a dependency that's been removed from the pubspec", () {
servePackages([
package("foo", "1.0.0"),
package("bar", "1.0.0")
]);
appDir([dependency("foo"), dependency("bar")]).scheduleCreate();
schedulePub(args: ['update'],
output: const RegExp(@"Dependencies updated!$"));
packagesDir({
"foo": "1.0.0",
"bar": "1.0.0"
}).scheduleValidate();
appDir([dependency("foo")]).scheduleCreate();
schedulePub(args: ['update'],
output: const RegExp(@"Dependencies updated!$"));
packagesDir({
"foo": "1.0.0",
"bar": null
}).scheduleValidate();
run();
});
test("removes a transitive dependency that's no longer depended on", () {
servePackages([
package("foo", "1.0.0", [dependency("shared-dep")]),
package("bar", "1.0.0", [
dependency("shared-dep"),
dependency("bar-dep")
]),
package("shared-dep", "1.0.0"),
package("bar-dep", "1.0.0")
]);
appDir([dependency("foo"), dependency("bar")]).scheduleCreate();
schedulePub(args: ['update'],
output: const RegExp(@"Dependencies updated!$"));
packagesDir({
"foo": "1.0.0",
"bar": "1.0.0",
"shared-dep": "1.0.0",
"bar-dep": "1.0.0",
}).scheduleValidate();
appDir([dependency("foo")]).scheduleCreate();
schedulePub(args: ['update'],
output: const RegExp(@"Dependencies updated!$"));
packagesDir({
"foo": "1.0.0",
"bar": null,
"shared-dep": "1.0.0",
"bar-dep": null,
}).scheduleValidate();
run();
});
test("updates dependencies whose constraints have been removed", () {
servePackages([
package("foo", "1.0.0", [dependency("shared-dep")]),
package("bar", "1.0.0", [dependency("shared-dep", "<2.0.0")]),
package("shared-dep", "1.0.0"),
package("shared-dep", "2.0.0")
]);
appDir([dependency("foo"), dependency("bar")]).scheduleCreate();
schedulePub(args: ['update'],
output: const RegExp(@"Dependencies updated!$"));
packagesDir({
"foo": "1.0.0",
"bar": "1.0.0",
"shared-dep": "1.0.0"
}).scheduleValidate();
appDir([dependency("foo")]).scheduleCreate();
schedulePub(args: ['update'],
output: const RegExp(@"Dependencies updated!$"));
packagesDir({
"foo": "1.0.0",
"bar": null,
"shared-dep": "2.0.0"
}).scheduleValidate();
run();
});
}

View file

@ -55,6 +55,11 @@ GitRepoDescriptor git(Pattern name, [List<Descriptor> contents]) =>
TarFileDescriptor tar(Pattern name, [List<Descriptor> contents]) =>
new TarFileDescriptor(name, contents);
/**
* Creates a new [NothingDescriptor] with [name].
*/
NothingDescriptor nothing(String name) => new NothingDescriptor(name);
/**
* The current [HttpServer] created using [serve].
*/
@ -301,11 +306,17 @@ DirectoryDescriptor gitPackageCacheDir(String name, [int modifier]) {
* Describes the `packages/` directory containing all the given [packages],
* which should be name/version pairs. The packages will be validated against
* the format produced by the mock package server.
*
* A package with a null version should not be installed.
*/
DirectoryDescriptor packagesDir(Map<String, String> packages) {
var contents = <Descriptor>[];
packages.forEach((name, version) {
contents.add(packageDir(name, version));
if (version == null) {
contents.add(nothing(name));
} else {
contents.add(packageDir(name, version));
}
});
return dir(packagesPath, contents);
}
@ -1021,6 +1032,31 @@ class TarFileDescriptor extends Descriptor {
}
}
/**
* A descriptor that validates that no file exists with the given name.
*/
class NothingDescriptor extends Descriptor {
NothingDescriptor(String name) : super(name);
Future create(dir) => new Future.immediate(null);
Future delete(dir) => new Future.immediate(null);
Future validate(String dir) {
return exists(join(dir, name)).transform((exists) {
if (exists) Expect.fail('File $name in $dir should not exist.');
});
}
InputStream load(List<String> path) {
if (path.isEmpty()) {
throw "Can't load the contents of $name: it doesn't exist.";
} else {
throw "Can't load ${Strings.join(path, '/')} from within $name: $name "
"doesn't exist.";
}
}
}
/**
* Takes a simple data structure (composed of [Map]s, [List]s, scalar objects,
* and [Future]s) and recursively resolves all the [Future]s contained within.