Index: sdk/lib/_internal/pub/lib/src/entrypoint.dart |
diff --git a/sdk/lib/_internal/pub/lib/src/entrypoint.dart b/sdk/lib/_internal/pub/lib/src/entrypoint.dart |
deleted file mode 100644 |
index e97f688e5b66d96da849a773965c4ecbc53be991..0000000000000000000000000000000000000000 |
--- a/sdk/lib/_internal/pub/lib/src/entrypoint.dart |
+++ /dev/null |
@@ -1,564 +0,0 @@ |
-// 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 pub.entrypoint; |
- |
-import 'dart:async'; |
- |
-import 'package:path/path.dart' as path; |
-import 'package:barback/barback.dart'; |
- |
-import 'barback/asset_environment.dart'; |
-import 'io.dart'; |
-import 'lock_file.dart'; |
-import 'log.dart' as log; |
-import 'package.dart'; |
-import 'package_graph.dart'; |
-import 'sdk.dart' as sdk; |
-import 'solver/version_solver.dart'; |
-import 'source/cached.dart'; |
-import 'system_cache.dart'; |
-import 'utils.dart'; |
- |
-/// The context surrounding the root package pub is operating on. |
-/// |
-/// Pub operates over a directed graph of dependencies that starts at a root |
-/// "entrypoint" package. This is typically the package where the current |
-/// working directory is located. An entrypoint knows the [root] package it is |
-/// associated with and is responsible for managing the "packages" directory |
-/// for it. |
-/// |
-/// That directory contains symlinks to all packages used by an app. These links |
-/// point either to the [SystemCache] or to some other location on the local |
-/// filesystem. |
-/// |
-/// While entrypoints are typically applications, a pure library package may end |
-/// up being used as an entrypoint. Also, a single package may be used as an |
-/// entrypoint in one context but not in another. For example, a package that |
-/// contains a reusable library may not be the entrypoint when used by an app, |
-/// but may be the entrypoint when you're running its tests. |
-class Entrypoint { |
- /// The root package this entrypoint is associated with. |
- final Package root; |
- |
- /// The system-wide cache which caches packages that need to be fetched over |
- /// the network. |
- final SystemCache cache; |
- |
- /// Whether to create and symlink a "packages" directory containing links to |
- /// 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; |
- |
- /// 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; |
- |
- /// 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'); |
- |
- /// `true` if the entrypoint package currently has a lock file. |
- bool get lockFileExists => _lockFile != null || entryExists(lockFilePath); |
- |
- LockFile get lockFile { |
- if (_lockFile != null) return _lockFile; |
- |
- if (!lockFileExists) { |
- _lockFile = new LockFile.empty(); |
- } else { |
- _lockFile = new LockFile.load(lockFilePath, cache.sources); |
- } |
- |
- return _lockFile; |
- } |
- |
- /// The path to the entrypoint package's pubspec. |
- String get pubspecPath => root.path('pubspec.yaml'); |
- |
- /// The path to the entrypoint package's lockfile. |
- String get lockFilePath => root.path('pubspec.lock'); |
- |
- /// Gets all dependencies of the [root] package. |
- /// |
- /// Performs version resolution according to [SolveType]. |
- /// |
- /// [useLatest], if provided, defines a list of packages that will be |
- /// unlocked and forced to their latest versions. If [upgradeAll] is |
- /// true, the previous lockfile is ignored and all packages are re-resolved |
- /// from scratch. Otherwise, it will attempt to preserve the versions of all |
- /// previously locked packages. |
- /// |
- /// Shows a report of the changes made relative to the previous lockfile. If |
- /// 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. |
- Future acquireDependencies(SolveType type, {List<String> useLatest, |
- bool dryRun: false}) async { |
- var result = await resolveVersions(type, cache.sources, root, |
- lockFile: lockFile, useLatest: useLatest); |
- if (!result.succeeded) throw result.error; |
- |
- result.showReport(type); |
- |
- if (dryRun) { |
- result.summarizeChanges(type, dryRun: dryRun); |
- return; |
- } |
- |
- // Install the packages and maybe link them into the entrypoint. |
- if (_packageSymlinks) { |
- cleanDir(packagesDir); |
- } else { |
- deleteEntry(packagesDir); |
- } |
- |
- var ids = await Future.wait(result.packages.map(_get)); |
- _saveLockFile(ids); |
- |
- if (_packageSymlinks) _linkSelf(); |
- _linkOrDeleteSecondaryPackageDirs(); |
- |
- result.summarizeChanges(type, dryRun: dryRun); |
- |
- /// 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.loadTransformerCache().clearIfOutdated(result.changedPackages); |
- |
- try { |
- await precompileDependencies(changed: result.changedPackages); |
- await precompileExecutables(changed: result.changedPackages); |
- } catch (error, stackTrace) { |
- // Just log exceptions here. Since the method is just about acquiring |
- // dependencies, it shouldn't fail unless that fails. |
- log.exception(error, stackTrace); |
- } |
- } |
- |
- /// Precompile any transformed dependencies of the entrypoint. |
- /// |
- /// If [changed] is passed, only dependencies whose contents might be changed |
- /// if one of the given packages changes will be recompiled. |
- 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) { |
- if (package.pubspec.transformers.isEmpty) return false; |
- if (graph.isPackageMutable(package.name)) return false; |
- if (!dirExists(path.join(depsDir, package.name))) return true; |
- if (changed == null) return true; |
- |
- /// Only recompile [package] if any of its transitive dependencies have |
- /// 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) |
- .map((package) => package.name).toSet(), |
- changed); |
- }).map((package) => package.name).toSet(); |
- |
- if (dirExists(depsDir)) { |
- // Delete any cached dependencies that are going to be recached. |
- for (var package in dependenciesToPrecompile) { |
- deleteEntry(path.join(depsDir, package)); |
- } |
- |
- // Also delete any cached dependencies that should no longer be cached. |
- for (var subdir in listDir(depsDir)) { |
- var package = graph.packages[path.basename(subdir)]; |
- if (package == null || package.pubspec.transformers.isEmpty || |
- graph.isPackageMutable(package.name)) { |
- deleteEntry(subdir); |
- } |
- } |
- } |
- |
- if (dependenciesToPrecompile.isEmpty) return; |
- |
- try { |
- await log.progress("Precompiling dependencies", () async { |
- var packagesToLoad = |
- unionAll(dependenciesToPrecompile.map(graph.transitiveDependencies)) |
- .map((package) => package.name).toSet(); |
- |
- var environment = await AssetEnvironment.create(this, BarbackMode.DEBUG, |
- packages: packagesToLoad, useDart2JS: false); |
- |
- /// Ignore barback errors since they'll be emitted via [getAllAssets] |
- /// below. |
- environment.barback.errors.listen((_) {}); |
- |
- // TODO(nweiz): only get assets from [dependenciesToPrecompile] so as |
- // not to trigger unnecessary lazy transformers. |
- var assets = await environment.barback.getAllAssets(); |
- await waitAndPrintErrors(assets.map((asset) async { |
- if (!dependenciesToPrecompile.contains(asset.id.package)) return; |
- |
- var destPath = path.join( |
- depsDir, asset.id.package, path.fromUri(asset.id.path)); |
- ensureDir(path.dirname(destPath)); |
- await createFileFromStream(asset.read(), destPath); |
- })); |
- |
- log.message("Precompiled " + |
- toSentence(ordered(dependenciesToPrecompile).map(log.bold)) + "."); |
- }); |
- } catch (_) { |
- // TODO(nweiz): When barback does a better job of associating errors with |
- // assets (issue 19491), catch and handle compilation errors on a |
- // per-package basis. |
- for (var package in dependenciesToPrecompile) { |
- deleteEntry(path.join(depsDir, package)); |
- } |
- rethrow; |
- } |
- } |
- |
- /// Precompiles all executables from dependencies that don't transitively |
- /// depend on [this] or on a path dependency. |
- Future precompileExecutables({Iterable<String> changed}) async { |
- if (changed != null) changed = changed.toSet(); |
- |
- var binDir = path.join('.pub', 'bin'); |
- var sdkVersionPath = path.join(binDir, 'sdk-version'); |
- |
- // If the existing executable was compiled with a different SDK, we need to |
- // recompile regardless of what changed. |
- // TODO(nweiz): Use the VM to check this when issue 20802 is fixed. |
- var sdkMatches = fileExists(sdkVersionPath) && |
- 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)) { |
- deleteEntry(entry); |
- } |
- } |
- } |
- |
- var executables = new Map.fromIterable(root.immediateDependencies, |
- key: (dep) => dep.name, |
- value: (dep) => _executablesForPackage(graph, dep.name, changed)); |
- |
- for (var package in executables.keys.toList()) { |
- if (executables[package].isEmpty) executables.remove(package); |
- } |
- |
- if (!sdkMatches) deleteEntry(binDir); |
- if (executables.isEmpty) return; |
- |
- await log.progress("Precompiling executables", () async { |
- ensureDir(binDir); |
- |
- // Make sure there's a trailing newline so our version file matches the |
- // SDK's. |
- writeTextFile(sdkVersionPath, "${sdk.version}\n"); |
- |
- var packagesToLoad = |
- unionAll(executables.keys.map(graph.transitiveDependencies)) |
- .map((package) => package.name).toSet(); |
- var executableIds = unionAll( |
- executables.values.map((ids) => ids.toSet())); |
- var environment = await AssetEnvironment.create(this, BarbackMode.RELEASE, |
- packages: packagesToLoad, |
- entrypoints: executableIds, |
- useDart2JS: false); |
- environment.barback.errors.listen((error) { |
- log.error(log.red("Build error:\n$error")); |
- }); |
- |
- await waitAndPrintErrors(executables.keys.map((package) async { |
- var dir = path.join(binDir, package); |
- cleanDir(dir); |
- await environment.precompileExecutables(package, dir, |
- executableIds: executables[package]); |
- })); |
- }); |
- } |
- |
- /// Returns the list of all executable assets for [packageName] that should be |
- /// precompiled. |
- /// |
- /// 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, |
- Set<String> changed) { |
- var package = graph.packages[packageName]; |
- var binDir = package.path('bin'); |
- if (!dirExists(binDir)) return []; |
- if (graph.isPackageMutable(packageName)) return []; |
- |
- var executables = package.executableIds; |
- |
- // If we don't know which packages were changed, always precompile the |
- // executables. |
- if (changed == null) return executables; |
- |
- // If any of the package's dependencies changed, recompile the executables. |
- if (graph.transitiveDependencies(packageName) |
- .any((package) => changed.contains(package.name))) { |
- return executables; |
- } |
- |
- // If any executables don't exist, precompile them regardless of what |
- // changed. Since we delete the bin directory before recompiling, we need to |
- // recompile all executables. |
- var executablesExist = executables.every((executable) => |
- fileExists(path.join('.pub', 'bin', packageName, |
- "${path.url.basename(executable.path)}.snapshot"))); |
- if (!executablesExist) return executables; |
- |
- // Otherwise, we don't need to recompile. |
- return []; |
- } |
- |
- /// Makes sure the package at [id] is locally available. |
- /// |
- /// This automatically downloads the package to the system-wide cache as well |
- /// if it requires network access to retrieve (specifically, if the package's |
- /// source is a [CachedSource]). |
- Future<PackageId> _get(PackageId id) { |
- if (id.isRoot) return new Future.value(id); |
- |
- var source = cache.sources[id.source]; |
- return new Future.sync(() { |
- if (!_packageSymlinks) { |
- if (source is! CachedSource) return null; |
- return source.downloadToSystemCache(id); |
- } |
- |
- var packageDir = path.join(packagesDir, id.name); |
- if (entryExists(packageDir)) deleteEntry(packageDir); |
- return source.get(id, packageDir); |
- }).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; |
- |
- 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`. |
- Future<bool> _arePackagesAvailable(LockFile lockFile) { |
- return Future.wait(lockFile.packages.values.map((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 new Future.value(true); |
- |
- // Get the directory. |
- return source.getDirectory(package).then((dir) { |
- // See if the directory is there and looks like a package. |
- return dirExists(dir) || fileExists(path.join(dir, "pubspec.yaml")); |
- }); |
- })).then((results) { |
- // Make sure they are all true. |
- return results.every((result) => result); |
- }); |
- } |
- |
- /// Gets dependencies if the lockfile is out of date with respect to the |
- /// pubspec. |
- Future ensureLockFileIsUpToDate() { |
- return new Future.sync(() { |
- // If we don't have a current lock file, we definitely need to install. |
- if (!_isLockFileUpToDate(lockFile)) { |
- if (lockFileExists) { |
- log.message( |
- "Your pubspec has changed, so we need to update your lockfile:"); |
- } else { |
- log.message( |
- "You don't have a lockfile, so we need to generate that:"); |
- } |
- |
- return false; |
- } |
- |
- // 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. |
- return _arePackagesAvailable(lockFile).then((available) { |
- if (!available) { |
- log.message( |
- "You are missing some dependencies, so we need to install them " |
- "first:"); |
- } |
- |
- return available; |
- }); |
- }).then((upToDate) { |
- if (upToDate) return null; |
- return 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 = await Future.wait(result.packages.map((id) async { |
- var dir = await cache.sources[id.source].getDirectory(id); |
- return new Package(result.pubspecs[id.name], dir); |
- })); |
- |
- return new PackageGraph(this, new LockFile(result.packages), |
- new Map.fromIterable(packages, key: (package) => package.name)); |
- } |
- |
- await ensureLockFileIsUpToDate(); |
- var packages = await Future.wait(lockFile.packages.values.map((id) async { |
- var source = cache.sources[id.source]; |
- var dir = await source.getDirectory(id); |
- return new Package.load(id.name, dir, cache.sources); |
- })); |
- |
- var packageMap = new Map.fromIterable(packages, key: (p) => p.name); |
- packageMap[root.name] = root; |
- return new PackageGraph(this, lockFile, packageMap); |
- }, fine: true); |
- |
- _packageGraph = graph; |
- return graph; |
- } |
- |
- /// Saves a list of concrete package versions to the `pubspec.lock` file. |
- void _saveLockFile(List<PackageId> packageIds) { |
- _lockFile = new LockFile(packageIds); |
- var lockFilePath = root.path('pubspec.lock'); |
- writeTextFile(lockFilePath, _lockFile.serialize(root.dir, cache.sources)); |
- } |
- |
- /// Creates a self-referential symlink in the `packages` directory that allows |
- /// a package to import its own files using `package:`. |
- void _linkSelf() { |
- var linkPath = path.join(packagesDir, root.name); |
- // Create the symlink if it doesn't exist. |
- if (entryExists(linkPath)) return; |
- ensureDir(packagesDir); |
- createPackageSymlink(root.name, root.dir, linkPath, |
- isSelfLink: true, relative: true); |
- } |
- |
- /// If [packageSymlinks] is true, add "packages" directories to the whitelist |
- /// of directories that may contain Dart entrypoints. |
- /// |
- /// Otherwise, delete any "packages" directories in the whitelist of |
- /// directories that may contain Dart entrypoints. |
- void _linkOrDeleteSecondaryPackageDirs() { |
- // Only the main "bin" directory gets a "packages" directory, not its |
- // subdirectories. |
- var binDir = root.path('bin'); |
- if (dirExists(binDir)) _linkOrDeleteSecondaryPackageDir(binDir); |
- |
- // The others get "packages" directories in subdirectories too. |
- for (var dir in ['benchmark', 'example', 'test', 'tool', 'web']) { |
- _linkOrDeleteSecondaryPackageDirsRecursively(root.path(dir)); |
- } |
- } |
- |
- /// If [packageSymlinks] is true, creates a symlink to the "packages" |
- /// directory in [dir] and all its subdirectories. |
- /// |
- /// Otherwise, deletes any "packages" directories in [dir] and all its |
- /// subdirectories. |
- void _linkOrDeleteSecondaryPackageDirsRecursively(String dir) { |
- if (!dirExists(dir)) return; |
- _linkOrDeleteSecondaryPackageDir(dir); |
- _listDirWithoutPackages(dir) |
- .where(dirExists) |
- .forEach(_linkOrDeleteSecondaryPackageDir); |
- } |
- |
- // TODO(nweiz): roll this into [listDir] in io.dart once issue 4775 is fixed. |
- /// Recursively lists the contents of [dir], excluding hidden `.DS_Store` |
- /// files and `package` files. |
- List<String> _listDirWithoutPackages(dir) { |
- return flatten(listDir(dir).map((file) { |
- if (path.basename(file) == 'packages') return []; |
- if (!dirExists(file)) return []; |
- var fileAndSubfiles = [file]; |
- fileAndSubfiles.addAll(_listDirWithoutPackages(file)); |
- return fileAndSubfiles; |
- })); |
- } |
- |
- /// If [packageSymlinks] is true, creates a symlink to the "packages" |
- /// directory in [dir]. |
- /// |
- /// Otherwise, deletes a "packages" directories in [dir] if one exists. |
- void _linkOrDeleteSecondaryPackageDir(String dir) { |
- var symlink = path.join(dir, 'packages'); |
- if (entryExists(symlink)) deleteEntry(symlink); |
- if (_packageSymlinks) createSymlink(packagesDir, symlink, relative: true); |
- } |
-} |