1 /** 2 Defines the behavior of the DUB command line client. 3 4 Copyright: © 2012-2013 Matthias Dondorff, Copyright © 2012-2014 Sönke Ludwig 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.packagemanager; 19 import dub.packagesupplier; 20 import dub.platform : determineCompiler; 21 import dub.project; 22 import dub.internal.utils : getDUBVersion, getClosestMatch; 23 24 import std.algorithm; 25 import std.array; 26 import std.conv; 27 import std.encoding; 28 import std.exception; 29 import std.file; 30 import std.getopt; 31 import std.process; 32 import std.stdio; 33 import std.string; 34 import std.typecons : Tuple, tuple; 35 import std.variant; 36 37 38 int runDubCommandLine(string[] args) 39 { 40 logDiagnostic("DUB version %s", getDUBVersion()); 41 42 version(Windows){ 43 // rdmd uses $TEMP to compute a temporary path. since cygwin substitutes backslashes 44 // with slashes, this causes OPTLINK to fail (it thinks path segments are options) 45 // we substitute the other way around here to fix this. 46 environment["TEMP"] = environment["TEMP"].replace("/", "\\"); 47 } 48 49 // split application arguments from DUB arguments 50 string[] app_args; 51 auto app_args_idx = args.countUntil("--"); 52 if (app_args_idx >= 0) { 53 app_args = args[app_args_idx+1 .. $]; 54 args = args[0 .. app_args_idx]; 55 } 56 args = args[1 .. $]; // strip the application name 57 58 // handle direct dub options 59 if (args.length) switch (args[0]) 60 { 61 case "--version": 62 showVersion(); 63 return 0; 64 65 default: 66 break; 67 } 68 69 // parse general options 70 bool verbose, vverbose, quiet, vquiet; 71 bool help, annotate; 72 LogLevel loglevel = LogLevel.info; 73 string[] registry_urls; 74 string root_path = getcwd(); 75 76 auto common_args = new CommandArgs(args); 77 try { 78 common_args.getopt("h|help", &help, ["Display general or command specific help"]); 79 common_args.getopt("root", &root_path, ["Path to operate in instead of the current working dir"]); 80 common_args.getopt("registry", ®istry_urls, ["Search the given DUB registry URL first when resolving dependencies. Can be specified multiple times."]); 81 common_args.getopt("annotate", &annotate, ["Do not perform any action, just print what would be done"]); 82 common_args.getopt("v|verbose", &verbose, ["Print diagnostic output"]); 83 common_args.getopt("vverbose", &vverbose, ["Print debug output"]); 84 common_args.getopt("q|quiet", &quiet, ["Only print warnings and errors"]); 85 common_args.getopt("vquiet", &vquiet, ["Print no messages"]); 86 common_args.getopt("cache", &defaultPlacementLocation, ["Puts any fetched packages in the specified location [local|system|user]."]); 87 88 if( vverbose ) loglevel = LogLevel.debug_; 89 else if( verbose ) loglevel = LogLevel.diagnostic; 90 else if( vquiet ) loglevel = LogLevel.none; 91 else if( quiet ) loglevel = LogLevel.warn; 92 setLogLevel(loglevel); 93 } catch (Throwable e) { 94 logError("Error processing arguments: %s", e.msg); 95 logDiagnostic("Full exception: %s", e.toString().sanitize); 96 logInfo("Run 'dub help' for usage information."); 97 return 1; 98 } 99 100 // create the list of all supported commands 101 102 CommandGroup[] commands = [ 103 CommandGroup("Package creation", 104 new InitCommand 105 ), 106 CommandGroup("Build, test and run", 107 new RunCommand, 108 new BuildCommand, 109 new TestCommand, 110 new GenerateCommand, 111 new DescribeCommand, 112 new CleanCommand, 113 new DustmiteCommand 114 ), 115 CommandGroup("Package management", 116 new FetchCommand, 117 new InstallCommand, 118 new RemoveCommand, 119 new UninstallCommand, 120 new UpgradeCommand, 121 new AddPathCommand, 122 new RemovePathCommand, 123 new AddLocalCommand, 124 new RemoveLocalCommand, 125 new ListCommand, 126 new ListInstalledCommand, 127 new AddOverrideCommand, 128 new RemoveOverrideCommand, 129 new ListOverridesCommand, 130 new CleanCachesCommand, 131 ) 132 ]; 133 134 // extract the command 135 string cmdname; 136 args = common_args.extractRemainingArgs(); 137 if (args.length >= 1 && !args[0].startsWith("-")) { 138 cmdname = args[0]; 139 args = args[1 .. $]; 140 } else { 141 if (help) { 142 showHelp(commands, common_args); 143 return 0; 144 } 145 cmdname = "run"; 146 } 147 auto command_args = new CommandArgs(args); 148 149 if (cmdname == "help") { 150 showHelp(commands, common_args); 151 return 0; 152 } 153 154 // find the selected command 155 Command cmd; 156 foreach (grp; commands) 157 foreach (c; grp.commands) 158 if (c.name == cmdname) { 159 cmd = c; 160 break; 161 } 162 163 if (!cmd) { 164 logError("Unknown command: %s", cmdname); 165 writeln(); 166 showHelp(commands, common_args); 167 return 1; 168 } 169 170 // process command line options for the selected command 171 try { 172 cmd.prepare(command_args); 173 enforceUsage(cmd.acceptsAppArgs || app_args.length == 0, cmd.name ~ " doesn't accept application arguments."); 174 } catch (Throwable e) { 175 logError("Error processing arguments: %s", e.msg); 176 logDiagnostic("Full exception: %s", e.toString().sanitize); 177 logInfo("Run 'dub help' for usage information."); 178 return 1; 179 } 180 181 if (help) { 182 showCommandHelp(cmd, command_args, common_args); 183 return 0; 184 } 185 186 auto remaining_args = command_args.extractRemainingArgs(); 187 if (remaining_args.any!(a => a.startsWith("-"))) { 188 logError("Unknown command line flags: %s", remaining_args.filter!(a => a.startsWith("-")).array.join(" ")); 189 logError(`Type "dub %s -h" to get a list of all supported flags.`, cmdname); 190 return 1; 191 } 192 193 Dub dub; 194 195 // initialize the root package 196 if (!cmd.skipDubInitialization) { 197 // initialize DUB 198 auto package_suppliers = registry_urls.map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url))).array; 199 dub = new Dub(package_suppliers, root_path); 200 dub.dryRun = annotate; 201 202 // make the CWD package available so that for example sub packages can reference their 203 // parent package. 204 try dub.packageManager.getOrLoadPackage(Path(root_path)); 205 catch (Exception e) { logDiagnostic("No package found in current working directory."); } 206 } 207 208 // execute the command 209 int rc; 210 try { 211 rc = cmd.execute(dub, remaining_args, app_args); 212 } 213 catch (UsageException e) { 214 logError("%s", e.msg); 215 logDebug("Full exception: %s", e.toString().sanitize); 216 logInfo(`Run "dub %s -h" for more information about the "%s" command.`, cmdname, cmdname); 217 return 1; 218 } 219 catch (Throwable e) { 220 logError("Error executing command %s:", cmd.name); 221 logError("%s", e.msg); 222 logDebug("Full exception: %s", e.toString().sanitize); 223 return 2; 224 } 225 226 if (!cmd.skipDubInitialization) 227 dub.shutdown(); 228 return rc; 229 } 230 231 class CommandArgs { 232 struct Arg { 233 Variant defaultValue; 234 Variant value; 235 string names; 236 string[] helpText; 237 } 238 private { 239 string[] m_args; 240 Arg[] m_recognizedArgs; 241 } 242 243 this(string[] args) 244 { 245 m_args = "dummy" ~ args; 246 } 247 248 @property const(Arg)[] recognizedArgs() { return m_recognizedArgs; } 249 250 void getopt(T)(string names, T* var, string[] help_text = null) 251 { 252 foreach (ref arg; m_recognizedArgs) 253 if (names == arg.names) { 254 assert(help_text is null); 255 *var = arg.value.get!T; 256 return; 257 } 258 assert(help_text.length > 0); 259 Arg arg; 260 arg.defaultValue = *var; 261 arg.names = names; 262 arg.helpText = help_text; 263 m_args.getopt(config.passThrough, names, var); 264 arg.value = *var; 265 m_recognizedArgs ~= arg; 266 } 267 268 void dropAllArgs() 269 { 270 m_args = null; 271 } 272 273 string[] extractRemainingArgs() 274 { 275 auto ret = m_args[1 .. $]; 276 m_args = null; 277 return ret; 278 } 279 } 280 281 class Command { 282 string name; 283 string argumentsPattern; 284 string description; 285 string[] helpText; 286 bool acceptsAppArgs; 287 bool hidden = false; // used for deprecated commands 288 bool skipDubInitialization = false; 289 290 abstract void prepare(scope CommandArgs args); 291 abstract int execute(Dub dub, string[] free_args, string[] app_args); 292 } 293 294 struct CommandGroup { 295 string caption; 296 Command[] commands; 297 298 this(string caption, Command[] commands...) 299 { 300 this.caption = caption; 301 this.commands = commands.dup; 302 } 303 } 304 305 306 /******************************************************************************/ 307 /* INIT */ 308 /******************************************************************************/ 309 310 class InitCommand : Command { 311 private{ 312 string m_buildType = "minimal"; 313 } 314 this() 315 { 316 this.name = "init"; 317 this.argumentsPattern = "[<directory> [<dependency>...]]"; 318 this.description = "Initializes an empty package skeleton"; 319 this.helpText = [ 320 "Initializes an empty package of the specified type in the given directory. By default, the current working dirctory is used." 321 ]; 322 } 323 324 override void prepare(scope CommandArgs args) 325 { 326 args.getopt("t|type", &m_buildType, [ 327 "Set the type of project to generate. Available types:", 328 "", 329 "minimal - simple \"hello world\" project (default)", 330 "vibe.d - minimal HTTP server based on vibe.d", 331 "deimos - skeleton for C header bindings", 332 ]); 333 } 334 335 override int execute(Dub dub, string[] free_args, string[] app_args) 336 { 337 string dir; 338 enforceUsage(app_args.empty, "Unexpected application arguments."); 339 if (free_args.length) 340 { 341 dir = free_args[0]; 342 free_args = free_args[1 .. $]; 343 } 344 //TODO: Remove this block in next version 345 // Checks if argument uses current method of specifying project type. 346 if (free_args.length) 347 { 348 if (["vibe.d", "deimos", "minimal"].canFind(free_args[0])) 349 { 350 m_buildType = free_args[0]; 351 free_args = free_args[1 .. $]; 352 logInfo("Deprecated use of init type. Use --type=[vibe.d | deimos | minimal] in future."); 353 } 354 } 355 dub.createEmptyPackage(Path(dir), free_args, m_buildType); 356 return 0; 357 } 358 } 359 360 361 /******************************************************************************/ 362 /* GENERATE / BUILD / RUN / TEST / DESCRIBE */ 363 /******************************************************************************/ 364 365 abstract class PackageBuildCommand : Command { 366 protected { 367 string m_buildType; 368 BuildMode m_buildMode; 369 string m_buildConfig; 370 string m_compilerName; 371 string m_arch; 372 string[] m_debugVersions; 373 Compiler m_compiler; 374 BuildPlatform m_buildPlatform; 375 BuildSettings m_buildSettings; 376 string m_defaultConfig; 377 bool m_nodeps; 378 bool m_forceRemove = false; 379 } 380 381 this() 382 { 383 m_compilerName = defaultCompiler(); 384 } 385 386 override void prepare(scope CommandArgs args) 387 { 388 args.getopt("b|build", &m_buildType, [ 389 "Specifies the type of build to perform. Note that setting the DFLAGS environment variable will override the build type with custom flags.", 390 "Possible names:", 391 " debug (default), plain, release, release-nobounds, unittest, profile, docs, ddox, cov, unittest-cov and custom types" 392 ]); 393 args.getopt("c|config", &m_buildConfig, [ 394 "Builds the specified configuration. Configurations can be defined in dub.json" 395 ]); 396 args.getopt("compiler", &m_compilerName, [ 397 "Specifies the compiler binary to use (can be a path).", 398 "Arbitrary pre- and suffixes to the identifiers below are recognized (e.g. ldc2 or dmd-2.063) and matched to the proper compiler type:", 399 " "~["dmd", "gdc", "ldc", "gdmd", "ldmd"].join(", "), 400 "Default value: "~m_compilerName, 401 ]); 402 args.getopt("a|arch", &m_arch, [ 403 "Force a different architecture (e.g. x86 or x86_64)" 404 ]); 405 args.getopt("d|debug", &m_debugVersions, [ 406 "Define the specified debug version identifier when building - can be used multiple times" 407 ]); 408 args.getopt("nodeps", &m_nodeps, [ 409 "Do not check/update dependencies before building" 410 ]); 411 args.getopt("force-remove", &m_forceRemove, [ 412 "Force deletion of fetched packages with untracked files when upgrading" 413 ]); 414 args.getopt("build-mode", &m_buildMode, [ 415 "Specifies the way the compiler and linker are invoked. Valid values:", 416 " separate (default), allAtOnce, singleFile" 417 ]); 418 } 419 420 protected void setupPackage(Dub dub, string package_name) 421 { 422 m_compiler = getCompiler(m_compilerName); 423 m_buildPlatform = m_compiler.determinePlatform(m_buildSettings, m_compilerName, m_arch); 424 m_buildSettings.addDebugVersions(m_debugVersions); 425 426 m_defaultConfig = null; 427 enforce (loadSpecificPackage(dub, package_name), "Failed to load package."); 428 429 if (m_buildConfig.length != 0 && !dub.configurations.canFind(m_buildConfig)) 430 { 431 string msg = "Unknown build configuration: "~m_buildConfig; 432 enum distance = 3; 433 if (auto match = dub.configurations.getClosestMatch(m_buildConfig, distance)) 434 msg ~= ". Did you mean '" ~ match ~ "'?"; 435 enforce(0, msg); 436 } 437 438 if (m_buildType.length == 0) { 439 if (environment.get("DFLAGS")) m_buildType = "$DFLAGS"; 440 else m_buildType = "debug"; 441 } 442 443 if (!m_nodeps) { 444 // TODO: only upgrade(select) if necessary, only upgrade(upgrade) every now and then 445 446 // retrieve missing packages 447 logDiagnostic("Checking for missing dependencies."); 448 dub.upgrade(UpgradeOptions.select); 449 // check for updates 450 logDiagnostic("Checking for upgrades."); 451 dub.upgrade(UpgradeOptions.upgrade|UpgradeOptions.printUpgradesOnly|UpgradeOptions.useCachedResult); 452 } 453 454 dub.project.validate(); 455 } 456 457 private bool loadSpecificPackage(Dub dub, string package_name) 458 { 459 Package pack; 460 if (!package_name.empty) { 461 // load package in root_path to enable searching for sub packages 462 if (loadCwdPackage(dub, null, false)) { 463 if (package_name.startsWith(":")) 464 package_name = dub.projectName ~ package_name; 465 } 466 pack = dub.packageManager.getFirstPackage(package_name); 467 enforce(pack, "Failed to find a package named '"~package_name~"'."); 468 logInfo("Building package %s in %s", pack.name, pack.path.toNativeString()); 469 dub.rootPath = pack.path; 470 } 471 if (!loadCwdPackage(dub, pack, true)) return false; 472 return true; 473 } 474 475 private bool loadCwdPackage(Dub dub, Package pack, bool warn_missing_package) 476 { 477 if (warn_missing_package) { 478 bool found = existsFile(dub.rootPath ~ "source/app.d"); 479 if (!found) 480 foreach (f; packageInfoFiles) 481 if (existsFile(dub.rootPath ~ f.filename)) { 482 found = true; 483 break; 484 } 485 if (!found) { 486 logInfo(""); 487 logInfo("Neither a package description file, nor source/app.d was found in"); 488 logInfo(dub.rootPath.toNativeString()); 489 logInfo("Please run DUB from the root directory of an existing package, or run"); 490 logInfo("\"dub init --help\" to get information on creating a new package."); 491 logInfo(""); 492 return false; 493 } 494 } 495 496 if (pack) dub.loadPackage(pack); 497 else dub.loadPackageFromCwd(); 498 499 return true; 500 } 501 } 502 503 class GenerateCommand : PackageBuildCommand { 504 protected { 505 string m_generator; 506 bool m_rdmd = false; 507 bool m_tempBuild = false; 508 bool m_run = false; 509 bool m_force = false; 510 bool m_combined = false; 511 bool m_parallel = false; 512 bool m_printPlatform, m_printBuilds, m_printConfigs; 513 } 514 515 this() 516 { 517 this.name = "generate"; 518 this.argumentsPattern = "<generator> [<package>]"; 519 this.description = "Generates project files using the specified generator"; 520 this.helpText = [ 521 "Generates project files using one of the supported generators:", 522 "", 523 "visuald - VisualD project files", 524 "sublimetext - SublimeText project file", 525 "cmake - CMake build scripts", 526 "build - Builds the package directly", 527 "", 528 "An optional package name can be given to generate a different package than the root/CWD package." 529 ]; 530 } 531 532 override void prepare(scope CommandArgs args) 533 { 534 super.prepare(args); 535 536 args.getopt("combined", &m_combined, [ 537 "Tries to build the whole project in a single compiler run." 538 ]); 539 540 args.getopt("print-builds", &m_printBuilds, [ 541 "Prints the list of available build types" 542 ]); 543 args.getopt("print-configs", &m_printConfigs, [ 544 "Prints the list of available configurations" 545 ]); 546 args.getopt("print-platform", &m_printPlatform, [ 547 "Prints the identifiers for the current build platform as used for the build fields in dub.json" 548 ]); 549 args.getopt("parallel", &m_parallel, [ 550 "Runs multiple compiler instances in parallel, if possible." 551 ]); 552 } 553 554 override int execute(Dub dub, string[] free_args, string[] app_args) 555 { 556 string package_name; 557 if (!m_generator.length) { 558 enforceUsage(free_args.length >= 1 && free_args.length <= 2, "Expected one or two arguments."); 559 m_generator = free_args[0]; 560 if (free_args.length >= 2) package_name = free_args[1]; 561 } else { 562 enforceUsage(free_args.length <= 1, "Expected one or zero arguments."); 563 if (free_args.length >= 1) package_name = free_args[0]; 564 } 565 566 setupPackage(dub, package_name); 567 568 if (m_printBuilds) { // FIXME: use actual package data 569 logInfo("Available build types:"); 570 foreach (tp; ["debug", "release", "unittest", "profile"]) 571 logInfo(" %s", tp); 572 logInfo(""); 573 } 574 575 m_defaultConfig = dub.project.getDefaultConfiguration(m_buildPlatform); 576 if (m_printConfigs) { 577 logInfo("Available configurations:"); 578 foreach (tp; dub.configurations) 579 logInfo(" %s%s", tp, tp == m_defaultConfig ? " [default]" : null); 580 logInfo(""); 581 } 582 583 GeneratorSettings gensettings; 584 gensettings.platform = m_buildPlatform; 585 gensettings.config = m_buildConfig.length ? m_buildConfig : m_defaultConfig; 586 gensettings.buildType = m_buildType; 587 gensettings.buildMode = m_buildMode; 588 gensettings.compiler = m_compiler; 589 gensettings.buildSettings = m_buildSettings; 590 gensettings.combined = m_combined; 591 gensettings.run = m_run; 592 gensettings.runArgs = app_args; 593 gensettings.force = m_force; 594 gensettings.rdmd = m_rdmd; 595 gensettings.tempBuild = m_tempBuild; 596 gensettings.parallelBuild = m_parallel; 597 598 logDiagnostic("Generating using %s", m_generator); 599 if (m_generator == "visuald-combined") { 600 gensettings.combined = true; 601 m_generator = "visuald"; 602 logWarn(`The generator "visuald-combined" is deprecated, please use the --combined switch instead.`); 603 } 604 dub.generateProject(m_generator, gensettings); 605 if (m_buildType == "ddox") dub.runDdox(gensettings.run); 606 return 0; 607 } 608 } 609 610 class BuildCommand : GenerateCommand { 611 this() 612 { 613 this.name = "build"; 614 this.argumentsPattern = "[<package>]"; 615 this.description = "Builds a package (uses the main package in the current working directory by default)"; 616 this.helpText = [ 617 "Builds a package (uses the main package in the current working directory by default)" 618 ]; 619 } 620 621 override void prepare(scope CommandArgs args) 622 { 623 args.getopt("rdmd", &m_rdmd, [ 624 "Use rdmd instead of directly invoking the compiler" 625 ]); 626 627 args.getopt("f|force", &m_force, [ 628 "Forces a recompilation even if the target is up to date" 629 ]); 630 super.prepare(args); 631 m_generator = "build"; 632 } 633 634 override int execute(Dub dub, string[] free_args, string[] app_args) 635 { 636 return super.execute(dub, free_args, app_args); 637 } 638 } 639 640 class RunCommand : BuildCommand { 641 this() 642 { 643 this.name = "run"; 644 this.argumentsPattern = "[<package>]"; 645 this.description = "Builds and runs a package (default command)"; 646 this.helpText = [ 647 "Builds and runs a package (uses the main package in the current working directory by default)" 648 ]; 649 this.acceptsAppArgs = true; 650 } 651 652 override void prepare(scope CommandArgs args) 653 { 654 args.getopt("temp-build", &m_tempBuild, [ 655 "Builds the project in the temp folder if possible." 656 ]); 657 658 super.prepare(args); 659 m_run = true; 660 } 661 662 override int execute(Dub dub, string[] free_args, string[] app_args) 663 { 664 return super.execute(dub, free_args, app_args); 665 } 666 } 667 668 class TestCommand : PackageBuildCommand { 669 private { 670 string m_mainFile; 671 bool m_combined = false; 672 bool m_force = false; 673 } 674 675 this() 676 { 677 this.name = "test"; 678 this.argumentsPattern = "[<package>]"; 679 this.description = "Executes the tests of the selected package"; 680 this.helpText = [ 681 `Builds the package and executes all contained unit tests.`, 682 ``, 683 `If no explicit configuration is given, an existing "unittest" ` ~ 684 `configuration will be preferred for testing. If none exists, the ` ~ 685 `first library type configuration will be used, and if that doesn't ` ~ 686 `exist either, the first executable configuration is chosen.`, 687 ``, 688 `When a custom main file (--main-file) is specified, only library ` ~ 689 `configurations can be used. Otherwise, depending on the type of ` ~ 690 `the selected configuration, either an existing main file will be ` ~ 691 `used (and needs to be properly adjusted to just run the unit ` ~ 692 `tests for 'version(unittest)'), or DUB will generate one for ` ~ 693 `library type configurations.`, 694 ``, 695 `Finally, if the package contains a dependency to the "tested" ` ~ 696 `package, the automatically generated main file will use it to ` ~ 697 `run the unit tests.` 698 ]; 699 this.acceptsAppArgs = true; 700 701 m_buildType = "unittest"; 702 } 703 704 override void prepare(scope CommandArgs args) 705 { 706 args.getopt("main-file", &m_mainFile, [ 707 "Specifies a custom file containing the main() function to use for running the tests." 708 ]); 709 args.getopt("combined", &m_combined, [ 710 "Tries to build the whole project in a single compiler run." 711 ]); 712 args.getopt("f|force", &m_force, [ 713 "Forces a recompilation even if the target is up to date" 714 ]); 715 bool coverage = false; 716 args.getopt("coverage", &coverage, [ 717 "Enables code coverage statistics to be generated." 718 ]); 719 if (coverage) m_buildType = "unittest-cov"; 720 721 super.prepare(args); 722 } 723 724 override int execute(Dub dub, string[] free_args, string[] app_args) 725 { 726 string package_name; 727 enforceUsage(free_args.length <= 1, "Expected one or zero arguments."); 728 if (free_args.length >= 1) package_name = free_args[0]; 729 730 setupPackage(dub, package_name); 731 732 GeneratorSettings settings; 733 settings.platform = m_buildPlatform; 734 settings.compiler = getCompiler(m_buildPlatform.compilerBinary); 735 settings.buildType = m_buildType; 736 settings.buildMode = m_buildMode; 737 settings.buildSettings = m_buildSettings; 738 settings.combined = m_combined; 739 settings.force = m_force; 740 settings.run = true; 741 settings.runArgs = app_args; 742 743 dub.testProject(settings, m_buildConfig, Path(m_mainFile)); 744 return 0; 745 } 746 } 747 748 class DescribeCommand : PackageBuildCommand { 749 this() 750 { 751 this.name = "describe"; 752 this.argumentsPattern = "[<package>]"; 753 this.description = "Prints a JSON description of the project and its dependencies"; 754 this.helpText = [ 755 "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.", 756 "All usual options that are also used for build/run/generate apply." 757 ]; 758 } 759 760 override void prepare(scope CommandArgs args) 761 { 762 super.prepare(args); 763 } 764 765 override int execute(Dub dub, string[] free_args, string[] app_args) 766 { 767 // disable all log output and use "writeln" to output the JSON description 768 auto ll = getLogLevel(); 769 setLogLevel(LogLevel.none); 770 scope (exit) setLogLevel(ll); 771 772 string package_name; 773 enforceUsage(free_args.length <= 1, "Expected one or zero arguments."); 774 if (free_args.length >= 1) package_name = free_args[0]; 775 setupPackage(dub, package_name); 776 777 m_defaultConfig = dub.project.getDefaultConfiguration(m_buildPlatform); 778 779 dub.describeProject(m_buildPlatform, m_buildConfig.length ? m_buildConfig : m_defaultConfig); 780 return 0; 781 } 782 } 783 784 class CleanCommand : Command { 785 private { 786 bool m_allPackages; 787 } 788 789 this() 790 { 791 this.name = "clean"; 792 this.argumentsPattern = "[<package>]"; 793 this.description = "Removes intermediate build files and cached build results"; 794 this.helpText = [ 795 "This command removes any cached build files of the given package(s). The final target file, as well as any copyFiles are currently not removed.", 796 "Without arguments, the package in the current working directory will be cleaned." 797 ]; 798 } 799 800 override void prepare(scope CommandArgs args) 801 { 802 args.getopt("all-packages", &m_allPackages, [ 803 "Cleans up *all* known packages (dub list)" 804 ]); 805 } 806 807 override int execute(Dub dub, string[] free_args, string[] app_args) 808 { 809 enforceUsage(free_args.length <= 1, "Expected one or zero arguments."); 810 enforceUsage(app_args.length == 0, "Application arguments are not supported for the clean command."); 811 enforceUsage(!m_allPackages || !free_args.length, "The --all-packages flag may not be used together with an explicit package name."); 812 813 enforce(free_args.length == 0, "Cleaning a specific package isn't possible right now."); 814 815 if (m_allPackages) { 816 foreach (p; dub.packageManager.getPackageIterator()) 817 dub.cleanPackage(p.path); 818 } else { 819 dub.cleanPackage(dub.rootPath); 820 } 821 822 return 0; 823 } 824 } 825 826 827 /******************************************************************************/ 828 /* FETCH / REMOVE / UPGRADE */ 829 /******************************************************************************/ 830 831 class UpgradeCommand : Command { 832 private { 833 bool m_prerelease = false; 834 bool m_forceRemove = false; 835 bool m_missingOnly = false; 836 bool m_verify = false; 837 } 838 839 this() 840 { 841 this.name = "upgrade"; 842 this.argumentsPattern = "[<package>]"; 843 this.description = "Forces an upgrade of all dependencies"; 844 this.helpText = [ 845 "Upgrades all dependencies of the package by querying the package registry(ies) for new versions.", 846 "", 847 "This will also update the versions stored in the selections file ("~SelectedVersions.defaultFile~") accordingly.", 848 "", 849 "If a package specified, (only) that package will be upgraded. Otherwise all direct and indirect dependencies of the current package will get upgraded." 850 ]; 851 } 852 853 override void prepare(scope CommandArgs args) 854 { 855 args.getopt("prerelease", &m_prerelease, [ 856 "Uses the latest pre-release version, even if release versions are available" 857 ]); 858 args.getopt("force-remove", &m_forceRemove, [ 859 "Force deletion of fetched packages with untracked files" 860 ]); 861 args.getopt("verify", &m_verify, [ 862 "Updates the project and performs a build. If successfull, rewrites the selected versions file <to be implemeted>." 863 ]); 864 args.getopt("missing-only", &m_missingOnly, [ 865 "Performs an upgrade only for dependencies that don't yet have a version selected. This is also done automatically before each build." 866 ]); 867 } 868 869 override int execute(Dub dub, string[] free_args, string[] app_args) 870 { 871 enforceUsage(free_args.length <= 1, "Unexpected arguments."); 872 enforceUsage(app_args.length == 0, "Unexpected application arguments."); 873 enforceUsage(!m_verify, "--verify is not yet implemented."); 874 dub.loadPackageFromCwd(); 875 logInfo("Upgrading project in %s", dub.projectPath.toNativeString()); 876 auto options = UpgradeOptions.upgrade|UpgradeOptions.select; 877 if (m_missingOnly) options &= ~UpgradeOptions.upgrade; 878 if (m_prerelease) options |= UpgradeOptions.preRelease; 879 if (m_forceRemove) options |= UpgradeOptions.forceRemove; 880 enforceUsage(app_args.length == 0, "Upgrading a specific package is not yet implemented."); 881 dub.upgrade(options); 882 return 0; 883 } 884 } 885 886 class FetchRemoveCommand : Command { 887 protected { 888 string m_version; 889 bool m_forceRemove = false; 890 bool m_system = false; 891 bool m_local = false; 892 } 893 894 override void prepare(scope CommandArgs args) 895 { 896 args.getopt("version", &m_version, [ 897 "Use the specified version/branch instead of the latest available match", 898 "The remove command also accepts \"*\" here as a wildcard to remove all versions of the package from the specified location" 899 ]); 900 901 args.getopt("system", &m_system, ["Deprecated: Puts the package into the system wide package cache instead of the user local one."]); 902 args.getopt("local", &m_local, ["Deprecated: Puts the package into a sub folder of the current working directory. Cannot be mixed with --system."]); 903 args.getopt("force-remove", &m_forceRemove, [ 904 "Force deletion of fetched packages with untracked files" 905 ]); 906 } 907 908 abstract override int execute(Dub dub, string[] free_args, string[] app_args); 909 } 910 911 class FetchCommand : FetchRemoveCommand { 912 this() 913 { 914 this.name = "fetch"; 915 this.argumentsPattern = "<name>"; 916 this.description = "Manually retrieves and caches a package"; 917 this.helpText = [ 918 "Note: Use the \"dependencies\" field in the package description file (e.g. dub.json) if you just want to use a certain package as a dependency, you don't have to explicitly fetch packages.", 919 "", 920 "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 dub.json, dub will do the rest for you.", 921 "", 922 "Without specified options, placement/removal will default to a user wide shared location.", 923 "", 924 "Complete applications can be retrieved and run easily by e.g.", 925 "$ dub fetch vibelog --local", 926 "$ cd vibelog", 927 "$ dub", 928 "", 929 "This will grab all needed dependencies and compile and run the application.", 930 "", 931 "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." 932 ]; 933 } 934 935 override void prepare(scope CommandArgs args) 936 { 937 super.prepare(args); 938 } 939 940 override int execute(Dub dub, string[] free_args, string[] app_args) 941 { 942 enforceUsage(!m_local || !m_system, "--local and --system are exclusive to each other."); 943 enforceUsage(free_args.length == 1, "Expecting exactly one argument."); 944 enforceUsage(app_args.length == 0, "Unexpected application arguments."); 945 946 auto location = defaultPlacementLocation; 947 if (m_local) 948 { 949 logWarn("--local is deprecated. Use --cache=local instead."); 950 location = PlacementLocation.local; 951 } 952 else if (m_system) 953 { 954 logWarn("--system is deprecated. Use --cache=system instead."); 955 location = PlacementLocation.system; 956 } 957 958 auto name = free_args[0]; 959 960 FetchOptions fetchOpts; 961 fetchOpts |= FetchOptions.forceBranchUpgrade; 962 fetchOpts |= m_forceRemove ? FetchOptions.forceRemove : FetchOptions.none; 963 if (m_version.length) dub.fetch(name, Dependency(m_version), location, fetchOpts); 964 else { 965 try { 966 dub.fetch(name, Dependency(">=0.0.0"), location, fetchOpts); 967 logInfo( 968 "Please note that you need to use `dub run <pkgname>` " ~ 969 "or add it to dependencies of your package to actually use/run it. " ~ 970 "dub does not do actual installation of packages outside of its own ecosystem."); 971 } 972 catch(Exception e){ 973 logInfo("Getting a release version failed: %s", e.msg); 974 logInfo("Retry with ~master..."); 975 dub.fetch(name, Dependency("~master"), location, fetchOpts); 976 } 977 } 978 return 0; 979 } 980 } 981 982 class InstallCommand : FetchCommand { 983 this() { this.name = "install"; hidden = true; } 984 override void prepare(scope CommandArgs args) { super.prepare(args); } 985 override int execute(Dub dub, string[] free_args, string[] app_args) 986 { 987 warnRenamed("install", "fetch"); 988 return super.execute(dub, free_args, app_args); 989 } 990 } 991 992 class RemoveCommand : FetchRemoveCommand { 993 this() 994 { 995 this.name = "remove"; 996 this.argumentsPattern = "<name>"; 997 this.description = "Removes a cached package"; 998 this.helpText = [ 999 "Removes a package that is cached on the local system." 1000 ]; 1001 } 1002 1003 override void prepare(scope CommandArgs args) 1004 { 1005 super.prepare(args); 1006 } 1007 1008 override int execute(Dub dub, string[] free_args, string[] app_args) 1009 { 1010 enforceUsage(free_args.length == 1, "Expecting exactly one argument."); 1011 enforceUsage(app_args.length == 0, "Unexpected application arguments."); 1012 1013 auto package_id = free_args[0]; 1014 auto location = defaultPlacementLocation; 1015 if (m_local) 1016 { 1017 logWarn("--local is deprecated. Use --cache=local instead."); 1018 location = PlacementLocation.local; 1019 } 1020 else if (m_system) 1021 { 1022 logWarn("--system is deprecated. Use --cache=system instead."); 1023 location = PlacementLocation.system; 1024 } 1025 1026 dub.remove(package_id, m_version, location, m_forceRemove); 1027 return 0; 1028 } 1029 } 1030 1031 class UninstallCommand : RemoveCommand { 1032 this() { this.name = "uninstall"; hidden = true; } 1033 override void prepare(scope CommandArgs args) { super.prepare(args); } 1034 override int execute(Dub dub, string[] free_args, string[] app_args) 1035 { 1036 warnRenamed("uninstall", "remove"); 1037 return super.execute(dub, free_args, app_args); 1038 } 1039 } 1040 1041 1042 /******************************************************************************/ 1043 /* ADD/REMOVE PATH/LOCAL */ 1044 /******************************************************************************/ 1045 1046 abstract class RegistrationCommand : Command { 1047 private { 1048 bool m_system; 1049 } 1050 1051 override void prepare(scope CommandArgs args) 1052 { 1053 args.getopt("system", &m_system, [ 1054 "Register system-wide instead of user-wide" 1055 ]); 1056 } 1057 1058 abstract override int execute(Dub dub, string[] free_args, string[] app_args); 1059 } 1060 1061 class AddPathCommand : RegistrationCommand { 1062 this() 1063 { 1064 this.name = "add-path"; 1065 this.argumentsPattern = "<path>"; 1066 this.description = "Adds a default package search path"; 1067 this.helpText = ["Adds a default package search path"]; 1068 } 1069 1070 override int execute(Dub dub, string[] free_args, string[] app_args) 1071 { 1072 enforceUsage(free_args.length == 1, "Missing search path."); 1073 dub.addSearchPath(free_args[0], m_system); 1074 return 0; 1075 } 1076 } 1077 1078 class RemovePathCommand : RegistrationCommand { 1079 this() 1080 { 1081 this.name = "remove-path"; 1082 this.argumentsPattern = "<path>"; 1083 this.description = "Removes a package search path"; 1084 this.helpText = ["Removes a package search path"]; 1085 } 1086 1087 override int execute(Dub dub, string[] free_args, string[] app_args) 1088 { 1089 enforceUsage(free_args.length == 1, "Expected one argument."); 1090 dub.removeSearchPath(free_args[0], m_system); 1091 return 0; 1092 } 1093 } 1094 1095 class AddLocalCommand : RegistrationCommand { 1096 this() 1097 { 1098 this.name = "add-local"; 1099 this.argumentsPattern = "<path> [<version>]"; 1100 this.description = "Adds a local package directory (e.g. a git repository)"; 1101 this.helpText = ["Adds a local package directory (e.g. a git repository)"]; 1102 } 1103 1104 override int execute(Dub dub, string[] free_args, string[] app_args) 1105 { 1106 enforceUsage(free_args.length == 1 || free_args.length == 2, "Expecting one or two arguments."); 1107 string ver = free_args.length == 2 ? free_args[1] : null; 1108 dub.addLocalPackage(free_args[0], ver, m_system); 1109 return 0; 1110 } 1111 } 1112 1113 class RemoveLocalCommand : RegistrationCommand { 1114 this() 1115 { 1116 this.name = "remove-local"; 1117 this.argumentsPattern = "<path>"; 1118 this.description = "Removes a local package directory"; 1119 this.helpText = ["Removes a local package directory"]; 1120 } 1121 1122 override int execute(Dub dub, string[] free_args, string[] app_args) 1123 { 1124 enforceUsage(free_args.length == 1, "Missing path to package."); 1125 dub.removeLocalPackage(free_args[0], m_system); 1126 return 0; 1127 } 1128 } 1129 1130 class ListCommand : Command { 1131 this() 1132 { 1133 this.name = "list"; 1134 this.argumentsPattern = ""; 1135 this.description = "Prints a list of all local packages dub is aware of"; 1136 this.helpText = [ 1137 "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\")." 1138 ]; 1139 } 1140 override void prepare(scope CommandArgs args) {} 1141 override int execute(Dub dub, string[] free_args, string[] app_args) 1142 { 1143 logInfo("Packages present in the system and known to dub:"); 1144 foreach (p; dub.packageManager.getPackageIterator()) 1145 logInfo(" %s %s: %s", p.name, p.ver, p.path.toNativeString()); 1146 logInfo(""); 1147 return 0; 1148 } 1149 } 1150 1151 class ListInstalledCommand : ListCommand { 1152 this() { this.name = "list-installed"; hidden = true; } 1153 override void prepare(scope CommandArgs args) { super.prepare(args); } 1154 override int execute(Dub dub, string[] free_args, string[] app_args) 1155 { 1156 warnRenamed("list-installed", "list"); 1157 return super.execute(dub, free_args, app_args); 1158 } 1159 } 1160 1161 1162 /******************************************************************************/ 1163 /* OVERRIDES */ 1164 /******************************************************************************/ 1165 1166 class AddOverrideCommand : Command { 1167 private { 1168 bool m_system = false; 1169 } 1170 1171 this() 1172 { 1173 this.name = "add-override"; 1174 this.argumentsPattern = "<package> <version-spec> <target-path/target-version>"; 1175 this.description = "Adds a new package override."; 1176 this.helpText = [ 1177 ]; 1178 } 1179 1180 override void prepare(scope CommandArgs args) 1181 { 1182 args.getopt("system", &m_system, [ 1183 "Register system-wide instead of user-wide" 1184 ]); 1185 } 1186 1187 override int execute(Dub dub, string[] free_args, string[] app_args) 1188 { 1189 enforceUsage(app_args.length == 0, "Unexpected application arguments."); 1190 enforceUsage(free_args.length == 3, "Expected three arguments, not "~free_args.length.to!string); 1191 auto scope_ = m_system ? LocalPackageType.system : LocalPackageType.user; 1192 auto pack = free_args[0]; 1193 auto ver = Dependency(free_args[1]); 1194 if (existsFile(Path(free_args[2]))) { 1195 auto target = Path(free_args[2]); 1196 dub.packageManager.addOverride(scope_, pack, ver, target); 1197 logInfo("Added override %s %s => %s", pack, ver, target); 1198 } else { 1199 auto target = Version(free_args[2]); 1200 dub.packageManager.addOverride(scope_, pack, ver, target); 1201 logInfo("Added override %s %s => %s", pack, ver, target); 1202 } 1203 return 0; 1204 } 1205 } 1206 1207 class RemoveOverrideCommand : Command { 1208 private { 1209 bool m_system = false; 1210 } 1211 1212 this() 1213 { 1214 this.name = "remove-override"; 1215 this.argumentsPattern = "<package> <version-spec>"; 1216 this.description = "Removes an existing package override."; 1217 this.helpText = [ 1218 ]; 1219 } 1220 1221 override void prepare(scope CommandArgs args) 1222 { 1223 args.getopt("system", &m_system, [ 1224 "Register system-wide instead of user-wide" 1225 ]); 1226 } 1227 1228 override int execute(Dub dub, string[] free_args, string[] app_args) 1229 { 1230 enforceUsage(app_args.length == 0, "Unexpected application arguments."); 1231 enforceUsage(free_args.length == 2, "Expected two arguments, not "~free_args.length.to!string); 1232 auto scope_ = m_system ? LocalPackageType.system : LocalPackageType.user; 1233 dub.packageManager.removeOverride(scope_, free_args[0], Dependency(free_args[1])); 1234 return 0; 1235 } 1236 } 1237 1238 class ListOverridesCommand : Command { 1239 this() 1240 { 1241 this.name = "list-overrides"; 1242 this.argumentsPattern = ""; 1243 this.description = "Prints a list of all local package overrides"; 1244 this.helpText = [ 1245 "Prints a list of all overriden packages added via \"dub add-override\"." 1246 ]; 1247 } 1248 override void prepare(scope CommandArgs args) {} 1249 override int execute(Dub dub, string[] free_args, string[] app_args) 1250 { 1251 void printList(in PackageOverride[] overrides, string caption) 1252 { 1253 if (overrides.length == 0) return; 1254 logInfo("# %s", caption); 1255 foreach (ovr; overrides) { 1256 if (!ovr.targetPath.empty) logInfo("%s %s => %s", ovr.package_, ovr.version_, ovr.targetPath); 1257 else logInfo("%s %s => %s", ovr.package_, ovr.version_, ovr.targetVersion); 1258 } 1259 } 1260 printList(dub.packageManager.getOverrides(LocalPackageType.user), "User wide overrides"); 1261 printList(dub.packageManager.getOverrides(LocalPackageType.system), "System wide overrides"); 1262 return 0; 1263 } 1264 } 1265 1266 /******************************************************************************/ 1267 /* Cache cleanup */ 1268 /******************************************************************************/ 1269 1270 class CleanCachesCommand : Command { 1271 this() 1272 { 1273 this.name = "clean-caches"; 1274 this.argumentsPattern = ""; 1275 this.description = "Removes cached metadata"; 1276 this.helpText = [ 1277 "This command removes any cached metadata like the list of available packages and their latest version." 1278 ]; 1279 } 1280 1281 override void prepare(scope CommandArgs args) {} 1282 1283 override int execute(Dub dub, string[] free_args, string[] app_args) 1284 { 1285 dub.cleanCaches(); 1286 return 0; 1287 } 1288 } 1289 1290 /******************************************************************************/ 1291 /* DUSTMITE */ 1292 /******************************************************************************/ 1293 1294 class DustmiteCommand : PackageBuildCommand { 1295 private { 1296 int m_compilerStatusCode = int.min; 1297 int m_linkerStatusCode = int.min; 1298 int m_programStatusCode = int.min; 1299 string m_compilerRegex; 1300 string m_linkerRegex; 1301 string m_programRegex; 1302 string m_testPackage; 1303 bool m_combined; 1304 } 1305 1306 this() 1307 { 1308 this.name = "dustmite"; 1309 this.argumentsPattern = "<destination-path>"; 1310 this.acceptsAppArgs = true; 1311 this.description = "Create reduced test cases for build errors"; 1312 this.helpText = [ 1313 "This command uses the Dustmite utility to isolate the cause of build errors in a DUB project.", 1314 "", 1315 "It will create a copy of all involved packages and run dustmite on this copy, leaving a reduced test case.", 1316 "", 1317 "Determining the desired error condition is done by checking the compiler/linker status code, as well as their output (stdout and stderr combined). If --program-status or --program-regex is given and the generated binary is an executable, it will be executed and its output will also be incorporated into the final decision." 1318 ]; 1319 } 1320 1321 override void prepare(scope CommandArgs args) 1322 { 1323 args.getopt("compiler-status", &m_compilerStatusCode, ["The expected status code of the compiler run"]); 1324 args.getopt("compiler-regex", &m_compilerRegex, ["A regular expression used to match against the compiler output"]); 1325 args.getopt("linker-status", &m_linkerStatusCode, ["The expected status code of the liner run"]); 1326 args.getopt("linker-regex", &m_linkerRegex, ["A regular expression used to match against the linker output"]); 1327 args.getopt("program-status", &m_programStatusCode, ["The expected status code of the built executable"]); 1328 args.getopt("program-regex", &m_programRegex, ["A regular expression used to match against the program output"]); 1329 args.getopt("test-package", &m_testPackage, ["Perform a test run - usually only used internally"]); 1330 args.getopt("combined", &m_combined, ["Builds multiple packages with one compiler run"]); 1331 super.prepare(args); 1332 1333 // speed up loading when in test mode 1334 if (m_testPackage.length) { 1335 skipDubInitialization = true; 1336 m_nodeps = true; 1337 } 1338 } 1339 1340 override int execute(Dub dub, string[] free_args, string[] app_args) 1341 { 1342 if (m_testPackage.length) { 1343 dub = new Dub(Path(getcwd())); 1344 1345 setupPackage(dub, m_testPackage); 1346 m_defaultConfig = dub.project.getDefaultConfiguration(m_buildPlatform); 1347 1348 GeneratorSettings gensettings; 1349 gensettings.platform = m_buildPlatform; 1350 gensettings.config = m_buildConfig.length ? m_buildConfig : m_defaultConfig; 1351 gensettings.buildType = m_buildType; 1352 gensettings.compiler = m_compiler; 1353 gensettings.buildSettings = m_buildSettings; 1354 gensettings.combined = m_combined; 1355 gensettings.run = m_programStatusCode != int.min || m_programRegex.length; 1356 gensettings.runArgs = app_args; 1357 gensettings.force = true; 1358 gensettings.compileCallback = check(m_compilerStatusCode, m_compilerRegex); 1359 gensettings.linkCallback = check(m_linkerStatusCode, m_linkerRegex); 1360 gensettings.runCallback = check(m_programStatusCode, m_programRegex); 1361 try dub.generateProject("build", gensettings); 1362 catch (DustmiteMismatchException) { 1363 logInfo("Dustmite test doesn't match."); 1364 return 3; 1365 } 1366 catch (DustmiteMatchException) { 1367 logInfo("Dustmite test matches."); 1368 return 0; 1369 } 1370 } else { 1371 enforceUsage(free_args.length == 1, "Expected destination path."); 1372 auto path = Path(free_args[0]); 1373 path.normalize(); 1374 enforceUsage(path.length > 0, "Destination path must not be empty."); 1375 if (!path.absolute) path = Path(getcwd()) ~ path; 1376 enforceUsage(!path.startsWith(dub.rootPath), "Destination path must not be a sub directory of the tested package!"); 1377 1378 setupPackage(dub, null); 1379 auto prj = dub.project; 1380 if (m_buildConfig.empty) 1381 m_buildConfig = prj.getDefaultConfiguration(m_buildPlatform); 1382 1383 void copyFolderRec(Path folder, Path dstfolder) 1384 { 1385 mkdirRecurse(dstfolder.toNativeString()); 1386 foreach (de; iterateDirectory(folder.toNativeString())) { 1387 if (de.name.startsWith(".")) continue; 1388 if (de.isDirectory) { 1389 copyFolderRec(folder ~ de.name, dstfolder ~ de.name); 1390 } else { 1391 if (de.name.endsWith(".o") || de.name.endsWith(".obj")) continue; 1392 if (de.name.endsWith(".exe")) continue; 1393 try copyFile(folder ~ de.name, dstfolder ~ de.name); 1394 catch (Exception e) { 1395 logWarn("Failed to copy file %s: %s", (folder ~ de.name).toNativeString(), e.msg); 1396 } 1397 } 1398 } 1399 } 1400 1401 bool[string] visited; 1402 foreach (pack_; prj.getTopologicalPackageList()) { 1403 auto pack = pack_.basePackage; 1404 if (pack.name in visited) continue; 1405 visited[pack.name] = true; 1406 logInfo("Copy package '%s' to destination folder...", pack.name); 1407 copyFolderRec(pack.path, path ~ pack.name); 1408 } 1409 logInfo("Executing dustmite..."); 1410 auto testcmd = format("dub dustmite --vquiet --test-package=%s", prj.name); 1411 if (m_compilerStatusCode != int.min) testcmd ~= format(" --compiler-status=%s", m_compilerStatusCode); 1412 if (m_compilerRegex.length) testcmd ~= format(" \"--compiler-regex=%s\"", m_compilerRegex); 1413 if (m_linkerStatusCode != int.min) testcmd ~= format(" --linker-status=%s", m_linkerStatusCode); 1414 if (m_linkerRegex.length) testcmd ~= format(" \"--linker-regex=%s\"", m_linkerRegex); 1415 if (m_programStatusCode != int.min) testcmd ~= format(" --program-status=%s", m_programStatusCode); 1416 if (m_programRegex.length) testcmd ~= format(" \"--program-regex=%s\"", m_programRegex); 1417 if (m_combined) testcmd ~= " --combined"; 1418 // TODO: pass *all* original parameters 1419 logDiagnostic("Running dustmite: %s", testcmd); 1420 auto dmpid = spawnProcess(["dustmite", path.toNativeString(), testcmd]); 1421 return dmpid.wait(); 1422 } 1423 return 0; 1424 } 1425 1426 void delegate(int, string) check(int code_match, string regex_match) 1427 { 1428 return (code, output) { 1429 import std.encoding; 1430 import std.regex; 1431 1432 logInfo("%s", output); 1433 1434 if (code_match != int.min && code != code_match) { 1435 logInfo("Exit code %s doesn't match expected value %s", code, code_match); 1436 throw new DustmiteMismatchException; 1437 } 1438 1439 if (regex_match.length > 0 && !match(output.sanitize, regex_match)) { 1440 logInfo("Output doesn't match regex:"); 1441 logInfo("%s", output); 1442 throw new DustmiteMismatchException; 1443 } 1444 1445 if (code != 0 && code_match != int.min || regex_match.length > 0) { 1446 logInfo("Tool failed, but matched either exit code or output - counting as match."); 1447 throw new DustmiteMatchException; 1448 } 1449 }; 1450 } 1451 1452 static class DustmiteMismatchException : Exception { 1453 this(string message = "", string file = __FILE__, int line = __LINE__, Throwable next = null) 1454 { 1455 super(message, file, line, next); 1456 } 1457 } 1458 1459 static class DustmiteMatchException : Exception { 1460 this(string message = "", string file = __FILE__, int line = __LINE__, Throwable next = null) 1461 { 1462 super(message, file, line, next); 1463 } 1464 } 1465 } 1466 1467 1468 /******************************************************************************/ 1469 /* HELP */ 1470 /******************************************************************************/ 1471 1472 private { 1473 enum shortArgColumn = 2; 1474 enum longArgColumn = 6; 1475 enum descColumn = 24; 1476 enum lineWidth = 80 - 1; 1477 } 1478 1479 private void showHelp(in CommandGroup[] commands, CommandArgs common_args) 1480 { 1481 writeln( 1482 `USAGE: dub [--version] [<command>] [<options...>] [-- [<application arguments...>]] 1483 1484 Manages the DUB project in the current directory. If the command is omitted, 1485 DUB will default to "run". When running an application, "--" can be used to 1486 separate DUB options from options passed to the application. 1487 1488 Run "dub <command> --help" to get help for a specific command. 1489 1490 You can use the "http_proxy" environment variable to configure a proxy server 1491 to be used for fetching packages. 1492 1493 1494 Available commands 1495 ==================`); 1496 1497 foreach (grp; commands) { 1498 writeln(); 1499 writeWS(shortArgColumn); 1500 writeln(grp.caption); 1501 writeWS(shortArgColumn); 1502 writerep!'-'(grp.caption.length); 1503 writeln(); 1504 foreach (cmd; grp.commands) { 1505 if (cmd.hidden) continue; 1506 writeWS(shortArgColumn); 1507 writef("%s %s", cmd.name, cmd.argumentsPattern); 1508 auto chars_output = cmd.name.length + cmd.argumentsPattern.length + shortArgColumn + 1; 1509 if (chars_output < descColumn) { 1510 writeWS(descColumn - chars_output); 1511 } else { 1512 writeln(); 1513 writeWS(descColumn); 1514 } 1515 writeWrapped(cmd.description, descColumn, descColumn); 1516 } 1517 } 1518 writeln(); 1519 writeln(); 1520 writeln(`Common options`); 1521 writeln(`==============`); 1522 writeln(); 1523 writeOptions(common_args); 1524 writeln(); 1525 showVersion(); 1526 } 1527 1528 private void showVersion() 1529 { 1530 writefln("DUB version %s, built on %s", getDUBVersion(), __DATE__); 1531 } 1532 1533 private void showCommandHelp(Command cmd, CommandArgs args, CommandArgs common_args) 1534 { 1535 writefln(`USAGE: dub %s %s [<options...>]%s`, cmd.name, cmd.argumentsPattern, cmd.acceptsAppArgs ? " [-- <application arguments...>]": null); 1536 writeln(); 1537 foreach (ln; cmd.helpText) 1538 ln.writeWrapped(); 1539 1540 if (args.recognizedArgs.length) { 1541 writeln(); 1542 writeln(); 1543 writeln("Command specific options"); 1544 writeln("========================"); 1545 writeln(); 1546 writeOptions(args); 1547 } 1548 1549 writeln(); 1550 writeln(); 1551 writeln("Common options"); 1552 writeln("=============="); 1553 writeln(); 1554 writeOptions(common_args); 1555 writeln(); 1556 writefln("DUB version %s, built on %s", getDUBVersion(), __DATE__); 1557 } 1558 1559 private void writeOptions(CommandArgs args) 1560 { 1561 foreach (arg; args.recognizedArgs) { 1562 auto names = arg.names.split("|"); 1563 assert(names.length == 1 || names.length == 2); 1564 string sarg = names[0].length == 1 ? names[0] : null; 1565 string larg = names[0].length > 1 ? names[0] : names.length > 1 ? names[1] : null; 1566 if (sarg) { 1567 writeWS(shortArgColumn); 1568 writef("-%s", sarg); 1569 writeWS(longArgColumn - shortArgColumn - 2); 1570 } else writeWS(longArgColumn); 1571 size_t col = longArgColumn; 1572 if (larg) { 1573 if (arg.defaultValue.peek!bool) { 1574 writef("--%s", larg); 1575 col += larg.length + 2; 1576 } else { 1577 writef("--%s=VALUE", larg); 1578 col += larg.length + 8; 1579 } 1580 } 1581 if (col < descColumn) { 1582 writeWS(descColumn - col); 1583 } else { 1584 writeln(); 1585 writeWS(descColumn); 1586 } 1587 foreach (i, ln; arg.helpText) { 1588 if (i > 0) writeWS(descColumn); 1589 ln.writeWrapped(descColumn, descColumn); 1590 } 1591 } 1592 } 1593 1594 private void writeWrapped(string string, size_t indent = 0, size_t first_line_pos = 0) 1595 { 1596 auto wrapped = string.wrap(lineWidth, getRepString!' '(first_line_pos), getRepString!' '(indent)); 1597 wrapped = wrapped[first_line_pos .. $]; 1598 foreach (ln; wrapped.splitLines()) 1599 writeln(ln); 1600 } 1601 1602 private void writeWS(size_t num) { writerep!' '(num); } 1603 private void writerep(char ch)(size_t num) { write(getRepString!ch(num)); } 1604 1605 private string getRepString(char ch)(size_t len) 1606 { 1607 static string buf; 1608 if (len > buf.length) buf ~= [ch].replicate(len-buf.length); 1609 return buf[0 .. len]; 1610 } 1611 1612 /*** 1613 */ 1614 1615 1616 private void enforceUsage(bool cond, string text) 1617 { 1618 if (!cond) throw new UsageException(text); 1619 } 1620 1621 private class UsageException : Exception { 1622 this(string message, string file = __FILE__, int line = __LINE__, Throwable next = null) 1623 { 1624 super(message, file, line, next); 1625 } 1626 } 1627 1628 private void warnRenamed(string prev, string curr) 1629 { 1630 logWarn("The '%s' Command was renamed to '%s'. Please update your scripts.", prev, curr); 1631 }