b3606c9047
Review URL: https://codereview.chromium.org//12171002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@18020 260f80e4-7a28-3924-810f-c04153c831b5
173 lines
7.0 KiB
Dart
173 lines
7.0 KiB
Dart
// Copyright (c) 2012, 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 source;
|
|
|
|
import 'dart:async';
|
|
import 'io.dart';
|
|
import 'package.dart';
|
|
import 'pubspec.dart';
|
|
import 'system_cache.dart';
|
|
import 'utils.dart';
|
|
import 'version.dart';
|
|
|
|
/// A source from which to install packages.
|
|
///
|
|
/// Each source has many packages that it looks up using [PackageId]s. The
|
|
/// source is responsible for installing these packages to the package cache.
|
|
abstract class Source {
|
|
/// The name of the source. Should be lower-case, suitable for use in a
|
|
/// filename, and unique accross all sources.
|
|
String get name;
|
|
|
|
/// Whether or not this source is the default source.
|
|
bool get isDefault => systemCache.sources.defaultSource == this;
|
|
|
|
/// Whether this source's packages should be cached in Pub's global cache
|
|
/// directory.
|
|
///
|
|
/// A source should be cached if it requires network access to retrieve
|
|
/// packages. It doesn't need to be cached if all packages are available
|
|
/// locally.
|
|
bool get shouldCache;
|
|
|
|
/// The system cache with which this source is registered.
|
|
SystemCache get systemCache {
|
|
assert(_systemCache != null);
|
|
return _systemCache;
|
|
}
|
|
|
|
/// The system cache variable. Set by [_bind].
|
|
SystemCache _systemCache;
|
|
|
|
/// The root directory of this source's cache within the system cache.
|
|
///
|
|
/// This shouldn't be overridden by subclasses.
|
|
String get systemCacheRoot => join(systemCache.rootDir, name);
|
|
|
|
/// Records the system cache to which this source belongs.
|
|
///
|
|
/// This should only be called once for each source, by
|
|
/// [SystemCache.register]. It should not be overridden by base classes.
|
|
void bind(SystemCache systemCache) {
|
|
assert(_systemCache == null);
|
|
this._systemCache = systemCache;
|
|
}
|
|
|
|
/// Get the list of all versions that exist for the package described by
|
|
/// [description]. [name] is the expected name of the package.
|
|
///
|
|
/// Note that this does *not* require the packages to be installed, which is
|
|
/// the point. This is used during version resolution to determine which
|
|
/// package versions are available to be installed (or already installed).
|
|
///
|
|
/// By default, this assumes that each description has a single version and
|
|
/// uses [describe] to get that version.
|
|
Future<List<Version>> getVersions(String name, description) {
|
|
return describe(new PackageId(name, this, Version.none, description))
|
|
.then((pubspec) => [pubspec.version]);
|
|
}
|
|
|
|
/// Loads the (possibly remote) pubspec for the package version identified by
|
|
/// [id]. This may be called for packages that have not yet been installed
|
|
/// during the version resolution process.
|
|
///
|
|
/// For cached sources, by default this uses [installToSystemCache] to get the
|
|
/// pubspec. There is no default implementation for non-cached sources; they
|
|
/// must implement it manually.
|
|
Future<Pubspec> describe(PackageId id) {
|
|
if (!shouldCache) throw "Source $name must implement describe(id).";
|
|
return installToSystemCache(id).then((package) => package.pubspec);
|
|
}
|
|
|
|
/// Installs the package identified by [id] to [path]. Returns a [Future] that
|
|
/// completes when the installation was finished. The [Future] should resolve
|
|
/// to true if the package was found in the source and false if it wasn't. For
|
|
/// all other error conditions, it should complete with an exception.
|
|
///
|
|
/// [path] is guaranteed not to exist, and its parent directory is guaranteed
|
|
/// to exist.
|
|
///
|
|
/// Note that [path] may be deleted. If re-installing a package that has
|
|
/// already been installed would be costly or impossible,
|
|
/// [installToSystemCache] should be implemented instead of [install].
|
|
///
|
|
/// This doesn't need to be implemented if [installToSystemCache] is
|
|
/// implemented.
|
|
Future<bool> install(PackageId id, String path) {
|
|
throw "Either install or installToSystemCache must be implemented for "
|
|
"source $name.";
|
|
}
|
|
|
|
/// Installs the package identified by [id] to the system cache. This is only
|
|
/// called for sources with [shouldCache] set to true.
|
|
///
|
|
/// By default, this uses [systemCacheDirectory] and [install].
|
|
Future<Package> installToSystemCache(PackageId id) {
|
|
var path;
|
|
return systemCacheDirectory(id).then((p) {
|
|
path = p;
|
|
if (dirExists(path)) return true;
|
|
ensureDir(dirname(path));
|
|
return install(id, path);
|
|
}).then((found) {
|
|
if (!found) throw 'Package $id not found.';
|
|
return new Package.load(id.name, path, systemCache.sources);
|
|
});
|
|
}
|
|
|
|
/// Returns the directory in the system cache that the package identified by
|
|
/// [id] should be installed to. This should return a path to a subdirectory
|
|
/// of [systemCacheRoot].
|
|
///
|
|
/// This doesn't need to be implemented if [shouldCache] is false.
|
|
Future<String> systemCacheDirectory(PackageId id) {
|
|
return new Future.immediateError(
|
|
"systemCacheDirectory() must be implemented if shouldCache is true.");
|
|
}
|
|
|
|
/// When a [Pubspec] or [LockFile] is parsed, it reads in the description for
|
|
/// each dependency. It is up to the dependency's [Source] to determine how
|
|
/// that should be interpreted. This will be called during parsing to validate
|
|
/// that the given [description] is well-formed according to this source. It
|
|
/// should return if the description is valid, or throw a [FormatException] if
|
|
/// not.
|
|
///
|
|
/// [fromLockFile] is true when the description comes from a [LockFile], to
|
|
/// allow the source to use lockfile-specific descriptions via [resolveId].
|
|
void validateDescription(description, {bool fromLockFile: false}) {}
|
|
|
|
/// Returns whether or not [description1] describes the same package as
|
|
/// [description2] for this source. This method should be light-weight. It
|
|
/// doesn't need to validate that either package exists.
|
|
///
|
|
/// By default, just uses regular equality.
|
|
bool descriptionsEqual(description1, description2) =>
|
|
description1 == description2;
|
|
|
|
/// For some sources, [PackageId]s can point to different chunks of code at
|
|
/// different times. This takes such an [id] and returns a future that
|
|
/// completes to a [PackageId] that will uniquely specify a single chunk of
|
|
/// code forever.
|
|
///
|
|
/// For example, [GitSource] might take an [id] with description
|
|
/// `http://github.com/dart-lang/some-lib.git` and return an id with a
|
|
/// description that includes the current commit of the Git repository.
|
|
///
|
|
/// This will be called after the package identified by [id] is installed, so
|
|
/// the source can use the installed package to determine information about
|
|
/// the resolved id.
|
|
///
|
|
/// The returned [PackageId] may have a description field that's invalid
|
|
/// according to [validateDescription], although it must still be serializable
|
|
/// to JSON and YAML. It must also be equal to [id] according to
|
|
/// [descriptionsEqual].
|
|
///
|
|
/// By default, this just returns [id].
|
|
Future<PackageId> resolveId(PackageId id) => new Future.immediate(id);
|
|
|
|
/// Returns the source's name.
|
|
String toString() => name;
|
|
}
|