Chromium Code Reviews| Index: sdk/lib/_internal/pub/lib/src/global_packages.dart |
| diff --git a/sdk/lib/_internal/pub/lib/src/global_packages.dart b/sdk/lib/_internal/pub/lib/src/global_packages.dart |
| index 6f3da60e58ea6e0c61a438ec5becfae227b293b9..7fb1d2a2e1543dbb287c6adeaf3a7da5c88f9cdb 100644 |
| --- a/sdk/lib/_internal/pub/lib/src/global_packages.dart |
| +++ b/sdk/lib/_internal/pub/lib/src/global_packages.dart |
| @@ -18,14 +18,19 @@ import 'lock_file.dart'; |
| import 'log.dart' as log; |
| import 'package.dart'; |
| import 'pubspec.dart'; |
| -import 'system_cache.dart'; |
| +import 'sdk.dart' as sdk; |
| import 'solver/version_solver.dart'; |
| import 'source/cached.dart'; |
| import 'source/git.dart'; |
| import 'source/path.dart'; |
| +import 'system_cache.dart'; |
| import 'utils.dart'; |
| import 'version.dart'; |
| +/// Matches the package name that a binstub was created for inside the contents |
| +/// of the shell script. |
| +final _binStubPackagePattern = new RegExp(r"Package: ([a-zA-Z0-9_-]+)"); |
| + |
| /// Maintains the set of packages that have been globally activated. |
| /// |
| /// These have been hand-chosen by the user to make their executables in bin/ |
| @@ -57,6 +62,9 @@ class GlobalPackages { |
| /// The directory where the lockfiles for activated packages are stored. |
| String get _directory => p.join(cache.rootDir, "global_packages"); |
| + /// The directory where binstubs for global package executables are stored. |
| + String get _binStubDir => p.join(cache.rootDir, "bin"); |
| + |
| /// Creates a new global package registry backed by the given directory on |
| /// the user's file system. |
| /// |
| @@ -66,7 +74,12 @@ class GlobalPackages { |
| /// Caches the package located in the Git repository [repo] and makes it the |
| /// active global version. |
| - Future activateGit(String repo) async { |
| + /// |
| + /// [executables] is the names of the executables that should have binstubs. |
| + /// If empty, all executables in the package will get binstubs, unless |
| + /// [createBinStubs] is false. |
|
nweiz
2014/09/15 22:57:18
Document [overwriteBinStubs].
Bob Nystrom
2014/09/17 21:34:35
Done.
|
| + Future activateGit(String repo, List<String> executables, |
| + {bool createBinStubs, bool overwriteBinStubs}) async { |
| var source = cache.sources["git"] as GitSource; |
| var name = await source.getPackageNameFromRepo(repo); |
| // Call this just to log what the current active package is, if any. |
| @@ -76,19 +89,35 @@ class GlobalPackages { |
| // dependencies. Their executables shouldn't be cached, and there should |
| // be a mechanism for redoing dependency resolution if a path pubspec has |
| // changed (see also issue 20499). |
| - await _installInCache( |
| + var package = await _installInCache( |
| new PackageDep(name, "git", VersionConstraint.any, repo)); |
| + _updateBinStubs(package, executables, createBinStubs: createBinStubs, |
| + overwriteBinStubs: overwriteBinStubs); |
|
nweiz
2014/09/15 22:57:17
Why not just have [_installInCache] call [_updateB
Bob Nystrom
2014/09/17 21:34:35
I thought about that, but it felt pointless to pip
|
| } |
| /// Finds the latest version of the hosted package with [name] that matches |
| /// [constraint] and makes it the active global version. |
| - Future activateHosted(String name, VersionConstraint constraint) { |
| + /// |
| + /// [executables] is the names of the executables that should have binstubs. |
| + /// If empty, all executables in the package will get binstubs, unless |
| + /// [createBinStubs] is false. |
| + Future activateHosted(String name, VersionConstraint constraint, |
| + List<String> executables, {bool createBinStubs, |
| + bool overwriteBinStubs}) async { |
| _describeActive(name); |
| - return _installInCache(new PackageDep(name, "hosted", constraint, name)); |
| + var package = await _installInCache( |
| + new PackageDep(name, "hosted", constraint, name)); |
| + _updateBinStubs(package, executables, createBinStubs: createBinStubs, |
| + overwriteBinStubs: overwriteBinStubs); |
| } |
| /// Makes the local package at [path] globally active. |
| - Future activatePath(String path) async { |
| + /// |
| + /// [executables] is the names of the executables that should have binstubs. |
| + /// If empty, all executables in the package will get binstubs, unless |
| + /// [createBinStubs] is false. |
| + Future activatePath(String path, List<String> executables, |
| + {bool createBinStubs, bool overwriteBinStubs}) async { |
| var entrypoint = new Entrypoint(path, cache); |
| // Get the package's dependencies. |
| @@ -109,10 +138,15 @@ class GlobalPackages { |
| var binDir = p.join(_directory, name, 'bin'); |
| if (dirExists(binDir)) deleteEntry(binDir); |
| + |
| + _updateBinStubs(entrypoint.root, executables, |
| + createBinStubs: createBinStubs, overwriteBinStubs: overwriteBinStubs); |
| } |
| /// Installs the package [dep] and its dependencies into the system cache. |
| - Future _installInCache(PackageDep dep) async { |
| + /// |
| + /// Returns the cached root [Package]. |
| + Future<Package> _installInCache(PackageDep dep) async { |
| var source = cache.sources[dep.source]; |
| // Create a dummy package with just [dep] so we can do resolution on it. |
| @@ -140,6 +174,8 @@ class GlobalPackages { |
| .loadPackageGraph(result); |
| await _precompileExecutables(graph.entrypoint, dep.name); |
| _writeLockFile(dep.name, lockFile); |
| + |
| + return graph.packages[dep.name]; |
| } |
| /// Precompiles the executables for [package] and saves them in the global |
| @@ -188,7 +224,6 @@ class GlobalPackages { |
| var id = lockFile.packages[package]; |
| log.message('Activated ${_formatPackage(id)}.'); |
| - |
| } |
| /// Shows the user the currently active package with [name], if any. |
| @@ -217,19 +252,16 @@ class GlobalPackages { |
| /// Deactivates a previously-activated package named [name]. |
| /// |
| - /// If [logDeactivate] is true, displays to the user when a package is |
| - /// deactivated. Otherwise, deactivates silently. |
| - /// |
| /// Returns `false` if no package with [name] was currently active. |
| - bool deactivate(String name, {bool logDeactivate: false}) { |
| + bool deactivate(String name) { |
| var dir = p.join(_directory, name); |
| if (!dirExists(dir)) return false; |
| - if (logDeactivate) { |
| - var lockFile = new LockFile.load(_getLockFilePath(name), cache.sources); |
| - var id = lockFile.packages[name]; |
| - log.message('Deactivated package ${_formatPackage(id)}.'); |
| - } |
| + _deleteBinStubs(name); |
| + |
| + var lockFile = new LockFile.load(_getLockFilePath(name), cache.sources); |
| + var id = lockFile.packages[name]; |
| + log.message('Deactivated package ${_formatPackage(id)}.'); |
| deleteEntry(dir); |
| @@ -360,4 +392,142 @@ class GlobalPackages { |
| return '${log.bold(id.name)} ${id.version}'; |
| } |
| } |
| + |
| + /// Updates the binstubs for [package]. |
| + /// |
| + /// A binstub is a little shell script in `PUB_CACHE/bin` that runs an |
| + /// executable from a globally activated package. This removes any old |
| + /// binstubs from the previously activated version of the package and |
| + /// (optionally) creates new ones for the executables listed in the package's |
| + /// pubspec. |
| + /// |
| + /// If [executables] is not empty, only creates binstubs for the listed |
| + /// executables. Otherwise, all of them will get binstubs unless |
| + /// [createBinStubs] is false. |
| + /// |
| + /// If a binstub with the same name was previously installed from another |
| + /// package, it will be preserved unless [overwriteBinStubs] is true. |
| + void _updateBinStubs(Package package, List<String> executables, |
| + {bool createBinStubs, bool overwriteBinStubs}) { |
| + // Remove any previously activated binstubs for this package, in case the |
| + // list of executables has changed. |
| + _deleteBinStubs(package.name); |
| + |
| + if (!createBinStubs || package.pubspec.executables.isEmpty) return; |
| + |
| + ensureDir(_binStubDir); |
| + |
| + var installed = []; |
| + var collided = {}; |
| + var allExecutables = ordered(package.pubspec.executables.keys); |
| + for (var command in allExecutables) { |
|
nweiz
2014/09/15 22:57:17
Nit: "command" -> "executable" for consistency.
T
Bob Nystrom
2014/09/17 21:34:35
Done.
|
| + if (executables.isNotEmpty && !executables.contains(command)) continue; |
| + |
| + // If there is no script name, it defaults to the name of the command. |
| + var script = package.pubspec.executables[command]; |
| + if (script == null) script = command; |
| + |
| + var binStubPath = p.join(_binStubDir, command); |
| + |
| + // See if the binstub already exists. If so, it's for another package |
| + // since we already deleted all of this package's binstubs. |
| + if (fileExists(binStubPath)) { |
| + var contents = readTextFile(binStubPath); |
| + var match = _binStubPackagePattern.firstMatch(contents); |
| + if (match != null) { |
| + collided[command] = match[1]; |
| + if (!overwriteBinStubs) continue; |
| + } else { |
| + log.fine("Could not parse binstub $binStubPath:\n$contents"); |
| + } |
| + } |
| + |
| + // Create the binstub. |
| + if (Platform.operatingSystem == "windows") { |
| + var batch = """ |
| +@echo off |
| +rem This file was created by pub v${sdk.version}. |
| +rem Package: ${package.name} |
| +rem Version: ${package.version} |
| +rem Command: ${command} |
| +rem Script: ${script} |
| +pub global run ${package.name}:$script %* |
|
nweiz
2014/09/15 22:57:17
Add a TODO to run the snapshot directly if possibl
Bob Nystrom
2014/09/17 21:34:35
Done.
|
| +"""; |
| + writeTextFile(binStubPath, batch); |
| + } else { |
| + var bash = """ |
| +# This file was created by pub v${sdk.version}. |
| +# Package: ${package.name} |
| +# Version: ${package.version} |
| +# Command: ${command} |
| +# Script: ${script} |
| +pub global run ${package.name}:$script \$@ |
|
nweiz
2014/09/15 22:57:17
$@ should be quoted for stupid bash reasons. If yo
Bob Nystrom
2014/09/17 21:34:34
Done.
|
| +"""; |
| + writeTextFile(binStubPath, bash); |
| + |
| + // Make it executable. |
| + var result = Process.runSync('chmod', ['+x', binStubPath]); |
| + if (result.exitCode != 0) { |
| + fail('Could not make "$binStubPath" executable:\n${result.stderr}'); |
|
nweiz
2014/09/15 22:57:17
Include the exit code here.
I think this should b
Bob Nystrom
2014/09/17 21:34:35
Done.
|
| + } |
| + } |
| + |
| + installed.add(log.bold(command)); |
| + } |
| + |
| + if (installed.isNotEmpty) { |
| + log.message("Installed ${namedSequence('executable', installed)}."); |
| + // TODO(rnystrom): Show the user how to add the binstub directory to |
| + // their PATH if not already on it. |
| + } |
| + |
| + // Show errors for any collisions. |
| + if (collided.isNotEmpty) { |
| + for (var command in ordered(collided.keys)) { |
| + if (overwriteBinStubs) { |
| + log.warning("Replaced ${log.bold(command)} previously installed from " |
| + "${log.bold(collided[command])}."); |
| + } else { |
| + log.warning("Executable ${log.bold(command)} was already installed " |
| + "from ${log.bold(collided[command])}."); |
| + } |
| + } |
| + |
| + if (!overwriteBinStubs) { |
| + log.warning("Deactivate the other package(s) or activate " |
| + "${log.bold(package.name)} using --force."); |
|
nweiz
2014/09/15 22:57:17
If you don't rename "--overwrite-executables" to "
Bob Nystrom
2014/09/17 21:34:35
Done.
|
| + } |
| + } |
| + |
| + // Show errors for any unknown executables. |
| + var unknown = ordered(executables.where( |
| + (exe) => !package.pubspec.executables.keys.contains(exe))); |
| + if (unknown.isNotEmpty) { |
| + dataError("Unknown ${namedSequence('executable', unknown)}."); |
| + } |
| + } |
| + |
| + /// Deletes all existing binstubs for [package]. |
| + void _deleteBinStubs(String package) { |
| + if (!dirExists(_binStubDir)) return; |
| + |
| + for (var file in listDir(_binStubDir, includeDirs: false)) { |
| + var contents = readTextFile(file); |
| + |
| + // Skip files pub didn't touch. This is mainly hidden files like |
| + // .DS_Store on Mac. |
|
nweiz
2014/09/15 22:57:18
...not that hidden files will be listed by [listDi
Bob Nystrom
2014/09/17 21:34:35
Done.
|
| + if (!contents.contains("This file was created by pub")) continue; |
| + |
| + var match = _binStubPackagePattern.firstMatch(contents); |
| + if (match == null) { |
| + log.fine("Could not parse binstub $file:\n$contents"); |
| + continue; |
| + } |
| + |
| + if (match[1] == package) { |
| + log.fine("Deleting old binstub $file"); |
| + deleteEntry(file); |
| + } |
| + } |
| + } |
| } |