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