blob: eb4864f0fc8f75b6f0d796cce2a631748d929a2f [file] [log] [blame]
// 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.global_packages;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'io.dart';
import 'lock_file.dart';
import 'log.dart' as log;
import 'package.dart';
import 'system_cache.dart';
import 'solver/version_solver.dart';
import 'source/cached.dart';
import 'utils.dart';
import 'version.dart';
/// Maintains the set of packages that have been globally activated.
///
/// These have been hand-chosen by the user to make their executables in bin/
/// available to the entire system. This lets them access them even when the
/// current working directory is not inside another entrypoint package.
///
/// Only one version of a given package name can be globally activated at a
/// time. Activating a different version of a package will deactivate the
/// previous one.
class GlobalPackages {
/// The [SystemCache] containing the global packages.
final SystemCache cache;
/// The directory where the lockfiles for activated packages are stored.
String get _directory => p.join(cache.rootDir, "global_packages");
/// The source that global packages can be activated from.
// TODO(rnystrom): Allow activating packages from other sources.
CachedSource get _source => cache.sources["hosted"] as CachedSource;
/// Creates a new global package registry backed by the given directory on
/// the user's file system.
///
/// The directory may not physically exist yet. If not, this will create it
/// when needed.
GlobalPackages(this.cache);
/// 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);
currentVersion = lockFile.packages[name].version;
// Pull the root package out of the lock file so the solver doesn't see
// it.
lockFile.packages.remove(name);
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.
lockFile = new LockFile.empty();
}
var package;
var id;
return _selectVersion(name, currentVersion, constraint).then((version) {
// Make sure it's in the cache.
id = new PackageId(name, _source.name, version, name);
return _source.downloadToSystemCache(id);
}).then((p) {
package = p;
// Resolve it and download its dependencies.
return resolveVersions(cache.sources, package, lockFile: lockFile);
}).then((result) {
if (!result.succeeded) throw result.error;
result.showReport();
// Make sure all of the dependencies are locally installed.
return Future.wait(result.packages.map((id) {
var source = cache.sources[id.source];
if (source is! CachedSource) return new Future.value();
return source.downloadToSystemCache(id)
.then((_) => source.resolveId(id));
}));
}).then((ids) {
var lockFile = new LockFile(ids);
// Add the root package itself to the lockfile.
lockFile.packages[name] = id;
ensureDir(_directory);
writeTextFile(lockFilePath,
lockFile.serialize(cache.rootDir, cache.sources));
log.message("Activated ${log.bold(package.name)} ${package.version}.");
// TODO(rnystrom): Look in "bin" and display list of binaries that
// user can run.
});
}
/// Deactivates a previously-activated package named [name] or fails with
/// an error if [name] is not an active package.
void deactivate(String name) {
// See if we already have it activated.
try {
var lockFilePath = p.join(_directory, "$name.lock");
var lockFile = new LockFile.load(lockFilePath, cache.sources);
var version = lockFile.packages[name].version;
deleteEntry(lockFilePath);
log.message("Deactivated package ${log.bold(name)} $version.");
} on IOException catch (error) {
dataError("No active package ${log.bold(name)}.");
}
}
/// Picks the best version of [package] to activate that meets [constraint].
///
/// If [version] is not `null`, this tries to maintain that version if
/// possible.
Future<Version> _selectVersion(String package, Version version,
VersionConstraint constraint) {
// If we already have a valid active version, just use it.
if (version != null && constraint.allows(version)) {
return new Future.value(version);
}
// Otherwise, select the best version the matches the constraint.
return _source.getVersions(package, package).then((versions) {
versions = versions.where(constraint.allows).toList();
if (versions.isEmpty) {
// TODO(rnystrom): Show most recent unmatching version?
dataError("Package ${log.bold(package)} has no versions that match "
"$constraint.");
}
// Pick the best matching version.
versions.sort(Version.prioritize);
return versions.last;
});
}
}