1 /**
2 	Defines the behavior of the DUB command line client.
3 
4 	Copyright: © 2012-2013 Matthias Dondorff
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Matthias Dondorff, Sönke Ludwig
7 */
8 module dub.commandline;
9 
10 import dub.compilers.compiler;
11 import dub.dependency;
12 import dub.dub;
13 import dub.generators.generator;
14 import dub.internal.vibecompat.core.file;
15 import dub.internal.vibecompat.core.log;
16 import dub.internal.vibecompat.inet.url;
17 import dub.package_;
18 import dub.packagesupplier;
19 import dub.project;
20 import dub.internal.utils : getDUBVersion;
21 
22 import std.algorithm;
23 import std.array;
24 import std.conv;
25 import std.encoding;
26 import std.exception;
27 import std.file;
28 import std.getopt;
29 import std.process;
30 import std.stdio;
31 import std..string;
32 import std.typecons : Tuple, tuple;
33 import std.variant;
34 
35 
36 int runDubCommandLine(string[] args)
37 {
38 	logDiagnostic("DUB version %s", getDUBVersion());
39 
40 	version(Windows){
41 		// rdmd uses $TEMP to compute a temporary path. since cygwin substitutes backslashes
42 		// with slashes, this causes OPTLINK to fail (it thinks path segments are options)
43 		// we substitute the other way around here to fix this.
44 		environment["TEMP"] = environment["TEMP"].replace("/", "\\");
45 	}
46 
47 	// split application arguments from DUB arguments
48 	string[] app_args;
49 	auto app_args_idx = args.countUntil("--");
50 	if (app_args_idx >= 0) {
51 		app_args = args[app_args_idx+1 .. $];
52 		args = args[0 .. app_args_idx];
53 	}
54 	args = args[1 .. $]; // strip the application name
55 
56 	// parse general options
57 	bool verbose, vverbose, quiet, vquiet;
58 	bool help, annotate;
59 	LogLevel loglevel = LogLevel.info;
60 	string[] registry_urls;
61 	string root_path = getcwd();
62 
63 	auto common_args = new CommandArgs(args);
64 	try {
65 		common_args.getopt("h|help", &help, ["Display general or command specific help"]);
66 		common_args.getopt("root", &root_path, ["Path to operate in instead of the current working dir"]);
67 		common_args.getopt("registry", &registry_urls, ["Search the given DUB registry URL first when resolving dependencies. Can be specified multiple times."]);
68 		common_args.getopt("annotate", &annotate, ["Do not perform any action, just print what would be done"]);
69 		common_args.getopt("v|verbose", &verbose, ["Print diagnostic output"]);
70 		common_args.getopt("vverbose", &vverbose, ["Print debug output"]);
71 		common_args.getopt("q|quiet", &quiet, ["Only print warnings and errors"]);
72 		common_args.getopt("vquiet", &vquiet, ["Print no messages"]);
73 
74 		if( vverbose ) loglevel = LogLevel.debug_;
75 		else if( verbose ) loglevel = LogLevel.diagnostic;
76 		else if( vquiet ) loglevel = LogLevel.none;
77 		else if( quiet ) loglevel = LogLevel.warn;
78 		setLogLevel(loglevel);
79 	} catch (Throwable e) {
80 		logError("Error processing arguments: %s", e.msg);
81 		logDiagnostic("Full exception: %s", e.toString().sanitize);
82 		logInfo("Run 'dub help' for usage information.");
83 		return 1;
84 	}
85 
86 	// create the list of all supported commands
87 
88 	CommandGroup[] commands = [
89 		CommandGroup("Package creation",
90 			new InitCommand
91 		),
92 		CommandGroup("Build, test and run",
93 			new RunCommand,
94 			new BuildCommand,
95 			new TestCommand,
96 			new GenerateCommand,
97 			new DescribeCommand
98 		),
99 		CommandGroup("Package management",
100 			new FetchCommand,
101 			new InstallCommand,
102 			new RemoveCommand,
103 			new UninstallCommand,
104 			new UpgradeCommand,
105 			new AddPathCommand,
106 			new RemovePathCommand,
107 			new AddLocalCommand,
108 			new RemoveLocalCommand,
109 			new ListCommand,
110 			new ListInstalledCommand
111 		)
112 	];
113 
114 	// extract the command
115 	string cmdname;
116 	args = common_args.extractRemainingArgs();
117 	if (args.length >= 1 && !args[0].startsWith("-")) {
118 		cmdname = args[0];
119 		args = args[1 .. $];
120 	} else {
121 		if (help) {
122 			showHelp(commands, common_args);
123 			return 0;
124 		}
125 		cmdname = "run";
126 	}
127 	auto command_args = new CommandArgs(args);
128 
129 	if (cmdname == "help") {
130 		showHelp(commands, common_args);
131 		return 0;
132 	}
133 
134 	// execute the selected command
135 	foreach (grp; commands) foreach (cmd; grp.commands)
136 		if (cmd.name == cmdname) {
137 			try {
138 				cmd.prepare(command_args);
139 				enforceUsage(cmd.acceptsAppArgs || app_args.length == 0, cmd.name ~ " doesn't accept application arguments.");
140 			} catch (Throwable e) {
141 				logError("Error processing arguments: %s", e.msg);
142 				logDiagnostic("Full exception: %s", e.toString().sanitize);
143 				logInfo("Run 'dub help' for usage information.");
144 				return 1;
145 			}
146 
147 			if (help) {
148 				showCommandHelp(cmd, command_args, common_args);
149 				return 0;
150 			}
151 
152 			// initialize DUB
153 			auto package_suppliers = registry_urls.map!(url => cast(PackageSupplier)new RegistryPackageSupplier(Url(url))).array;
154 			Dub dub = new Dub(package_suppliers, root_path);
155 			dub.dryRun = annotate;
156 			
157 			// make the CWD package available so that for example sub packages can reference their
158 			// parent package.
159 			try dub.packageManager.getTemporaryPackage(Path(root_path), Version("~master"));
160 			catch (Exception e) { logDiagnostic("No package found in current working directory."); }
161 
162 			try return cmd.execute(dub, command_args.extractRemainingArgs(), app_args);
163 			catch (UsageException e) {
164 				logError("%s", e.msg);
165 				logDiagnostic("Full exception: %s", e.toString().sanitize);
166 				return 1;
167 			}
168 			catch (Throwable e) {
169 				logError("Error executing command %s: %s\n", cmd.name, e.msg);
170 				logDiagnostic("Full exception: %s", e.toString().sanitize);
171 				return 2;
172 			}
173 		}
174 	
175 	logError("Unknown command: %s", cmdname);
176 	writeln();
177 	showHelp(commands, common_args);
178 	return 1;
179 }
180 
181 class CommandArgs {
182 	struct Arg {
183 		Variant defaultValue;
184 		Variant value;
185 		string names;
186 		string[] helpText;
187 	}
188 	private {
189 		string[] m_args;
190 		Arg[] m_recognizedArgs;
191 	}
192 
193 	this(string[] args)
194 	{
195 		m_args = "dummy" ~ args;
196 	}
197 
198 	@property const(Arg)[] recognizedArgs() { return m_recognizedArgs; }
199 
200 	void getopt(T)(string names, T* var, string[] help_text = null)
201 	{
202 		foreach (ref arg; m_recognizedArgs)
203 			if (names == arg.names) {
204 				assert(help_text is null);
205 				*var = arg.value.get!T;
206 				return;
207 			}
208 		assert(help_text.length > 0);
209 		Arg arg;
210 		arg.defaultValue = *var;
211 		arg.names = names;
212 		arg.helpText = help_text;
213 		m_args.getopt(config.passThrough, names, var);
214 		arg.value = *var;
215 		m_recognizedArgs ~= arg;
216 	}
217 
218 	void dropAllArgs()
219 	{
220 		m_args = null;
221 	}
222 
223 	string[] extractRemainingArgs()
224 	{
225 		auto ret = m_args[1 .. $];
226 		m_args = null;
227 		return ret;
228 	}
229 }
230 
231 class Command {
232 	string name;
233 	string argumentsPattern;
234 	string description;
235 	string[] helpText;
236 	bool acceptsAppArgs;
237 	bool hidden = false; // used for deprecated commands
238 
239 	abstract void prepare(scope CommandArgs args);
240 	abstract int execute(Dub dub, string[] free_args, string[] app_args);
241 }
242 
243 struct CommandGroup {
244 	string caption;
245 	Command[] commands;
246 
247 	this(string caption, Command[] commands...)
248 	{
249 		this.caption = caption;
250 		this.commands = commands.dup;
251 	}
252 }
253 
254 
255 /******************************************************************************/
256 /* INIT                                                                       */
257 /******************************************************************************/
258 
259 class InitCommand : Command {
260 	this()
261 	{
262 		this.name = "init";
263 		this.argumentsPattern = "[<directory> [<type>]]";
264 		this.description = "Initializes an empty package skeleton";
265 		this.helpText = [
266 			"Initializes an empty package of the specified type in the given directory. By default, the current working dirctory is used. Available types:",
267 			"",
268 			"minimal - a simple \"hello world\" project with no dependencies (default)",
269 			"vibe.d - minimal HTTP server based on vibe.d"
270 		];
271 	}
272 
273 	override void prepare(scope CommandArgs args)
274 	{
275 	}
276 
277 	override int execute(Dub dub, string[] free_args, string[] app_args)
278 	{
279 		string dir, type = "minimal";
280 		enforceUsage(app_args.empty, "Unexpected application arguments.");
281 		enforceUsage(free_args.length <= 2, "Too many arguments.");
282 		if (free_args.length >= 1) dir = free_args[0];
283 		if (free_args.length >= 2) type = free_args[1];
284 		dub.createEmptyPackage(Path(dir), type);
285 		return 0;
286 	}
287 }
288 
289 
290 /******************************************************************************/
291 /* GENERATE / BUILD / RUN / TEST / DESCRIBE                                   */
292 /******************************************************************************/
293 
294 abstract class PackageBuildCommand : Command {
295 	protected {
296 		string m_build_type;
297 		string m_build_config;
298 		string m_compiler_name = "dmd";
299 		string m_arch;
300 		string[] m_debug_versions;
301 		Compiler m_compiler;
302 		BuildPlatform m_buildPlatform;
303 		BuildSettings m_buildSettings;
304 		string m_defaultConfig;
305 		bool m_nodeps;
306 	}
307 
308 	override void prepare(scope CommandArgs args)
309 	{
310 		args.getopt("b|build", &m_build_type, [
311 			"Specifies the type of build to perform. Note that setting the DFLAGS environment variable will override the build type with custom flags.",
312 			"Possible names:",
313 			"  debug (default), plain, release, release-nobounds, unittest, profile, docs, ddox, cov, unittest-cov and custom types"
314 		]);
315 		args.getopt("c|config", &m_build_config, [
316 			"Builds the specified configuration. Configurations can be defined in package.json"
317 		]);
318 		args.getopt("compiler", &m_compiler_name, [
319 			"Specifies the compiler binary to use. Arbitrary pre- and suffixes to the identifiers below are recognized (e.g. ldc2 or dmd-2.063) and matched to the proper compiler type:",
320 			"  dmd (default), gdc, ldc, gdmd, ldmd"
321 		]);
322 		args.getopt("a|arch", &m_arch, [
323 			"Force a different architecture (e.g. x86 or x86_64)"
324 		]);
325 		args.getopt("d|debug", &m_debug_versions, [
326 			"Define the specified debug version identifier when building - can be used multiple times"
327 		]);
328 		args.getopt("nodeps", &m_nodeps, [
329 			"Do not check/update dependencies before building"
330 		]);
331 	}
332 
333 	protected void setupPackage(Dub dub, string package_name)
334 	{
335 		m_compiler = getCompiler(m_compiler_name);
336 		m_buildPlatform = m_compiler.determinePlatform(m_buildSettings, m_compiler_name, m_arch);
337 		m_buildSettings.addDebugVersions(m_debug_versions);
338 
339 		m_defaultConfig = null;
340 		enforce (loadSpecificPackage(dub, package_name), "Failed to load package.");
341 
342 		enforce(m_build_config.length == 0 || dub.configurations.canFind(m_build_config), "Unknown build configuration: "~m_build_config);
343 
344 		if (m_build_type.length == 0) {
345 			if (environment.get("DFLAGS")) m_build_type = "$DFLAGS";
346 			else m_build_type = "debug";
347 		}
348 
349 		if (!m_nodeps) {
350 			logInfo("Checking dependencies in '%s'", dub.projectPath.toNativeString());
351 			dub.update(UpdateOptions.none);
352 		}
353 	}
354 
355 	private bool loadSpecificPackage(Dub dub, string package_name)
356 	{
357 		Package pack;
358 		if (!package_name.empty) {
359 			// load package in root_path to enable searching for sub packages
360 			loadCwdPackage(dub, null, false);
361 			pack = dub.packageManager.getFirstPackage(package_name);
362 			enforce(pack, "Failed to find a package named '"~package_name~"'.");
363 			logInfo("Building package %s in %s", pack.name, pack.path.toNativeString());
364 			dub.rootPath = pack.path;
365 		}
366 		if (!loadCwdPackage(dub, pack, true)) return false;
367 		return true;
368 	}
369 
370 	private bool loadCwdPackage(Dub dub, Package pack, bool warn_missing_package)
371 	{
372 		if (warn_missing_package) {
373 			bool found = existsFile(dub.rootPath ~ "source/app.d");
374 			if (!found)
375 				foreach (f; packageInfoFilenames)
376 					if (existsFile(dub.rootPath ~ f)) {
377 						found = true;
378 						break;
379 					}
380 			if (!found) {
381 				logInfo("");
382 				logInfo("Neither a package description file, nor source/app.d was found in");
383 				logInfo(dub.rootPath.toNativeString());
384 				logInfo("Please run DUB from the root directory of an existing package, or run");
385 				logInfo("\"dub init --help\" to get information on creating a new package.");
386 				logInfo("");
387 				return false;
388 			}
389 		}
390 
391 		if (pack) dub.loadPackage(pack);
392 		else dub.loadPackageFromCwd();
393 
394 		return true;
395 	}
396 }
397 
398 class GenerateCommand : PackageBuildCommand {
399 	protected {
400 		string m_generator;
401 		bool m_rdmd = false;
402 		bool m_run = false;
403 		bool m_force = false;
404 		bool m_combined = false;
405 		bool m_print_platform, m_print_builds, m_print_configs;
406 	}
407 
408 	this()
409 	{
410 		this.name = "generate";
411 		this.argumentsPattern = "<generator> [<package>]";
412 		this.description = "Generates project files using the specified generator";
413 		this.helpText = [
414 			"Generates project files using one of the supported generators:",
415 			"",
416 			"visuald - VisualD project files",
417 			"build - Builds the package directly",
418 			"",
419 			"An optional package name can be given to generate a different package than the root/CWD package."
420 		];
421 	}
422 
423 	override void prepare(scope CommandArgs args)
424 	{
425 		super.prepare(args);
426 
427 		args.getopt("combined", &m_combined, [
428 			"Tries to build the whole project in a single compiler run."
429 		]);
430 
431 		args.getopt("print-builds", &m_print_builds, [
432 			"Prints the list of available build types"
433 		]);
434 		args.getopt("print-configs", &m_print_configs, [
435 			"Prints the list of available configurations"
436 		]);
437 		args.getopt("print-platform", &m_print_platform, [
438 			"Prints the identifiers for the current build platform as used for the build fields in package.json"
439 		]);
440 	}
441 
442 	override int execute(Dub dub, string[] free_args, string[] app_args)
443 	{
444 		string package_name;
445 		if (!m_generator.length) {
446 			enforceUsage(free_args.length >= 1 && free_args.length <= 2, "Expected one or two arguments.");
447 			m_generator = free_args[0];
448 			if (free_args.length >= 2) package_name = free_args[1];
449 		} else {
450 			enforceUsage(free_args.length <= 1, "Expected one or zero arguments.");
451 			if (free_args.length >= 1) package_name = free_args[0];
452 		}
453 
454 		setupPackage(dub, package_name);
455 		
456 		if (m_print_builds) { // FIXME: use actual package data
457 			logInfo("Available build types:");
458 			foreach (tp; ["debug", "release", "unittest", "profile"])
459 				logInfo("  %s", tp);
460 			logInfo("");
461 		}
462 
463 		if (!m_nodeps) {
464 			logInfo("Checking dependencies in '%s'", dub.projectPath.toNativeString());
465 			dub.update(UpdateOptions.none);
466 		}
467 
468 		m_defaultConfig = dub.project.getDefaultConfiguration(m_buildPlatform);
469 		if (m_print_configs) {
470 			logInfo("Available configurations:");
471 			foreach (tp; dub.configurations)
472 				logInfo("  %s%s", tp, tp == m_defaultConfig ? " [default]" : null);
473 			logInfo("");
474 		}
475 
476 		GeneratorSettings gensettings;
477 		gensettings.platform = m_buildPlatform;
478 		gensettings.config = m_build_config.length ? m_build_config : m_defaultConfig;
479 		gensettings.buildType = m_build_type;
480 		gensettings.compiler = m_compiler;
481 		gensettings.buildSettings = m_buildSettings;
482 		gensettings.combined = m_combined;
483 		gensettings.run = m_run;
484 		gensettings.runArgs = app_args;
485 		gensettings.force = m_force;
486 		gensettings.rdmd = m_rdmd;
487 
488 		logDiagnostic("Generating using %s", m_generator);
489 		if (m_generator == "visuald-combined") {
490 			gensettings.combined = true;
491 			m_generator = "visuald";
492 			logWarn(`The generator "visuald-combined" is deprecated, please use the --combined switch instead.`);
493 		}
494 		dub.generateProject(m_generator, gensettings);
495 		if (m_build_type == "ddox") dub.runDdox(gensettings.run);
496 		return 0;
497 	}
498 }
499 
500 class BuildCommand : GenerateCommand {
501 	this()
502 	{
503 		this.name = "build";
504 		this.argumentsPattern = "[<package>]";
505 		this.description = "Builds a package (uses the main package in the current working directory by default)";
506 		this.helpText = [
507 			"Builds a package (uses the main package in the current working directory by default)"
508 		];
509 	}
510 
511 	override void prepare(scope CommandArgs args)
512 	{
513 		args.getopt("rdmd", &m_rdmd, [
514 			"Use rdmd instead of directly invoking the compiler"
515 		]);
516 		args.getopt("f|force", &m_force, [
517 			"Forces a recompilation even if the target is up to date"
518 		]);
519 		super.prepare(args);
520 		m_generator = "build";
521 	}
522 
523 	override int execute(Dub dub, string[] free_args, string[] app_args)
524 	{
525 		return super.execute(dub, free_args, app_args);
526 	}
527 }
528 
529 class RunCommand : BuildCommand {
530 	this()
531 	{
532 		this.name = "run";
533 		this.argumentsPattern = "[<package>]";
534 		this.description = "Builds and runs a package (default command)";
535 		this.helpText = [
536 			"Builds and runs a package (uses the main package in the current working directory by default)"
537 		];
538 		this.acceptsAppArgs = true;
539 	}
540 
541 	override void prepare(scope CommandArgs args)
542 	{
543 		super.prepare(args);
544 		m_run = true;
545 	}
546 
547 	override int execute(Dub dub, string[] free_args, string[] app_args)
548 	{
549 		return super.execute(dub, free_args, app_args);
550 	}
551 }
552 
553 class TestCommand : PackageBuildCommand {
554 	private {
555 		string m_mainFile;
556 	}
557 
558 	this()
559 	{
560 		this.name = "test";
561 		this.argumentsPattern = "[<package>]";
562 		this.description = "Executes the tests of the selected package";
563 		this.helpText = [
564 			"Builds a library configuration of the selected package and executes all contained unit tests."
565 		];
566 		this.acceptsAppArgs = true;
567 	}
568 
569 	override void prepare(scope CommandArgs args)
570 	{
571 		args.getopt("main-file", &m_mainFile, [
572 			"Specifies a custom file containing the main() function to use for running the tests."
573 		]);
574 		super.prepare(args);
575 	}
576 
577 	override int execute(Dub dub, string[] free_args, string[] app_args)
578 	{
579 		string package_name;
580 		enforceUsage(free_args.length <= 1, "Expected one or zero arguments.");
581 		if (free_args.length >= 1) package_name = free_args[0];
582 
583 		setupPackage(dub, package_name);
584 
585 		dub.testProject(m_buildSettings, m_buildPlatform, m_build_config, Path(m_mainFile), app_args);
586 		return 0;
587 	}
588 }
589 
590 class DescribeCommand : PackageBuildCommand {
591 	this()
592 	{
593 		this.name = "describe";
594 		this.argumentsPattern = "[<package>]";
595 		this.description = "Prints a JSON description of the project and its dependencies";
596 		this.helpText = [
597 			"Prints a JSON build description for the root package an all of their dependencies in a format similar to a JSON package description file. This is useful mostly for IDEs.",
598 			"All usual options that are also used for build/run/generate apply."
599 		];
600 	}
601 
602 	override void prepare(scope CommandArgs args)
603 	{
604 		super.prepare(args);
605 	}
606 
607 	override int execute(Dub dub, string[] free_args, string[] app_args)
608 	{
609 		string package_name;
610 		enforceUsage(free_args.length <= 1, "Expected one or zero arguments.");
611 		if (free_args.length >= 1) package_name = free_args[1];
612 
613 		setupPackage(dub, package_name);
614 
615 		if (!m_nodeps) {
616 			logInfo("Checking dependencies in '%s'", dub.projectPath.toNativeString());
617 			dub.update(UpdateOptions.none);
618 		}
619 
620 		m_defaultConfig = dub.project.getDefaultConfiguration(m_buildPlatform);
621 
622 		dub.describeProject(m_buildPlatform, m_build_config.length ? m_build_config : m_defaultConfig);				
623 		return 0;
624 	}
625 }
626 
627 
628 /******************************************************************************/
629 /* FETCH / REMOVE / UPGRADE                                                   */
630 /******************************************************************************/
631 
632 class UpgradeCommand : Command {
633 	private {
634 		bool m_prerelease = false;
635 	}
636 
637 	this()
638 	{
639 		this.name = "upgrade";
640 		this.argumentsPattern = "";
641 		this.description = "Forces an upgrade of all dependencies";
642 		this.helpText = [
643 			"Upgrades all dependencies of the package by querying the package registry(ies) for new versions."
644 		];
645 	}
646 
647 	override void prepare(scope CommandArgs args)
648 	{
649 		args.getopt("prerelease", &m_prerelease, [
650 			"Uses the latest pre-release version, even if release versions are available"
651 		]);
652 	}
653 
654 	override int execute(Dub dub, string[] free_args, string[] app_args)
655 	{
656 		enforceUsage(free_args.length == 0, "Unexpected arguments.");
657 		enforceUsage(app_args.length == 0, "Unexpected application arguments.");
658 		dub.loadPackageFromCwd();
659 		logInfo("Upgrading project in %s", dub.projectPath.toNativeString());
660 		auto options = UpdateOptions.upgrade;
661 		if (m_prerelease) options |= UpdateOptions.preRelease;
662 		dub.update(options);
663 		return 0;
664 	}
665 }
666 
667 class FetchRemoveCommand : Command {
668 	protected {
669 		string m_version;
670 		bool m_system = false;
671 		bool m_local = false;
672 	}
673 
674 	override void prepare(scope CommandArgs args)
675 	{
676 		args.getopt("version", &m_version, [
677 			"Use the specified version/branch instead of the latest available match",
678 			"The remove command also accepts \"*\" here as a wildcard to remove all versions of the package from the specified location"
679 		]);
680 
681 		args.getopt("system", &m_system, ["Puts the package into the system wide package cache instead of the user local one."]);
682 		args.getopt("local", &m_system, ["Puts the package into a sub folder of the current working directory. Cannot be mixed with --system."]);
683 	}
684 
685 	abstract override int execute(Dub dub, string[] free_args, string[] app_args);
686 }
687 
688 class FetchCommand : FetchRemoveCommand {
689 	this()
690 	{
691 		this.name = "fetch";
692 		this.argumentsPattern = "<name>";
693 		this.description = "Manually retrieves and caches a package";
694 		this.helpText = [
695 			"Note: Use the \"dependencies\" field in the package description file (e.g. package.json) if you just want to use a certain package as a dependency, you don't have to explicitly fetch packages.",
696 			"",
697 			"Explicit retrieval/removal of packages is only needed when you want to put packages to a place where several applications can share these. If you just have an dependency to a package, just add it to your package.json, dub will do the rest for you."
698 			"",
699 			"Without specified options, placement/removal will default to a user wide shared location."
700 			"",
701 			"Complete applications can be retrieved and run easily by e.g.",
702 			"$ dub fetch vibelog --local",
703 			"$ cd vibelog",
704 			"$ dub",
705 			""
706 			"This will grab all needed dependencies and compile and run the application.",
707 			"",
708 			"Note: DUB does not do a system installation of packages. Packages are instead only registered within DUB's internal ecosystem. Generation of native system packages/installers may be added later as a separate feature."
709 		];
710 	}
711 
712 	override void prepare(scope CommandArgs args)
713 	{
714 		super.prepare(args);
715 	}
716 
717 	override int execute(Dub dub, string[] free_args, string[] app_args)
718 	{
719 		enforceUsage(!m_local || !m_system, "--local and --system are exclusive to each other.");
720 		enforceUsage(free_args.length == 1, "Expecting exactly one argument.");
721 		enforceUsage(app_args.length == 0, "Unexpected application arguments.");
722 
723 		auto location = PlacementLocation.userWide;
724 		if (m_local) location = PlacementLocation.local;
725 		else if (m_system) location = PlacementLocation.systemWide;
726 
727 		auto name = free_args[0];
728 
729 		if (m_version.length) dub.fetch(name, Dependency(m_version), location, true, false);
730 		else {
731 			try {
732 				dub.fetch(name, Dependency(">=0.0.0"), location, true, false);
733 				logInfo(
734 					"Please note that you need to use `dub run <pkgname>` " ~ 
735 					"or add it to dependencies of your package to actually use/run it. " ~
736 					"dub does not do actual installation of packages outside of its own ecosystem.");
737 			}
738 			catch(Exception e){
739 				logInfo("Getting a release version failed: %s", e.msg);
740 				logInfo("Retry with ~master...");
741 				dub.fetch(name, Dependency("~master"), location, true, true);
742 			}
743 		}
744 		return 0;
745 	}
746 }
747 
748 class InstallCommand : FetchCommand {
749 	this() { this.name = "install"; hidden = true; }
750 	override void prepare(scope CommandArgs args) { super.prepare(args); }
751 	override int execute(Dub dub, string[] free_args, string[] app_args)
752 	{
753 		warnRenamed("install", "fetch");
754 		return super.execute(dub, free_args, app_args);
755 	}
756 }
757 
758 class RemoveCommand : FetchRemoveCommand {
759 	this()
760 	{
761 		this.name = "remove";
762 		this.argumentsPattern = "<name>";
763 		this.description = "Removes a cached package";
764 		this.helpText = [
765 			"Removes a package that is cached on the local system."
766 		];
767 	}
768 
769 	override void prepare(scope CommandArgs args)
770 	{
771 		super.prepare(args);
772 	}
773 
774 	override int execute(Dub dub, string[] free_args, string[] app_args)
775 	{
776 		enforceUsage(!m_local || !m_system, "--local and --system are exclusive to each other.");
777 		enforceUsage(free_args.length == 1, "Expecting exactly one argument.");
778 		enforceUsage(app_args.length == 0, "Unexpected application arguments.");
779 
780 		auto package_id = free_args[0];
781 		auto location = PlacementLocation.userWide;
782 		if (m_local) location = PlacementLocation.local;
783 		else if (m_system) location = PlacementLocation.systemWide;
784 
785 		try dub.remove(package_id, m_version, location);
786 		catch {
787 			logError("Please specify a individual version or use the wildcard identifier '%s' (without quotes).", Dub.RemoveVersionWildcard);
788 			return 1;
789 		}
790 
791 		return 0;
792 	}
793 }
794 
795 class UninstallCommand : RemoveCommand {
796 	this() { this.name = "uninstall"; hidden = true; }
797 	override void prepare(scope CommandArgs args) { super.prepare(args); }
798 	override int execute(Dub dub, string[] free_args, string[] app_args)
799 	{
800 		warnRenamed("uninstall", "remove");
801 		return super.execute(dub, free_args, app_args);
802 	}
803 }
804 
805 
806 /******************************************************************************/
807 /* ADD/REMOVE PATH/LOCAL                                                      */
808 /******************************************************************************/
809 
810 abstract class RegistrationCommand : Command {
811 	private {
812 		bool m_system;
813 	}
814 
815 	override void prepare(scope CommandArgs args)
816 	{
817 		args.getopt("system", &m_system, [
818 			"Register system-wide instead of user-wide"
819 		]);
820 	}
821 
822 	abstract override int execute(Dub dub, string[] free_args, string[] app_args);
823 }
824 
825 class AddPathCommand : RegistrationCommand {
826 	this()
827 	{
828 		this.name = "add-path";
829 		this.argumentsPattern = "<path>";
830 		this.description = "Adds a default package search path";
831 		this.helpText = ["Adds a default package search path"];
832 	}
833 
834 	override int execute(Dub dub, string[] free_args, string[] app_args)
835 	{
836 		enforceUsage(free_args.length == 1, "Missing search path.");
837 		dub.addSearchPath(free_args[0], m_system);
838 		return 0;
839 	}
840 }
841 
842 class RemovePathCommand : RegistrationCommand {
843 	this()
844 	{
845 		this.name = "remove-path";
846 		this.argumentsPattern = "<path>";
847 		this.description = "Removes a package search path";
848 		this.helpText = ["Removes a package search path"];
849 	}
850 
851 	override int execute(Dub dub, string[] free_args, string[] app_args)
852 	{
853 		enforceUsage(free_args.length == 1, "Expected one argument.");
854 		dub.removeSearchPath(free_args[0], m_system);
855 		return 0;
856 	}
857 }
858 
859 class AddLocalCommand : RegistrationCommand {
860 	this()
861 	{
862 		this.name = "add-local";
863 		this.argumentsPattern = "<path> [<version>]";
864 		this.description = "Adds a local package directory (e.g. a git repository)";
865 		this.helpText = ["Adds a local package directory (e.g. a git repository)"];
866 	}
867 
868 	override int execute(Dub dub, string[] free_args, string[] app_args)
869 	{
870 		enforceUsage(free_args.length == 1 || free_args.length == 2, "Expecting one or two arguments.");
871 		string ver = free_args.length == 2 ? free_args[1] : null;
872 		dub.addLocalPackage(free_args[0], ver, m_system);
873 		return 0;
874 	}
875 }
876 
877 class RemoveLocalCommand : RegistrationCommand {
878 	this()
879 	{
880 		this.name = "remove-local";
881 		this.argumentsPattern = "<path>";
882 		this.description = "Removes a local package directory";
883 		this.helpText = ["Removes a local package directory"];
884 	}
885 
886 	override int execute(Dub dub, string[] free_args, string[] app_args)
887 	{
888 		enforceUsage(free_args.length == 1, "Missing path to package.");
889 		dub.removeLocalPackage(free_args[0], m_system);
890 		return 0;
891 	}
892 }
893 
894 
895 /******************************************************************************/
896 /* LIST                                                                       */
897 /******************************************************************************/
898 
899 class ListCommand : Command {
900 	this()
901 	{
902 		this.name = "list";
903 		this.argumentsPattern = "";
904 		this.description = "Prints a list of all local packages dub is aware of";
905 		this.helpText = [
906 			"Prints a list of all local packages. This includes all cached packages (user or system wide), all packages in the package search paths (\"dub add-path\") and all manually registered packages (\"dub add-local\")."
907 		];
908 	}
909 	override void prepare(scope CommandArgs args) {}
910 	override int execute(Dub dub, string[] free_args, string[] app_args)
911 	{
912 		logInfo("Packages present in the system and known to dub:");
913 		foreach (p; dub.packageManager.getPackageIterator())
914 			logInfo("  %s %s: %s", p.name, p.ver, p.path.toNativeString());
915 		logInfo("");
916 		return true;
917 	}
918 }
919 
920 class ListInstalledCommand : ListCommand {
921 	this() { this.name = "list-installed"; hidden = true; }
922 	override void prepare(scope CommandArgs args) { super.prepare(args); }
923 	override int execute(Dub dub, string[] free_args, string[] app_args)
924 	{
925 		warnRenamed("list-installed", "list");
926 		return super.execute(dub, free_args, app_args);
927 	}
928 }
929 
930 
931 /******************************************************************************/
932 /* HELP                                                                       */
933 /******************************************************************************/
934 
935 private {
936 	enum shortArgColumn = 2;
937 	enum longArgColumn = 6;
938 	enum descColumn = 24;
939 	enum lineWidth = 80;
940 }
941 
942 private void showHelp(in CommandGroup[] commands, CommandArgs common_args)
943 {
944 	writeln(
945 `USAGE: dub [<command>] [<options...>] [-- [<application arguments...>]]
946 
947 Manages the DUB project in the current directory. If the command is omitted,
948 DUB will default to "run". When running an application, "--" can be used to
949 separate DUB options from options passed to the application. 
950 
951 Run "dub <command> --help" to get help for a specific command.
952 
953 You can use the "http_proxy" environment variable to configure a proxy server
954 to be used for fetching packages.
955 
956 
957 Available commands
958 ==================`);
959 
960 	foreach (grp; commands) {
961 		writeln();
962 		writeWS(shortArgColumn);
963 		writeln(grp.caption);
964 		writeWS(shortArgColumn);
965 		writerep!'-'(grp.caption.length);
966 		writeln();
967 		foreach (cmd; grp.commands) {
968 			if (cmd.hidden) continue;
969 			writeWS(shortArgColumn);
970 			writef("%s %s", cmd.name, cmd.argumentsPattern);
971 			auto chars_output = cmd.name.length + cmd.argumentsPattern.length + shortArgColumn + 1;
972 			if (chars_output < descColumn) {
973 				writeWS(descColumn - chars_output);
974 			} else {
975 				writeln();
976 				writeWS(descColumn);
977 			}
978 			writeWrapped(cmd.description, descColumn, descColumn);
979 		}
980 	}
981 	writeln();
982 	writeln();
983 	writeln(`Common options`);
984 	writeln(`==============`);
985 	writeln();
986 	writeOptions(common_args);
987 	writeln();
988 	writefln("DUB version %s", getDUBVersion());
989 }
990 
991 private void showCommandHelp(Command cmd, CommandArgs args, CommandArgs common_args)
992 {
993 	writefln(`USAGE: dub %s %s [<options...>]%s`, cmd.name, cmd.argumentsPattern, cmd.acceptsAppArgs ? " [-- <application arguments...>]": null);
994 	writeln();
995 	foreach (ln; cmd.helpText)
996 		ln.writeWrapped();
997 	
998 	if (args.recognizedArgs.length) {
999 		writeln();
1000 		writeln();
1001 		writeln("Command specific options");
1002 		writeln("========================");
1003 		writeln();
1004 		writeOptions(args);
1005 	}
1006 	
1007 	writeln();
1008 	writeln();
1009 	writeln("Common options");
1010 	writeln("==============");
1011 	writeln();
1012 	writeOptions(common_args);
1013 	writeln();
1014 	writefln("DUB version %s", getDUBVersion());
1015 }
1016 
1017 private void writeOptions(CommandArgs args)
1018 {
1019 	foreach (arg; args.recognizedArgs) {
1020 		auto names = arg.names.split("|");
1021 		assert(names.length == 1 || names.length == 2);
1022 		string sarg = names[0].length == 1 ? names[0] : null;
1023 		string larg = names[0].length > 1 ? names[0] : names.length > 1 ? names[1] : null;
1024 		if (sarg) {
1025 			writeWS(shortArgColumn);
1026 			writef("-%s", sarg);
1027 			writeWS(longArgColumn - shortArgColumn - 2);
1028 		} else writeWS(longArgColumn);
1029 		size_t col = longArgColumn;
1030 		if (larg) {
1031 			if (arg.defaultValue.peek!bool) {
1032 				writef("--%s", larg);
1033 				col += larg.length + 2;
1034 			} else {
1035 				writef("--%s=VALUE", larg);
1036 				col += larg.length + 8;
1037 			}
1038 		}
1039 		if (col < descColumn) {
1040 			writeWS(descColumn - col);
1041 		} else {
1042 			writeln();
1043 			writeWS(descColumn);
1044 		}
1045 		foreach (i, ln; arg.helpText) {
1046 			if (i > 0) writeWS(descColumn);
1047 			ln.writeWrapped(descColumn, descColumn);
1048 		}
1049 	}
1050 }
1051 
1052 private void writeWrapped(string string, size_t indent = 0, size_t first_line_pos = 0)
1053 {
1054 	auto wrapped = string.wrap(lineWidth, getRepString!' '(first_line_pos), getRepString!' '(indent));
1055 	wrapped = wrapped[first_line_pos .. $];
1056 	foreach (ln; wrapped.splitLines())
1057 		writeln(ln);
1058 }
1059 
1060 private void writeWS(size_t num) { writerep!' '(num); }
1061 private void writerep(char ch)(size_t num) { write(getRepString!ch(num)); }
1062 
1063 private string getRepString(char ch)(size_t len)
1064 {
1065 	static string buf;
1066 	if (len > buf.length) buf ~= [ch].replicate(len-buf.length);
1067 	return buf[0 .. len];
1068 }
1069 
1070 /***
1071 */
1072 
1073 
1074 private void enforceUsage(bool cond, string text)
1075 {
1076 	if (!cond) throw new UsageException(text);
1077 }
1078 
1079 private class UsageException : Exception {
1080 	this(string message, string file = __FILE__, int line = __LINE__, Throwable next = null)
1081 	{
1082 		super(message, file, line, next);
1083 	}
1084 }
1085 
1086 private void warnRenamed(string prev, string curr)
1087 {
1088 	logWarn("The '%s' Command was renamed to '%s'. Please update your scripts.", prev, curr);
1089 }