| Index: lib/src/entrypoint.dart
|
| diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
|
| index 10c76177c26f5220e1508c10cf69d6f570aa4c15..3c2fa25809eff4062f859eab320c927b99f9cacf 100644
|
| --- a/lib/src/entrypoint.dart
|
| +++ b/lib/src/entrypoint.dart
|
| @@ -5,6 +5,7 @@
|
| library pub.entrypoint;
|
|
|
| import 'dart:async';
|
| +import 'dart:io';
|
|
|
| import 'package:path/path.dart' as path;
|
| import 'package:barback/barback.dart';
|
| @@ -50,41 +51,17 @@ class Entrypoint {
|
| /// the installed packages.
|
| final bool _packageSymlinks;
|
|
|
| - /// The lockfile for the entrypoint.
|
| - ///
|
| - /// If not provided to the entrypoint, it will be laoded lazily from disc.
|
| - LockFile _lockFile;
|
| -
|
| - /// The graph of all packages reachable from the entrypoint.
|
| - PackageGraph _packageGraph;
|
| + /// Whether this entrypoint is in memory only, as opposed to representing a
|
| + /// real directory on disk.
|
| + final bool _inMemory;
|
|
|
| - /// Loads the entrypoint from a package at [rootDir].
|
| + /// The lockfile for the entrypoint.
|
| ///
|
| - /// If [packageSymlinks] is `true`, this will create a "packages" directory
|
| - /// with symlinks to the installed packages. This directory will be symlinked
|
| - /// into any directory that might contain an entrypoint.
|
| - Entrypoint(String rootDir, SystemCache cache, {bool packageSymlinks: true})
|
| - : root = new Package.load(null, rootDir, cache.sources),
|
| - cache = cache,
|
| - _packageSymlinks = packageSymlinks;
|
| -
|
| - /// Creates an entrypoint given package and lockfile objects.
|
| - Entrypoint.inMemory(this.root, this._lockFile, this.cache)
|
| - : _packageSymlinks = false;
|
| -
|
| - /// The path to the entrypoint's "packages" directory.
|
| - String get packagesDir => root.path('packages');
|
| -
|
| - /// The path to the entrypoint's ".packages" file.
|
| - String get packagesFile => root.path('.packages');
|
| -
|
| - /// `true` if the entrypoint package currently has a lock file.
|
| - bool get lockFileExists => _lockFile != null || entryExists(lockFilePath);
|
| -
|
| + /// If not provided to the entrypoint, it will be loaded lazily from disk.
|
| LockFile get lockFile {
|
| if (_lockFile != null) return _lockFile;
|
|
|
| - if (!lockFileExists) {
|
| + if (!fileExists(lockFilePath)) {
|
| _lockFile = new LockFile.empty(cache.sources);
|
| } else {
|
| _lockFile = new LockFile.load(lockFilePath, cache.sources);
|
| @@ -92,6 +69,35 @@ class Entrypoint {
|
|
|
| return _lockFile;
|
| }
|
| + LockFile _lockFile;
|
| +
|
| + /// The package graph for the application and all of its transitive
|
| + /// dependencies.
|
| + ///
|
| + /// Throws a [DataError] if the `.packages` file isn't up-to-date relative to
|
| + /// the pubspec and the lockfile.
|
| + PackageGraph get packageGraph {
|
| + if (_packageGraph != null) return _packageGraph;
|
| +
|
| + assertUpToDate();
|
| + var packages = new Map.fromIterable(lockFile.packages.values,
|
| + key: (id) => id.name,
|
| + value: (id) {
|
| + var dir = cache.sources[id.source].getDirectory(id);
|
| + return new Package.load(id.name, dir, cache.sources);
|
| + });
|
| + packages[root.name] = root;
|
| +
|
| + _packageGraph = new PackageGraph(this, lockFile, packages);
|
| + return _packageGraph;
|
| + }
|
| + PackageGraph _packageGraph;
|
| +
|
| + /// The path to the entrypoint's "packages" directory.
|
| + String get packagesDir => root.path('packages');
|
| +
|
| + /// The path to the entrypoint's ".packages" file.
|
| + String get packagesFile => root.path('.packages');
|
|
|
| /// The path to the entrypoint package's pubspec.
|
| String get pubspecPath => root.path('pubspec.yaml');
|
| @@ -99,6 +105,31 @@ class Entrypoint {
|
| /// The path to the entrypoint package's lockfile.
|
| String get lockFilePath => root.path('pubspec.lock');
|
|
|
| + /// Loads the entrypoint from a package at [rootDir].
|
| + ///
|
| + /// If [packageSymlinks] is `true`, this will create a "packages" directory
|
| + /// with symlinks to the installed packages. This directory will be symlinked
|
| + /// into any directory that might contain an entrypoint.
|
| + Entrypoint(String rootDir, SystemCache cache, {bool packageSymlinks: true})
|
| + : root = new Package.load(null, rootDir, cache.sources),
|
| + cache = cache,
|
| + _packageSymlinks = packageSymlinks,
|
| + _inMemory = false;
|
| +
|
| + /// Creates an entrypoint given package and lockfile objects.
|
| + Entrypoint.inMemory(this.root, this._lockFile, this.cache)
|
| + : _packageSymlinks = false,
|
| + _inMemory = true;
|
| +
|
| + /// Creates an entrypoint given a package and a [solveResult], from which the
|
| + /// package graph and lockfile will be computed.
|
| + Entrypoint.fromSolveResult(this.root, this.cache, SolveResult solveResult)
|
| + : _packageSymlinks = false,
|
| + _inMemory = true {
|
| + _packageGraph = new PackageGraph.fromSolveResult(this, solveResult);
|
| + _lockFile = _packageGraph.lockFile;
|
| + }
|
| +
|
| /// Gets all dependencies of the [root] package.
|
| ///
|
| /// Performs version resolution according to [SolveType].
|
| @@ -113,6 +144,8 @@ class Entrypoint {
|
| /// this is an upgrade or downgrade, all transitive dependencies are shown in
|
| /// the report. Otherwise, only dependencies that were changed are shown. If
|
| /// [dryRun] is `true`, no physical changes are made.
|
| + ///
|
| + /// Updates [lockFile] and [packageRoot] accordingly.
|
| Future acquireDependencies(SolveType type, {List<String> useLatest,
|
| bool dryRun: false}) async {
|
| var result = await resolveVersions(type, cache.sources, root,
|
| @@ -143,7 +176,7 @@ class Entrypoint {
|
|
|
| /// Build a package graph from the version solver results so we don't
|
| /// have to reload and reparse all the pubspecs.
|
| - var packageGraph = await loadPackageGraph(result);
|
| + _packageGraph = new PackageGraph.fromSolveResult(this, result);
|
| packageGraph.loadTransformerCache().clearIfOutdated(result.changedPackages);
|
|
|
| try {
|
| @@ -165,16 +198,15 @@ class Entrypoint {
|
| Future precompileDependencies({Iterable<String> changed}) async {
|
| if (changed != null) changed = changed.toSet();
|
|
|
| - var graph = await loadPackageGraph();
|
| -
|
| // Just precompile the debug version of a package. We're mostly interested
|
| // in improving speed for development iteration loops, which usually use
|
| // debug mode.
|
| var depsDir = path.join('.pub', 'deps', 'debug');
|
|
|
| - var dependenciesToPrecompile = graph.packages.values.where((package) {
|
| + var dependenciesToPrecompile = packageGraph.packages.values
|
| + .where((package) {
|
| if (package.pubspec.transformers.isEmpty) return false;
|
| - if (graph.isPackageMutable(package.name)) return false;
|
| + if (packageGraph.isPackageMutable(package.name)) return false;
|
| if (!dirExists(path.join(depsDir, package.name))) return true;
|
| if (changed == null) return true;
|
|
|
| @@ -182,7 +214,7 @@ class Entrypoint {
|
| /// changed. We check all transitive dependencies because it's possible
|
| /// that a transformer makes decisions based on their contents.
|
| return overlaps(
|
| - graph.transitiveDependencies(package.name)
|
| + packageGraph.transitiveDependencies(package.name)
|
| .map((package) => package.name).toSet(),
|
| changed);
|
| }).map((package) => package.name).toSet();
|
| @@ -195,9 +227,9 @@ class Entrypoint {
|
|
|
| // Also delete any cached dependencies that should no longer be cached.
|
| for (var subdir in listDir(depsDir)) {
|
| - var package = graph.packages[path.basename(subdir)];
|
| + var package = packageGraph.packages[path.basename(subdir)];
|
| if (package == null || package.pubspec.transformers.isEmpty ||
|
| - graph.isPackageMutable(package.name)) {
|
| + packageGraph.isPackageMutable(package.name)) {
|
| deleteEntry(subdir);
|
| }
|
| }
|
| @@ -208,7 +240,8 @@ class Entrypoint {
|
| try {
|
| await log.progress("Precompiling dependencies", () async {
|
| var packagesToLoad =
|
| - unionAll(dependenciesToPrecompile.map(graph.transitiveDependencies))
|
| + unionAll(dependenciesToPrecompile.map(
|
| + packageGraph.transitiveDependencies))
|
| .map((package) => package.name).toSet();
|
|
|
| var environment = await AssetEnvironment.create(this, BarbackMode.DEBUG,
|
| @@ -259,16 +292,14 @@ class Entrypoint {
|
| readTextFile(sdkVersionPath) == "${sdk.version}\n";
|
| if (!sdkMatches) changed = null;
|
|
|
| - var graph = await loadPackageGraph();
|
| -
|
| // Clean out any outdated snapshots.
|
| if (dirExists(binDir)) {
|
| for (var entry in listDir(binDir)) {
|
| if (!dirExists(entry)) continue;
|
|
|
| var package = path.basename(entry);
|
| - if (!graph.packages.containsKey(package) ||
|
| - graph.isPackageMutable(package)) {
|
| + if (!packageGraph.packages.containsKey(package) ||
|
| + packageGraph.isPackageMutable(package)) {
|
| deleteEntry(entry);
|
| }
|
| }
|
| @@ -276,7 +307,7 @@ class Entrypoint {
|
|
|
| var executables = new Map.fromIterable(root.immediateDependencies,
|
| key: (dep) => dep.name,
|
| - value: (dep) => _executablesForPackage(graph, dep.name, changed));
|
| + value: (dep) => _executablesForPackage(dep.name, changed));
|
|
|
| for (var package in executables.keys.toList()) {
|
| if (executables[package].isEmpty) executables.remove(package);
|
| @@ -293,7 +324,7 @@ class Entrypoint {
|
| writeTextFile(sdkVersionPath, "${sdk.version}\n");
|
|
|
| var packagesToLoad =
|
| - unionAll(executables.keys.map(graph.transitiveDependencies))
|
| + unionAll(executables.keys.map(packageGraph.transitiveDependencies))
|
| .map((package) => package.name).toSet();
|
| var executableIds = unionAll(
|
| executables.values.map((ids) => ids.toSet()));
|
| @@ -319,12 +350,12 @@ class Entrypoint {
|
| ///
|
| /// If [changed] isn't `null`, executables for [packageName] will only be
|
| /// compiled if they might depend on a package in [changed].
|
| - List<AssetId> _executablesForPackage(PackageGraph graph, String packageName,
|
| + List<AssetId> _executablesForPackage(String packageName,
|
| Set<String> changed) {
|
| - var package = graph.packages[packageName];
|
| + var package = packageGraph.packages[packageName];
|
| var binDir = package.path('bin');
|
| if (!dirExists(binDir)) return [];
|
| - if (graph.isPackageMutable(packageName)) return [];
|
| + if (packageGraph.isPackageMutable(packageName)) return [];
|
|
|
| var executables = package.executableIds;
|
|
|
| @@ -333,7 +364,7 @@ class Entrypoint {
|
| if (changed == null) return executables;
|
|
|
| // If any of the package's dependencies changed, recompile the executables.
|
| - if (graph.transitiveDependencies(packageName)
|
| + if (packageGraph.transitiveDependencies(packageName)
|
| .any((package) => changed.contains(package.name))) {
|
| return executables;
|
| }
|
| @@ -371,119 +402,31 @@ class Entrypoint {
|
| }).then((_) => source.resolveId(id));
|
| }
|
|
|
| - /// Determines whether or not the lockfile is out of date with respect to the
|
| - /// pubspec.
|
| - ///
|
| - /// This will be `false` if there is no lockfile at all, or if the pubspec
|
| - /// contains dependencies that are not in the lockfile or that don't match
|
| - /// what's in there.
|
| - bool _isLockFileUpToDate(LockFile lockFile) {
|
| - /// If this is an entrypoint for an in-memory package, trust the in-memory
|
| - /// lockfile provided for it.
|
| - if (root.dir == null) return true;
|
| -
|
| - return root.immediateDependencies.every((package) {
|
| - var locked = lockFile.packages[package.name];
|
| - if (locked == null) return false;
|
| -
|
| - if (package.source != locked.source) return false;
|
| - if (!package.constraint.allows(locked.version)) return false;
|
| -
|
| - var source = cache.sources[package.source];
|
| - if (source == null) return false;
|
| + /// Throws a [DataError] if the `.packages` file doesn't exist or if it's
|
| + /// out-of-date relative to the lockfile or the pubspec.
|
| + void assertUpToDate() {
|
| + if (_inMemory) return;
|
|
|
| - return source.descriptionsEqual(package.description, locked.description);
|
| - });
|
| - }
|
| -
|
| - /// Determines whether all of the packages in the lockfile are already
|
| - /// installed and available.
|
| - ///
|
| - /// Note: this assumes [isLockFileUpToDate] has already been called and
|
| - /// returned `true`.
|
| - bool _arePackagesAvailable(LockFile lockFile) {
|
| - return lockFile.packages.values.every((package) {
|
| - var source = cache.sources[package.source];
|
| -
|
| - // This should only be called after [_isLockFileUpToDate] has returned
|
| - // `true`, which ensures all of the sources in the lock file are valid.
|
| - assert(source != null);
|
| -
|
| - // We only care about cached sources. Uncached sources aren't "installed".
|
| - // If one of those is missing, we want to show the user the file not
|
| - // found error later since installing won't accomplish anything.
|
| - if (source is! CachedSource) return true;
|
| -
|
| - // Get the directory.
|
| - var dir = source.getDirectory(package);
|
| - // See if the directory is there and looks like a package.
|
| - return dirExists(dir) || fileExists(path.join(dir, "pubspec.yaml"));
|
| - });
|
| - }
|
| -
|
| - /// Gets dependencies if the lockfile is out of date with respect to the
|
| - /// pubspec.
|
| - Future ensureLockFileIsUpToDate() async {
|
| - if (!lockFileExists) {
|
| - log.message(
|
| - "You don't have a lockfile, so we need to generate that:");
|
| - } else if (_isLockFileUpToDate(lockFile)) {
|
| - // If we do have a lock file, we still need to make sure the packages are
|
| - // actually installed. The user may have just gotten a package that
|
| - // includes a lockfile.
|
| - if (_arePackagesAvailable(lockFile)) return;
|
| -
|
| - // If we don't have a current lock file, we definitely need to install.
|
| - log.message(
|
| - "You are missing some dependencies, so we need to install them "
|
| - "first:");
|
| - } else {
|
| - log.message(
|
| - "Your pubspec has changed, so we need to update your lockfile:");
|
| + if (!entryExists(lockFilePath)) {
|
| + dataError('No pubspec.lock file found, please run "pub get" first.');
|
| }
|
|
|
| - await acquireDependencies(SolveType.GET);
|
| - }
|
| -
|
| - /// Loads the package graph for the application and all of its transitive
|
| - /// dependencies.
|
| - ///
|
| - /// If [result] is passed, this loads the graph from it without re-parsing the
|
| - /// lockfile or any pubspecs. Otherwise, before loading, this makes sure the
|
| - /// lockfile and dependencies are installed and up to date.
|
| - Future<PackageGraph> loadPackageGraph([SolveResult result]) async {
|
| - if (_packageGraph != null) return _packageGraph;
|
| -
|
| - var graph = await log.progress("Loading package graph", () async {
|
| - if (result != null) {
|
| - var packages = new Map.fromIterable(result.packages,
|
| - key: (id) => id.name,
|
| - value: (id) {
|
| - if (id.name == root.name) return root;
|
| -
|
| - return new Package(result.pubspecs[id.name],
|
| - cache.sources[id.source].getDirectory(id));
|
| - });
|
| -
|
| - return new PackageGraph(
|
| - this,
|
| - new LockFile(result.packages, cache.sources),
|
| - packages);
|
| - }
|
| + if (!entryExists(packagesFile)) {
|
| + dataError('No .packages file found, please run "pub get" first.');
|
| + }
|
|
|
| - await ensureLockFileIsUpToDate();
|
| - var packages = new Map.fromIterable(lockFile.packages.values,
|
| - key: (id) => id.name,
|
| - value: (id) {
|
| - var dir = cache.sources[id.source].getDirectory(id);
|
| - return new Package.load(id.name, dir, cache.sources);
|
| - });
|
| - packages[root.name] = root;
|
| - return new PackageGraph(this, lockFile, packages);
|
| - }, fine: true);
|
| + var packagesModified = new File(packagesFile).lastModifiedSync();
|
| + var pubspecModified = new File(pubspecPath).lastModifiedSync();
|
| + if (packagesModified.isBefore(pubspecModified)) {
|
| + dataError('The pubspec.yaml file has changed since the .packages file '
|
| + 'was generated, please run "pub get" again.');
|
| + }
|
|
|
| - _packageGraph = graph;
|
| - return graph;
|
| + var lockFileModified = new File(lockFilePath).lastModifiedSync();
|
| + if (packagesModified.isBefore(lockFileModified)) {
|
| + dataError('The pubspec.lock file has changed since the .packages file '
|
| + 'was generated, please run "pub get" again.');
|
| + }
|
| }
|
|
|
| /// Saves a list of concrete package versions to the `pubspec.lock` file.
|
|
|