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 |