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 sepected 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, 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 && !existsFile(dub.rootPath~"package.json") && !existsFile(dub.rootPath~"source/app.d")) { 373 logInfo(""); 374 logInfo("Neither package.json, nor source/app.d was found in the current directory."); 375 logInfo("Please run dub from the root directory of an existing package, or create a new"); 376 logInfo("package using \"dub init <name>\"."); 377 logInfo(""); 378 return false; 379 } 380 381 if (pack) dub.loadPackage(pack); 382 else dub.loadPackageFromCwd(); 383 384 m_defaultConfig = dub.getDefaultConfiguration(m_buildPlatform); 385 386 return true; 387 } 388 } 389 390 class GenerateCommand : PackageBuildCommand { 391 protected { 392 string m_generator; 393 bool m_rdmd = false; 394 bool m_run = false; 395 bool m_force = false; 396 bool m_print_platform, m_print_builds, m_print_configs; 397 } 398 399 this() 400 { 401 this.name = "generate"; 402 this.argumentsPattern = "<generator> [<package>]"; 403 this.description = "Generates project files using the specified generator"; 404 this.helpText = [ 405 "Generates project files using one of the supported generators:", 406 "", 407 "visuald - VisualD project files", 408 "visuald-combined - VisualD single project file", 409 "build - Builds the package directly", 410 "", 411 "An optional package name can be given to generate a different package than the root/CWD package." 412 ]; 413 } 414 415 override void prepare(scope CommandArgs args) 416 { 417 super.prepare(args); 418 419 args.getopt("print-builds", &m_print_builds, [ 420 "Prints the list of available build types" 421 ]); 422 args.getopt("print-configs", &m_print_configs, [ 423 "Prints the list of available configurations" 424 ]); 425 args.getopt("print-platform", &m_print_platform, [ 426 "Prints the identifiers for the current build platform as used for the build fields in package.json" 427 ]); 428 } 429 430 override int execute(Dub dub, string[] free_args, string[] app_args) 431 { 432 string package_name; 433 if (!m_generator.length) { 434 enforceUsage(free_args.length >= 1 && free_args.length <= 2, "Expected one or two arguments."); 435 m_generator = free_args[0]; 436 if (free_args.length >= 2) package_name = free_args[1]; 437 } else { 438 enforceUsage(free_args.length <= 1, "Expected one or zero arguments."); 439 if (free_args.length >= 1) package_name = free_args[0]; 440 } 441 442 setupPackage(dub, package_name); 443 444 if (m_print_builds) { // FIXME: use actual package data 445 logInfo("Available build types:"); 446 foreach (tp; ["debug", "release", "unittest", "profile"]) 447 logInfo(" %s", tp); 448 logInfo(""); 449 } 450 451 if (m_print_configs) { 452 logInfo("Available configurations:"); 453 foreach (tp; dub.configurations) 454 logInfo(" %s%s", tp, tp == m_defaultConfig ? " [default]" : null); 455 logInfo(""); 456 } 457 458 if (!m_nodeps) { 459 logInfo("Checking dependencies in '%s'", dub.projectPath.toNativeString()); 460 dub.update(UpdateOptions.none); 461 } 462 463 GeneratorSettings gensettings; 464 gensettings.platform = m_buildPlatform; 465 gensettings.config = m_build_config.length ? m_build_config : m_defaultConfig; 466 gensettings.buildType = m_build_type; 467 gensettings.compiler = m_compiler; 468 gensettings.buildSettings = m_buildSettings; 469 gensettings.run = m_run; 470 gensettings.runArgs = app_args; 471 gensettings.force = m_force; 472 gensettings.rdmd = m_rdmd; 473 474 logDiagnostic("Generating using %s", m_generator); 475 dub.generateProject(m_generator, gensettings); 476 if (m_build_type == "ddox") dub.runDdox(gensettings.run); 477 return 0; 478 } 479 } 480 481 class BuildCommand : GenerateCommand { 482 this() 483 { 484 this.name = "build"; 485 this.argumentsPattern = "[<package>]"; 486 this.description = "Builds a package (uses the main package in the current working directory by default)"; 487 this.helpText = [ 488 "Builds a package (uses the main package in the current working directory by default)" 489 ]; 490 } 491 492 override void prepare(scope CommandArgs args) 493 { 494 args.getopt("rdmd", &m_rdmd, [ 495 "Use rdmd instead of directly invoking the compiler" 496 ]); 497 args.getopt("f|force", &m_force, [ 498 "Forces a recompilation even if the target is up to date" 499 ]); 500 super.prepare(args); 501 m_generator = "build"; 502 } 503 504 override int execute(Dub dub, string[] free_args, string[] app_args) 505 { 506 return super.execute(dub, free_args, app_args); 507 } 508 } 509 510 class RunCommand : BuildCommand { 511 this() 512 { 513 this.name = "run"; 514 this.argumentsPattern = "[<package>]"; 515 this.description = "Builds and runs a package (default command)"; 516 this.helpText = [ 517 "Builds and runs a package (uses the main package in the current working directory by default)" 518 ]; 519 this.acceptsAppArgs = true; 520 } 521 522 override void prepare(scope CommandArgs args) 523 { 524 super.prepare(args); 525 m_run = true; 526 } 527 528 override int execute(Dub dub, string[] free_args, string[] app_args) 529 { 530 return super.execute(dub, free_args, app_args); 531 } 532 } 533 534 class TestCommand : PackageBuildCommand { 535 private { 536 string m_mainFile; 537 } 538 539 this() 540 { 541 this.name = "test"; 542 this.argumentsPattern = "[<package>]"; 543 this.description = "Executes the tests of the selected package"; 544 this.helpText = [ 545 "Builds a library configuration of the selected package and executes all contained unit tests." 546 ]; 547 this.acceptsAppArgs = true; 548 } 549 550 override void prepare(scope CommandArgs args) 551 { 552 args.getopt("main-file", &m_mainFile, [ 553 "Specifies a custom file containing the main() function to use for running the tests." 554 ]); 555 super.prepare(args); 556 } 557 558 override int execute(Dub dub, string[] free_args, string[] app_args) 559 { 560 string package_name; 561 enforceUsage(free_args.length <= 1, "Expected one or zero arguments."); 562 if (free_args.length >= 1) package_name = free_args[0]; 563 564 setupPackage(dub, package_name); 565 566 dub.testProject(m_buildSettings, m_buildPlatform, m_build_config, Path(m_mainFile), app_args); 567 return 0; 568 } 569 } 570 571 class DescribeCommand : PackageBuildCommand { 572 this() 573 { 574 this.name = "describe"; 575 this.argumentsPattern = "[<package>]"; 576 this.description = "Prints a JSON description of the project and its dependencies"; 577 this.helpText = [ 578 "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.", 579 "All usual options that are also used for build/run/generate apply." 580 ]; 581 } 582 583 override void prepare(scope CommandArgs args) 584 { 585 super.prepare(args); 586 } 587 588 override int execute(Dub dub, string[] free_args, string[] app_args) 589 { 590 string package_name; 591 enforceUsage(free_args.length <= 1, "Expected one or zero arguments."); 592 if (free_args.length >= 1) package_name = free_args[1]; 593 594 setupPackage(dub, package_name); 595 596 if (!m_nodeps) { 597 logInfo("Checking dependencies in '%s'", dub.projectPath.toNativeString()); 598 dub.update(UpdateOptions.none); 599 } 600 601 dub.describeProject(m_buildPlatform, m_build_config.length ? m_build_config : m_defaultConfig); 602 return 0; 603 } 604 } 605 606 607 /******************************************************************************/ 608 /* FETCH / REMOVE / UPGRADE */ 609 /******************************************************************************/ 610 611 class UpgradeCommand : Command { 612 private { 613 bool m_prerelease = false; 614 } 615 616 this() 617 { 618 this.name = "upgrade"; 619 this.argumentsPattern = ""; 620 this.description = "Forces an upgrade of all dependencies"; 621 this.helpText = [ 622 "Upgrades all dependencies of the package by querying the package registry(ies) for new versions." 623 ]; 624 } 625 626 override void prepare(scope CommandArgs args) 627 { 628 args.getopt("prerelease", &m_prerelease, [ 629 "Uses the latest pre-release version, even if release versions are available" 630 ]); 631 } 632 633 override int execute(Dub dub, string[] free_args, string[] app_args) 634 { 635 enforceUsage(free_args.length == 0, "Unexpected arguments."); 636 enforceUsage(app_args.length == 0, "Unexpected application arguments."); 637 dub.loadPackageFromCwd(); 638 logInfo("Upgrading project in %s", dub.projectPath.toNativeString()); 639 auto options = UpdateOptions.upgrade; 640 if (m_prerelease) options |= UpdateOptions.preRelease; 641 dub.update(options); 642 return 0; 643 } 644 } 645 646 class FetchRemoveCommand : Command { 647 protected { 648 string m_version; 649 bool m_system = false; 650 bool m_local = false; 651 } 652 653 override void prepare(scope CommandArgs args) 654 { 655 args.getopt("version", &m_version, [ 656 "Use the specified version/branch instead of the latest available match", 657 "The remove command also accepts \"*\" here as a wildcard to remove all versions of the package from the specified location" 658 ]); 659 660 args.getopt("system", &m_system, ["Puts the package into the system wide package cache instead of the user local one."]); 661 args.getopt("local", &m_system, ["Puts the package into a sub folder of the current working directory. Cannot be mixed with --system."]); 662 } 663 664 abstract override int execute(Dub dub, string[] free_args, string[] app_args); 665 } 666 667 class FetchCommand : FetchRemoveCommand { 668 this() 669 { 670 this.name = "fetch"; 671 this.argumentsPattern = "<name>"; 672 this.description = "Manually retrieves and caches a package"; 673 this.helpText = [ 674 "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.", 675 "", 676 "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." 677 "", 678 "Without specified options, placement/removal will default to a user wide shared location." 679 "", 680 "Complete applications can be retrieved and run easily by e.g.", 681 "$ dub fetch vibelog --local", 682 "$ cd vibelog", 683 "$ dub", 684 "" 685 "This will grab all needed dependencies and compile and run the application.", 686 "", 687 "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." 688 ]; 689 } 690 691 override void prepare(scope CommandArgs args) 692 { 693 super.prepare(args); 694 } 695 696 override int execute(Dub dub, string[] free_args, string[] app_args) 697 { 698 enforceUsage(!m_local || !m_system, "--local and --system are exclusive to each other."); 699 enforceUsage(free_args.length == 1, "Expecting exactly one argument."); 700 enforceUsage(app_args.length == 0, "Unexpected application arguments."); 701 702 auto location = PlacementLocation.userWide; 703 if (m_local) location = PlacementLocation.local; 704 else if (m_system) location = PlacementLocation.systemWide; 705 706 auto name = free_args[0]; 707 708 if (m_version.length) dub.fetch(name, Dependency(m_version), location, true, false); 709 else { 710 try { 711 dub.fetch(name, Dependency(">=0.0.0"), location, true, false); 712 logInfo( 713 "Please note that you need to use `dub run <pkgname>` " ~ 714 "or add it to dependencies of your package to actually use/run it. " ~ 715 "dub does not do actual installation of packages outside of its own ecosystem."); 716 } 717 catch(Exception e){ 718 logInfo("Getting a release version failed: %s", e.msg); 719 logInfo("Retry with ~master..."); 720 dub.fetch(name, Dependency("~master"), location, true, true); 721 } 722 } 723 return 0; 724 } 725 } 726 727 class InstallCommand : FetchCommand { 728 this() { this.name = "install"; hidden = true; } 729 override void prepare(scope CommandArgs args) { super.prepare(args); } 730 override int execute(Dub dub, string[] free_args, string[] app_args) 731 { 732 warnRenamed("install", "fetch"); 733 return super.execute(dub, free_args, app_args); 734 } 735 } 736 737 class RemoveCommand : FetchRemoveCommand { 738 this() 739 { 740 this.name = "remove"; 741 this.argumentsPattern = "<name>"; 742 this.description = "Removes a cached package"; 743 this.helpText = [ 744 "Removes a package that is cached on the local system." 745 ]; 746 } 747 748 override void prepare(scope CommandArgs args) 749 { 750 super.prepare(args); 751 } 752 753 override int execute(Dub dub, string[] free_args, string[] app_args) 754 { 755 enforceUsage(!m_local || !m_system, "--local and --system are exclusive to each other."); 756 enforceUsage(free_args.length == 1, "Expecting exactly one argument."); 757 enforceUsage(app_args.length == 0, "Unexpected application arguments."); 758 759 auto package_id = free_args[0]; 760 auto location = PlacementLocation.userWide; 761 if (m_local) location = PlacementLocation.local; 762 else if (m_system) location = PlacementLocation.systemWide; 763 764 try dub.remove(package_id, m_version, location); 765 catch { 766 logError("Please specify a individual version or use the wildcard identifier '%s' (without quotes).", Dub.RemoveVersionWildcard); 767 return 1; 768 } 769 770 return 0; 771 } 772 } 773 774 class UninstallCommand : RemoveCommand { 775 this() { this.name = "uninstall"; hidden = true; } 776 override void prepare(scope CommandArgs args) { super.prepare(args); } 777 override int execute(Dub dub, string[] free_args, string[] app_args) 778 { 779 warnRenamed("uninstall", "remove"); 780 return super.execute(dub, free_args, app_args); 781 } 782 } 783 784 785 /******************************************************************************/ 786 /* ADD/REMOVE PATH/LOCAL */ 787 /******************************************************************************/ 788 789 abstract class RegistrationCommand : Command { 790 private { 791 bool m_system; 792 } 793 794 override void prepare(scope CommandArgs args) 795 { 796 args.getopt("system", &m_system, [ 797 "Register system-wide instead of user-wide" 798 ]); 799 } 800 801 abstract override int execute(Dub dub, string[] free_args, string[] app_args); 802 } 803 804 class AddPathCommand : RegistrationCommand { 805 this() 806 { 807 this.name = "add-path"; 808 this.argumentsPattern = "<path>"; 809 this.description = "Adds a default package search path"; 810 this.helpText = ["Adds a default package search path"]; 811 } 812 813 override int execute(Dub dub, string[] free_args, string[] app_args) 814 { 815 enforceUsage(free_args.length == 1, "Missing search path."); 816 dub.addSearchPath(free_args[0], m_system); 817 return 0; 818 } 819 } 820 821 class RemovePathCommand : RegistrationCommand { 822 this() 823 { 824 this.name = "remove-path"; 825 this.argumentsPattern = "<path>"; 826 this.description = "Removes a package search path"; 827 this.helpText = ["Removes a package search path"]; 828 } 829 830 override int execute(Dub dub, string[] free_args, string[] app_args) 831 { 832 enforceUsage(free_args.length == 1, "Expected one argument."); 833 dub.removeSearchPath(free_args[0], m_system); 834 return 0; 835 } 836 } 837 838 class AddLocalCommand : RegistrationCommand { 839 this() 840 { 841 this.name = "add-local"; 842 this.argumentsPattern = "<path> <version>"; 843 this.description = "Adds a local package directory (e.g. a git repository)"; 844 this.helpText = ["Adds a local package directory (e.g. a git repository)"]; 845 } 846 847 override int execute(Dub dub, string[] free_args, string[] app_args) 848 { 849 enforceUsage(free_args.length == 2, "Expecting two arguments."); 850 dub.addLocalPackage(free_args[0], free_args[1], m_system); 851 return 0; 852 } 853 } 854 855 class RemoveLocalCommand : RegistrationCommand { 856 this() 857 { 858 this.name = "remove-local"; 859 this.argumentsPattern = "<path>"; 860 this.description = "Removes a local package directory"; 861 this.helpText = ["Removes a local package directory"]; 862 } 863 864 override int execute(Dub dub, string[] free_args, string[] app_args) 865 { 866 enforceUsage(free_args.length == 1, "Missing path to package."); 867 dub.removeLocalPackage(free_args[0], m_system); 868 return 0; 869 } 870 } 871 872 873 /******************************************************************************/ 874 /* LIST */ 875 /******************************************************************************/ 876 877 class ListCommand : Command { 878 this() 879 { 880 this.name = "list"; 881 this.argumentsPattern = ""; 882 this.description = "Prints a list of all local packages dub is aware of"; 883 this.helpText = [ 884 "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\")." 885 ]; 886 } 887 override void prepare(scope CommandArgs args) {} 888 override int execute(Dub dub, string[] free_args, string[] app_args) 889 { 890 logInfo("Packages present in the system and known to dub:"); 891 foreach (p; dub.packageManager.getPackageIterator()) 892 logInfo(" %s %s: %s", p.name, p.ver, p.path.toNativeString()); 893 logInfo(""); 894 return true; 895 } 896 } 897 898 class ListInstalledCommand : ListCommand { 899 this() { this.name = "list-installed"; hidden = true; } 900 override void prepare(scope CommandArgs args) { super.prepare(args); } 901 override int execute(Dub dub, string[] free_args, string[] app_args) 902 { 903 warnRenamed("list-installed", "list"); 904 return super.execute(dub, free_args, app_args); 905 } 906 } 907 908 909 /******************************************************************************/ 910 /* HELP */ 911 /******************************************************************************/ 912 913 private { 914 enum shortArgColumn = 2; 915 enum longArgColumn = 6; 916 enum descColumn = 24; 917 enum lineWidth = 80; 918 } 919 920 private void showHelp(in CommandGroup[] commands, CommandArgs common_args) 921 { 922 writeln( 923 `USAGE: dub [<command>] [<options...>] [-- [<application arguments...>]] 924 925 Manages the DUB project in the current directory. If the command is omitted, 926 DUB will default to "run". When running an application, "--" can be used to 927 separate DUB options from options passed to the application. 928 929 Run "dub <command> --help" to get help for a specific command. 930 931 You can use the "http_proxy" environment variable to configure a proxy server 932 to be used for fetching packages. 933 934 935 Available commands 936 ==================`); 937 938 foreach (grp; commands) { 939 writeln(); 940 writeWS(shortArgColumn); 941 writeln(grp.caption); 942 writeWS(shortArgColumn); 943 writerep!'-'(grp.caption.length); 944 writeln(); 945 foreach (cmd; grp.commands) { 946 if (cmd.hidden) continue; 947 writeWS(shortArgColumn); 948 writef("%s %s", cmd.name, cmd.argumentsPattern); 949 auto chars_output = cmd.name.length + cmd.argumentsPattern.length + shortArgColumn + 1; 950 if (chars_output < descColumn) { 951 writeWS(descColumn - chars_output); 952 } else { 953 writeln(); 954 writeWS(descColumn); 955 } 956 writeWrapped(cmd.description, descColumn, descColumn); 957 } 958 } 959 writeln(); 960 writeln(); 961 writeln(`Common options`); 962 writeln(`==============`); 963 writeln(); 964 writeOptions(common_args); 965 writeln(); 966 writefln("DUB version %s", getDUBVersion()); 967 } 968 969 private void showCommandHelp(Command cmd, CommandArgs args, CommandArgs common_args) 970 { 971 writefln(`USAGE: dub %s %s [<options...>]%s`, cmd.name, cmd.argumentsPattern, cmd.acceptsAppArgs ? " [-- <application arguments...>]": null); 972 writeln(); 973 foreach (ln; cmd.helpText) 974 ln.writeWrapped(); 975 976 if (args.recognizedArgs.length) { 977 writeln(); 978 writeln(); 979 writeln("Command specific options"); 980 writeln("========================"); 981 writeln(); 982 writeOptions(args); 983 } 984 985 writeln(); 986 writeln(); 987 writeln("Common options"); 988 writeln("=============="); 989 writeln(); 990 writeOptions(common_args); 991 writeln(); 992 writefln("DUB version %s", getDUBVersion()); 993 } 994 995 private void writeOptions(CommandArgs args) 996 { 997 foreach (arg; args.recognizedArgs) { 998 auto names = arg.names.split("|"); 999 assert(names.length == 1 || names.length == 2); 1000 string sarg = names[0].length == 1 ? names[0] : null; 1001 string larg = names[0].length > 1 ? names[0] : names.length > 1 ? names[1] : null; 1002 if (sarg) { 1003 writeWS(shortArgColumn); 1004 writef("-%s", sarg); 1005 writeWS(longArgColumn - shortArgColumn - 2); 1006 } else writeWS(longArgColumn); 1007 size_t col = longArgColumn; 1008 if (larg) { 1009 if (arg.defaultValue.peek!bool) { 1010 writef("--%s", larg); 1011 col += larg.length + 2; 1012 } else { 1013 writef("--%s=VALUE", larg); 1014 col += larg.length + 8; 1015 } 1016 } 1017 if (col < descColumn) { 1018 writeWS(descColumn - col); 1019 } else { 1020 writeln(); 1021 writeWS(descColumn); 1022 } 1023 foreach (i, ln; arg.helpText) { 1024 if (i > 0) writeWS(descColumn); 1025 ln.writeWrapped(descColumn, descColumn); 1026 } 1027 } 1028 } 1029 1030 private void writeWrapped(string string, size_t indent = 0, size_t first_line_pos = 0) 1031 { 1032 auto wrapped = string.wrap(lineWidth, getRepString!' '(first_line_pos), getRepString!' '(indent)); 1033 wrapped = wrapped[first_line_pos .. $]; 1034 foreach (ln; wrapped.splitLines()) 1035 writeln(ln); 1036 } 1037 1038 private void writeWS(size_t num) { writerep!' '(num); } 1039 private void writerep(char ch)(size_t num) { write(getRepString!ch(num)); } 1040 1041 private string getRepString(char ch)(size_t len) 1042 { 1043 static string buf; 1044 if (len > buf.length) buf ~= [ch].replicate(len-buf.length); 1045 return buf[0 .. len]; 1046 } 1047 1048 /*** 1049 */ 1050 1051 1052 private void enforceUsage(bool cond, string text) 1053 { 1054 if (!cond) throw new UsageException(text); 1055 } 1056 1057 private class UsageException : Exception { 1058 this(string message, string file = __FILE__, int line = __LINE__, Throwable next = null) 1059 { 1060 super(message, file, line, next); 1061 } 1062 } 1063 1064 private void warnRenamed(string prev, string curr) 1065 { 1066 logWarn("The '%s' Command was renamed to '%s'. Please update your scripts.", prev, curr); 1067 }