blob: 2af07292e48972aefda7ff88bcd681d7e13471c0 [file] [log] [blame]
// Copyright (c) 2015, 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.
/// Analyse a directory structure and find packages resolvers for each
/// sub-directory.
///
/// The resolvers are generally the same that would be found by using
/// the `discovery.dart` library on each sub-directory in turn,
/// but more efficiently and with some heuristics for directories that
/// wouldn't otherwise have a package resolution strategy, or that are
/// determined to be "package directories" themselves.
@Deprecated("Use the package_config.json based API")
library package_config.discovery_analysis;
import "dart:collection" show HashMap;
import "dart:io" show File, Directory;
import "package:path/path.dart" as path;
import "packages.dart";
import "packages_file.dart" as pkgfile;
import "src/packages_impl.dart";
import "src/packages_io_impl.dart";
/// Associates a [Packages] package resolution strategy with a directory.
///
/// The package resolution applies to the directory and any sub-directory
/// that doesn't have its own overriding child [PackageContext].
abstract class PackageContext {
/// The directory that introduced the [packages] resolver.
Directory get directory;
/// A [Packages] resolver that applies to the directory.
///
/// Introduced either by a `.packages` file or a `packages/` directory.
Packages get packages;
/// Child contexts that apply to sub-directories of [directory].
List<PackageContext> get children;
/// Look up the [PackageContext] that applies to a specific directory.
///
/// The directory must be inside [directory].
PackageContext operator [](Directory directory);
/// A map from directory to package resolver.
///
/// Has an entry for this package context and for each child context
/// contained in this one.
Map<Directory, Packages> asMap();
/// Analyze [directory] and sub-directories for package resolution strategies.
///
/// Returns a mapping from sub-directories to [Packages] objects.
///
/// The analysis assumes that there are no `.packages` files in a parent
/// directory of `directory`. If there is, its corresponding `Packages` object
/// should be provided as `root`.
static PackageContext findAll(Directory directory,
{Packages root = Packages.noPackages}) {
if (!directory.existsSync()) {
throw ArgumentError("Directory not found: $directory");
}
var contexts = <PackageContext>[];
void findRoots(Directory directory) {
Packages packages;
List<PackageContext> oldContexts;
var packagesFile = File(path.join(directory.path, ".packages"));
if (packagesFile.existsSync()) {
packages = _loadPackagesFile(packagesFile);
oldContexts = contexts;
contexts = [];
} else {
var packagesDir = Directory(path.join(directory.path, "packages"));
if (packagesDir.existsSync()) {
packages = FilePackagesDirectoryPackages(packagesDir);
oldContexts = contexts;
contexts = [];
}
}
for (var entry in directory.listSync()) {
if (entry is Directory) {
if (packages == null || !entry.path.endsWith("/packages")) {
findRoots(entry);
}
}
}
if (packages != null) {
oldContexts.add(_PackageContext(directory, packages, contexts));
contexts = oldContexts;
}
}
findRoots(directory);
// If the root is not itself context root, add a the wrapper context.
if (contexts.length == 1 && contexts[0].directory == directory) {
return contexts[0];
}
return _PackageContext(directory, root, contexts);
}
}
class _PackageContext implements PackageContext {
final Directory directory;
final Packages packages;
final List<PackageContext> children;
_PackageContext(this.directory, this.packages, List<PackageContext> children)
: children = List<PackageContext>.unmodifiable(children);
Map<Directory, Packages> asMap() {
var result = HashMap<Directory, Packages>();
void recurse(_PackageContext current) {
result[current.directory] = current.packages;
for (var child in current.children) {
recurse(child);
}
}
recurse(this);
return result;
}
PackageContext operator [](Directory directory) {
var path = directory.path;
if (!path.startsWith(this.directory.path)) {
throw ArgumentError("Not inside $path: $directory");
}
var current = this;
// The current path is know to agree with directory until deltaIndex.
var deltaIndex = current.directory.path.length;
List children = current.children;
var i = 0;
while (i < children.length) {
// TODO(lrn): Sort children and use binary search.
_PackageContext child = children[i];
var childPath = child.directory.path;
if (_stringsAgree(path, childPath, deltaIndex, childPath.length)) {
deltaIndex = childPath.length;
if (deltaIndex == path.length) {
return child;
}
current = child;
children = current.children;
i = 0;
continue;
}
i++;
}
return current;
}
static bool _stringsAgree(String a, String b, int start, int end) {
if (a.length < end || b.length < end) return false;
for (var i = start; i < end; i++) {
if (a.codeUnitAt(i) != b.codeUnitAt(i)) return false;
}
return true;
}
}
Packages _loadPackagesFile(File file) {
var uri = Uri.file(file.path);
var bytes = file.readAsBytesSync();
var map = pkgfile.parse(bytes, uri);
return MapPackages(map);
}