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 // 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 }