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