| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /// The main entrypoint for the pub command line application. | |
| 6 library pub; | |
| 7 | |
| 8 import 'dart:async'; | |
| 9 import 'dart:io'; | |
| 10 import 'dart:math'; | |
| 11 | |
| 12 import 'package:args/args.dart'; | |
| 13 import 'package:pathos/path.dart' as path; | |
| 14 | |
| 15 import 'command_help.dart'; | |
| 16 import 'command_install.dart'; | |
| 17 import 'command_lish.dart'; | |
| 18 import 'command_update.dart'; | |
| 19 import 'command_uploader.dart'; | |
| 20 import 'command_version.dart'; | |
| 21 import 'command_cache.dart'; | |
| 22 import 'entrypoint.dart'; | |
| 23 import 'exit_codes.dart' as exit_codes; | |
| 24 import 'http.dart'; | |
| 25 import 'io.dart'; | |
| 26 import 'log.dart' as log; | |
| 27 import 'package.dart'; | |
| 28 import 'pubspec.dart'; | |
| 29 import 'sdk.dart' as sdk; | |
| 30 import 'source.dart'; | |
| 31 import 'source_registry.dart'; | |
| 32 import 'system_cache.dart'; | |
| 33 import 'utils.dart'; | |
| 34 import 'version.dart'; | |
| 35 | |
| 36 /// The commands that Pub understands. | |
| 37 Map<String, PubCommand> get pubCommands { | |
| 38 var commands = { | |
| 39 'cache': new CacheCommand(), | |
| 40 'help': new HelpCommand(), | |
| 41 'install': new InstallCommand(), | |
| 42 'publish': new LishCommand(), | |
| 43 'update': new UpdateCommand(), | |
| 44 'uploader': new UploaderCommand(), | |
| 45 'version': new VersionCommand() | |
| 46 }; | |
| 47 for (var command in commands.values.toList()) { | |
| 48 for (var alias in command.aliases) { | |
| 49 commands[alias] = command; | |
| 50 } | |
| 51 } | |
| 52 return commands; | |
| 53 } | |
| 54 | |
| 55 /// The parser for arguments that are global to Pub rather than specific to a | |
| 56 /// single command. | |
| 57 ArgParser get pubArgParser { | |
| 58 var parser = new ArgParser(); | |
| 59 parser.addFlag('help', abbr: 'h', negatable: false, | |
| 60 help: 'Print this usage information.'); | |
| 61 parser.addFlag('version', negatable: false, | |
| 62 help: 'Print pub version.'); | |
| 63 parser.addFlag('trace', | |
| 64 help: 'Print debugging information when an error occurs.'); | |
| 65 parser.addOption('verbosity', | |
| 66 help: 'Control output verbosity.', | |
| 67 allowed: ['normal', 'io', 'solver', 'all'], | |
| 68 allowedHelp: { | |
| 69 'normal': 'Show errors, warnings, and user messages.', | |
| 70 'io': 'Also show IO operations.', | |
| 71 'solver': 'Show steps during version resolution.', | |
| 72 'all': 'Show all output including internal tracing messages.' | |
| 73 }); | |
| 74 parser.addFlag('verbose', abbr: 'v', negatable: false, | |
| 75 help: 'Shortcut for "--verbosity=all"'); | |
| 76 return parser; | |
| 77 } | |
| 78 | |
| 79 main() { | |
| 80 var globalOptions; | |
| 81 try { | |
| 82 globalOptions = pubArgParser.parse(new Options().arguments); | |
| 83 } on FormatException catch (e) { | |
| 84 log.error(e.message); | |
| 85 log.error('Run "pub help" to see available options.'); | |
| 86 exit(exit_codes.USAGE); | |
| 87 } | |
| 88 | |
| 89 if (globalOptions['version']) { | |
| 90 printVersion(); | |
| 91 return; | |
| 92 } | |
| 93 | |
| 94 if (globalOptions['help'] || globalOptions.rest.isEmpty) { | |
| 95 printUsage(); | |
| 96 return; | |
| 97 } | |
| 98 | |
| 99 if (globalOptions['trace']) { | |
| 100 log.recordTranscript(); | |
| 101 } | |
| 102 | |
| 103 switch (globalOptions['verbosity']) { | |
| 104 case 'normal': log.showNormal(); break; | |
| 105 case 'io': log.showIO(); break; | |
| 106 case 'solver': log.showSolver(); break; | |
| 107 case 'all': log.showAll(); break; | |
| 108 default: | |
| 109 // No specific verbosity given, so check for the shortcut. | |
| 110 if (globalOptions['verbose']) { | |
| 111 log.showAll(); | |
| 112 } else { | |
| 113 log.showNormal(); | |
| 114 } | |
| 115 break; | |
| 116 } | |
| 117 | |
| 118 SecureSocket.initialize(database: relativeToPub('resource/certs')); | |
| 119 | |
| 120 var cacheDir; | |
| 121 if (Platform.environment.containsKey('PUB_CACHE')) { | |
| 122 cacheDir = Platform.environment['PUB_CACHE']; | |
| 123 } else if (Platform.operatingSystem == 'windows') { | |
| 124 var appData = Platform.environment['APPDATA']; | |
| 125 cacheDir = path.join(appData, 'Pub', 'Cache'); | |
| 126 } else { | |
| 127 cacheDir = '${Platform.environment['HOME']}/.pub-cache'; | |
| 128 } | |
| 129 | |
| 130 validatePlatform().then((_) { | |
| 131 var cache = new SystemCache.withSources(cacheDir); | |
| 132 | |
| 133 // Select the command. | |
| 134 var command = pubCommands[globalOptions.rest[0]]; | |
| 135 if (command == null) { | |
| 136 log.error('Could not find a command named "${globalOptions.rest[0]}".'); | |
| 137 log.error('Run "pub help" to see available commands.'); | |
| 138 exit(exit_codes.USAGE); | |
| 139 return; | |
| 140 } | |
| 141 | |
| 142 var commandArgs = globalOptions.rest.sublist(1); | |
| 143 command.run(cache, globalOptions, commandArgs); | |
| 144 }); | |
| 145 } | |
| 146 | |
| 147 /// Checks that pub is running on a supported platform. If it isn't, it prints | |
| 148 /// an error message and exits. Completes when the validation is done. | |
| 149 Future validatePlatform() { | |
| 150 return new Future.sync(() { | |
| 151 if (Platform.operatingSystem != 'windows') return; | |
| 152 | |
| 153 return runProcess('ver', []).then((result) { | |
| 154 if (result.stdout.join('\n').contains('XP')) { | |
| 155 log.error('Sorry, but pub is not supported on Windows XP.'); | |
| 156 exit(exit_codes.USAGE); | |
| 157 } | |
| 158 }); | |
| 159 }); | |
| 160 } | |
| 161 | |
| 162 /// Displays usage information for the app. | |
| 163 void printUsage([String description = 'Pub is a package manager for Dart.']) { | |
| 164 // Build up a buffer so it shows up as a single log entry. | |
| 165 var buffer = new StringBuffer(); | |
| 166 buffer.write(description); | |
| 167 buffer.write('\n\n'); | |
| 168 buffer.write('Usage: pub command [arguments]\n\n'); | |
| 169 buffer.write('Global options:\n'); | |
| 170 buffer.write('${pubArgParser.getUsage()}\n\n'); | |
| 171 | |
| 172 // Show the commands sorted. | |
| 173 buffer.write('Available commands:\n'); | |
| 174 | |
| 175 // TODO(rnystrom): A sorted map would be nice. | |
| 176 int length = 0; | |
| 177 var names = <String>[]; | |
| 178 for (var command in pubCommands.keys) { | |
| 179 // Hide aliases. | |
| 180 if (pubCommands[command].aliases.indexOf(command) >= 0) continue; | |
| 181 length = max(length, command.length); | |
| 182 names.add(command); | |
| 183 } | |
| 184 | |
| 185 names.sort((a, b) => a.compareTo(b)); | |
| 186 | |
| 187 for (var name in names) { | |
| 188 buffer.write(' ${padRight(name, length)} ' | |
| 189 '${pubCommands[name].description}\n'); | |
| 190 } | |
| 191 | |
| 192 buffer.write('\n'); | |
| 193 buffer.write( | |
| 194 'Use "pub help [command]" for more information about a command.'); | |
| 195 log.message(buffer.toString()); | |
| 196 } | |
| 197 | |
| 198 void printVersion() { | |
| 199 log.message('Pub ${sdk.version}'); | |
| 200 } | |
| 201 | |
| 202 abstract class PubCommand { | |
| 203 SystemCache cache; | |
| 204 ArgResults globalOptions; | |
| 205 ArgResults commandOptions; | |
| 206 | |
| 207 Entrypoint entrypoint; | |
| 208 | |
| 209 /// A one-line description of this command. | |
| 210 String get description; | |
| 211 | |
| 212 /// How to invoke this command (e.g. `"pub install [package]"`). | |
| 213 String get usage; | |
| 214 | |
| 215 /// Whether or not this command requires [entrypoint] to be defined. If false, | |
| 216 /// Pub won't look for a pubspec and [entrypoint] will be null when the | |
| 217 /// command runs. | |
| 218 final requiresEntrypoint = true; | |
| 219 | |
| 220 /// Alternate names for this command. These names won't be used in the | |
| 221 /// documentation, but they will work when invoked on the command line. | |
| 222 final aliases = const <String>[]; | |
| 223 | |
| 224 /// Override this to define command-specific options. The results will be made | |
| 225 /// available in [commandOptions]. | |
| 226 ArgParser get commandParser => new ArgParser(); | |
| 227 | |
| 228 void run(SystemCache cache_, ArgResults globalOptions_, | |
| 229 List<String> commandArgs) { | |
| 230 cache = cache_; | |
| 231 globalOptions = globalOptions_; | |
| 232 | |
| 233 try { | |
| 234 commandOptions = commandParser.parse(commandArgs); | |
| 235 } on FormatException catch (e) { | |
| 236 log.error(e.message); | |
| 237 log.error('Use "pub help" for more information.'); | |
| 238 exit(exit_codes.USAGE); | |
| 239 } | |
| 240 | |
| 241 handleError(error) { | |
| 242 var trace = getAttachedStackTrace(error); | |
| 243 | |
| 244 // This is basically the top-level exception handler so that we don't | |
| 245 // spew a stack trace on our users. | |
| 246 var message; | |
| 247 | |
| 248 try { | |
| 249 // Most exception types have a "message" property. We prefer this since | |
| 250 // it skips the "Exception:", "HttpException:", etc. prefix that calling | |
| 251 // toString() adds. But, alas, "message" isn't actually defined in the | |
| 252 // base Exception type so there's no easy way to know if it's available | |
| 253 // short of a giant pile of type tests for each known exception type. | |
| 254 // | |
| 255 // So just try it. If it throws, default to toString(). | |
| 256 message = error.message; | |
| 257 } on NoSuchMethodError catch (_) { | |
| 258 message = error.toString(); | |
| 259 } | |
| 260 | |
| 261 log.error(message); | |
| 262 | |
| 263 if (trace != null) { | |
| 264 if (globalOptions['trace'] || !isUserFacingException(error)) { | |
| 265 log.error(trace); | |
| 266 } else { | |
| 267 log.fine(trace); | |
| 268 } | |
| 269 } | |
| 270 | |
| 271 if (globalOptions['trace']) { | |
| 272 log.dumpTranscript(); | |
| 273 } else if (!isUserFacingException(error)) { | |
| 274 log.error(""" | |
| 275 This is an unexpected error. Please run | |
| 276 | |
| 277 pub --trace ${new Options().arguments.map((arg) => "'$arg'").join(' ')} | |
| 278 | |
| 279 and include the results in a bug report on http://dartbug.com/new. | |
| 280 """); | |
| 281 } | |
| 282 | |
| 283 exit(_chooseExitCode(error)); | |
| 284 } | |
| 285 | |
| 286 new Future.sync(() { | |
| 287 if (requiresEntrypoint) { | |
| 288 // TODO(rnystrom): Will eventually need better logic to walk up | |
| 289 // subdirectories until we hit one that looks package-like. For now, | |
| 290 // just assume the cwd is it. | |
| 291 entrypoint = new Entrypoint(path.current, cache); | |
| 292 } | |
| 293 | |
| 294 var commandFuture = onRun(); | |
| 295 if (commandFuture == null) return true; | |
| 296 | |
| 297 return commandFuture; | |
| 298 }).whenComplete(() => cache_.deleteTempDir()).catchError((e) { | |
| 299 if (e is PubspecNotFoundException && e.name == null) { | |
| 300 e = 'Could not find a file named "pubspec.yaml" in the directory ' | |
| 301 '${path.current}.'; | |
| 302 } else if (e is PubspecHasNoNameException && e.name == null) { | |
| 303 e = 'pubspec.yaml is missing the required "name" field (e.g. "name: ' | |
| 304 '${path.basename(path.current)}").'; | |
| 305 } | |
| 306 | |
| 307 handleError(e); | |
| 308 }).then((_) { | |
| 309 // Explicitly exit on success to ensure that any dangling dart:io handles | |
| 310 // don't cause the process to never terminate. | |
| 311 exit(0); | |
| 312 }); | |
| 313 } | |
| 314 | |
| 315 /// Override this to perform the specific command. Return a future that | |
| 316 /// completes when the command is done or fails if the command fails. If the | |
| 317 /// command is synchronous, it may return `null`. | |
| 318 Future onRun(); | |
| 319 | |
| 320 /// Displays usage information for this command. | |
| 321 void printUsage([String description]) { | |
| 322 if (description == null) description = this.description; | |
| 323 | |
| 324 var buffer = new StringBuffer(); | |
| 325 buffer.write('$description\n\nUsage: $usage'); | |
| 326 | |
| 327 var commandUsage = commandParser.getUsage(); | |
| 328 if (!commandUsage.isEmpty) { | |
| 329 buffer.write('\n'); | |
| 330 buffer.write(commandUsage); | |
| 331 } | |
| 332 | |
| 333 log.message(buffer.toString()); | |
| 334 } | |
| 335 | |
| 336 /// Returns the appropriate exit code for [exception], falling back on 1 if no | |
| 337 /// appropriate exit code could be found. | |
| 338 int _chooseExitCode(exception) { | |
| 339 if (exception is HttpException || exception is HttpParserException || | |
| 340 exception is SocketIOException || exception is PubHttpException) { | |
| 341 return exit_codes.UNAVAILABLE; | |
| 342 } else if (exception is FormatException) { | |
| 343 return exit_codes.DATA; | |
| 344 } else { | |
| 345 return 1; | |
| 346 } | |
| 347 } | |
| 348 } | |
| OLD | NEW |