1 module workspaced.com.dub; 2 3 import core.exception; 4 import core.sync.mutex; 5 import core.thread; 6 7 import std.algorithm; 8 import std.array : appender; 9 import std.conv; 10 import std.exception; 11 import std.parallelism; 12 import std.regex; 13 import std.stdio; 14 import std.string; 15 16 import workspaced.api; 17 18 import dub.description; 19 import dub.dub; 20 import dub.package_; 21 import dub.project; 22 23 import dub.compilers.buildsettings; 24 import dub.compilers.compiler; 25 import dub.dependency; 26 import dub.generators.build; 27 import dub.generators.generator; 28 29 import dub.internal.vibecompat.core.log; 30 import dub.internal.vibecompat.inet.url; 31 32 import dub.recipe.io; 33 34 @component("dub") 35 class DubComponent : ComponentWrapper 36 { 37 mixin DefaultComponentWrapper; 38 39 enum WarningId 40 { 41 invalidDefaultConfig, 42 unexpectedError, 43 failedListingImportPaths 44 } 45 46 static void registered() 47 { 48 setLogLevel(LogLevel.none); 49 } 50 51 protected void load() 52 { 53 if (!refInstance) 54 throw new Exception("dub requires to be instanced"); 55 56 if (config.get!bool("dub", "registerImportProvider", true)) 57 importPathProvider = &imports; 58 if (config.get!bool("dub", "registerStringImportProvider", true)) 59 stringImportPathProvider = &stringImports; 60 if (config.get!bool("dub", "registerImportFilesProvider", false)) 61 importFilesProvider = &fileImports; 62 if (config.get!bool("dub", "registerProjectVersionsProvider", true)) 63 projectVersionsProvider = &versions; 64 if (config.get!bool("dub", "registerDebugSpecificationsProvider", true)) 65 debugSpecificationsProvider = &debugVersions; 66 67 try 68 { 69 start(); 70 71 _configuration = _dub.project.getDefaultConfiguration(_platform); 72 if (!_dub.project.configurations.canFind(_configuration)) 73 { 74 workspaced.messageHandler.warn(refInstance, "dub", 75 cast(int)WarningId.invalidDefaultConfig, 76 "Dub Error: No configuration available"); 77 } 78 else 79 updateImportPaths(false); 80 } 81 catch (Exception e) 82 { 83 if (!_dub || !_dub.project) 84 throw e; 85 workspaced.messageHandler.warn(refInstance, "dub", 86 cast(int)WarningId.unexpectedError, 87 "Dub Error (ignored)", 88 e.toString); 89 } 90 /*catch (AssertError e) 91 { 92 if (!_dub || !_dub.project) 93 throw e; 94 workspaced.messageHandler.warn(refInstance, "dub", 95 cast(int)WarningId.unexpectedError, 96 "Dub Error (ignored): " ~ e.toString); 97 }*/ 98 } 99 100 private void start() 101 { 102 _dubRunning = false; 103 _dub = new Dub(instance.cwd, null, SkipPackageSuppliers.none); 104 _dub.packageManager.getOrLoadPackage(NativePath(instance.cwd)); 105 _dub.loadPackage(); 106 _dub.project.validate(); 107 108 // mark all packages as optional so we don't crash 109 int missingPackages; 110 auto optionalified = optionalifyPackages; 111 foreach (ref pkg; _dub.project.getTopologicalPackageList()) 112 { 113 optionalifyRecipe(pkg); 114 foreach (dep; pkg.getAllDependencies() 115 .filter!(a => optionalified.canFind(a.name))) 116 { 117 auto d = _dub.project.getDependency(dep.name, true); 118 if (!d) 119 missingPackages++; 120 else 121 optionalifyRecipe(d); 122 } 123 } 124 125 if (!_compilerBinaryName.length) 126 _compilerBinaryName = _dub.defaultCompiler; 127 setCompiler(_compilerBinaryName); 128 129 _settingsTemplate = cast() _dub.project.rootPackage.getBuildSettings(); 130 131 if (missingPackages > 0) 132 { 133 upgrade(false); 134 optionalifyPackages(); 135 } 136 137 _dubRunning = true; 138 } 139 140 private string[] optionalifyPackages() 141 { 142 bool[Package] visited; 143 string[] optionalified; 144 foreach (pkg; _dub.project.dependencies) 145 optionalified ~= optionalifyRecipe(cast() pkg); 146 return optionalified; 147 } 148 149 private string[] optionalifyRecipe(Package pkg) 150 { 151 string[] optionalified; 152 foreach (key, ref value; pkg.recipe.buildSettings.dependencies) 153 { 154 if (!value.optional) 155 { 156 value.optional = true; 157 value.default_ = true; 158 optionalified ~= key; 159 } 160 } 161 foreach (ref config; pkg.recipe.configurations) 162 foreach (key, ref value; config.buildSettings.dependencies) 163 { 164 if (!value.optional) 165 { 166 value.optional = true; 167 value.default_ = true; 168 optionalified ~= key; 169 } 170 } 171 return optionalified; 172 } 173 174 private void restart() 175 { 176 _dub.destroy(); 177 _dubRunning = false; 178 start(); 179 } 180 181 bool isRunning() 182 { 183 return _dub !is null 184 && _dub.project !is null 185 && _dub.project.rootPackage !is null 186 && _dubRunning; 187 } 188 189 /// Reloads the dub.json or dub.sdl file from the cwd 190 /// Returns: `false` if there are no import paths available 191 Future!bool update() 192 { 193 restart(); 194 mixin(gthreadsAsyncProxy!`updateImportPaths(false)`); 195 } 196 197 bool updateImportPaths(bool restartDub = true) 198 { 199 validateConfiguration(); 200 201 if (restartDub) 202 restart(); 203 204 GeneratorSettings settings; 205 settings.platform = _platform; 206 settings.config = _configuration; 207 settings.buildType = _buildType; 208 settings.compiler = _compiler; 209 settings.buildSettings = _settings; 210 settings.buildSettings.addOptions(BuildOption.syntaxOnly); 211 settings.combined = true; 212 settings.run = false; 213 214 try 215 { 216 auto paths = _dub.project.listBuildSettings(settings, [ 217 "import-paths", "string-import-paths", "source-files", "versions", "debug-versions" 218 ], ListBuildSettingsFormat.listNul); 219 _importPaths = paths[0].split('\0'); 220 _stringImportPaths = paths[1].split('\0'); 221 _importFiles = paths[2].split('\0'); 222 _versions = paths[3].split('\0'); 223 _debugVersions = paths[4].split('\0'); 224 return _importPaths.length > 0 || _importFiles.length > 0; 225 } 226 catch (Exception e) 227 { 228 workspaced.messageHandler.error(refInstance, "dub", 229 cast(int)WarningId.failedListingImportPaths, 230 "Error while listing import paths", 231 e.toString); 232 _importPaths = []; 233 _stringImportPaths = []; 234 return false; 235 } 236 } 237 238 /// Calls `dub upgrade` 239 void upgrade(bool save = true) 240 { 241 if (save) 242 _dub.upgrade(UpgradeOptions.select | UpgradeOptions.upgrade); 243 else 244 _dub.upgrade(UpgradeOptions.noSaveSelections); 245 } 246 247 /// Throws if configuration is invalid, otherwise does nothing. 248 void validateConfiguration() const 249 { 250 if (!_dub.project.configurations.canFind(_configuration)) 251 throw new Exception("Cannot use dub with invalid configuration"); 252 } 253 254 /// Throws if configuration is invalid or targetType is none or source library, otherwise does nothing. 255 void validateBuildConfiguration() 256 { 257 if (!_dub.project.configurations.canFind(_configuration)) 258 throw new Exception("Cannot use dub with invalid configuration"); 259 if (_settings.targetType == TargetType.none) 260 throw new Exception("Cannot build with dub with targetType == none"); 261 if (_settings.targetType == TargetType.sourceLibrary) 262 throw new Exception("Cannot build with dub with targetType == sourceLibrary"); 263 } 264 265 /// Lists all dependencies. This will go through all dependencies and contain the dependencies of dependencies. You need to create a tree structure from this yourself. 266 /// Returns: `[{dependencies: string[string], ver: string, name: string}]` 267 auto dependencies() @property const 268 { 269 validateConfiguration(); 270 271 return listDependencies(_dub.project); 272 } 273 274 /// Lists dependencies of the root package. This can be used as a base to create a tree structure. 275 string[] rootDependencies() @property const 276 { 277 validateConfiguration(); 278 279 return listDependencies(_dub.project.rootPackage); 280 } 281 282 /// Returns the path to the root package recipe (dub.json/dub.sdl) 283 /// 284 /// Note that this can be empty if the package is not in the local file system. 285 string recipePath() @property 286 { 287 return _dub.project.rootPackage.recipePath.toString; 288 } 289 290 /// Re-parses the package recipe on the file system and returns if the syntax is valid. 291 /// Returns: empty string/null if no error occured, error message if an error occured. 292 string validateRecipeSyntaxOnFileSystem() 293 { 294 auto p = recipePath; 295 if (!p.length) 296 return "Package is not in local file system"; 297 298 try 299 { 300 readPackageRecipe(p); 301 return null; 302 } 303 catch (Exception e) 304 { 305 return e.msg; 306 } 307 } 308 309 /// Lists all import paths 310 string[] imports() @property nothrow 311 { 312 return _importPaths; 313 } 314 315 /// Lists all string import paths 316 string[] stringImports() @property nothrow 317 { 318 return _stringImportPaths; 319 } 320 321 /// Lists all import paths to files 322 string[] fileImports() @property nothrow 323 { 324 return _importFiles; 325 } 326 327 /// Lists the currently defined versions 328 string[] versions() @property nothrow 329 { 330 return _versions; 331 } 332 333 /// Lists the currently defined debug versions (debug specifications) 334 string[] debugVersions() @property nothrow 335 { 336 return _debugVersions; 337 } 338 339 /// Lists all configurations defined in the package description 340 string[] configurations() @property 341 { 342 return _dub.project.configurations; 343 } 344 345 PackageBuildSettings rootPackageBuildSettings() @property 346 { 347 auto pkg = _dub.project.rootPackage; 348 BuildSettings settings = pkg.getBuildSettings(_platform, _configuration); 349 return PackageBuildSettings(settings, 350 pkg.path.toString, 351 pkg.name, 352 _dub.project.rootPackage.recipePath.toNativeString()); 353 } 354 355 /// Lists all build types defined in the package description AND the predefined ones from dub ("plain", "debug", "release", "release-debug", "release-nobounds", "unittest", "docs", "ddox", "profile", "profile-gc", "cov", "unittest-cov") 356 string[] buildTypes() const @property 357 { 358 string[] types = [ 359 "plain", "debug", "release", "release-debug", "release-nobounds", 360 "unittest", "docs", "ddox", "profile", "profile-gc", "cov", "unittest-cov" 361 ]; 362 foreach (type, info; _dub.project.rootPackage.recipe.buildTypes) 363 types ~= type; 364 return types; 365 } 366 367 /// Gets the current selected configuration 368 string configuration() const @property 369 { 370 return _configuration; 371 } 372 373 /// Selects a new configuration and updates the import paths accordingly 374 /// Returns: `false` if there are no import paths in the new configuration 375 bool setConfiguration(string configuration) 376 { 377 if (!_dub.project.configurations.canFind(configuration)) 378 return false; 379 _configuration = configuration; 380 _settingsTemplate = cast() _dub.project.rootPackage.getBuildSettings(configuration); 381 return updateImportPaths(false); 382 } 383 384 /// List all possible arch types for current set compiler 385 string[] archTypes() const @property 386 { 387 auto types = appender!(string[]); 388 types ~= ["x86_64", "x86"]; 389 390 string compilerName = _compiler.name; 391 392 if (compilerName == "dmd") 393 { 394 // https://github.com/dlang/dub/blob/master/source/dub/compilers/dmd.d#L110 395 version (Windows) 396 { 397 types ~= ["x86_omf", "x86_mscoff"]; 398 } 399 } 400 else if (compilerName == "gdc") 401 { 402 // https://github.com/dlang/dub/blob/master/source/dub/compilers/gdc.d#L69 403 types ~= ["arm", "arm_thumb"]; 404 } 405 else if (compilerName == "ldc") 406 { 407 // https://github.com/dlang/dub/blob/master/source/dub/compilers/ldc.d#L80 408 types ~= ["aarch64", "powerpc64"]; 409 } 410 411 return types.data; 412 } 413 414 /// ditto 415 ArchType[] extendedArchTypes() const @property 416 { 417 auto types = appender!(ArchType[]); 418 string compilerName = _compiler.name; 419 420 if (compilerName == "dmd") 421 { 422 types ~= [ 423 ArchType("", "(compiler default)"), 424 ArchType("x86_64"), 425 ArchType("x86") 426 ]; 427 // https://github.com/dlang/dub/blob/master/source/dub/compilers/dmd.d#L110 428 version (Windows) 429 { 430 types ~= [ArchType("x86_omf"), ArchType("x86_mscoff")]; 431 } 432 } 433 else if (compilerName == "gdc") 434 { 435 // https://github.com/dlang/dub/blob/master/source/dub/compilers/gdc.d#L69 436 types ~= [ 437 ArchType("", "(compiler default)"), 438 ArchType("x86_64", "64-bit (current platform)"), 439 ArchType("x86", "32-bit (current platform)"), 440 ArchType("arm"), 441 ArchType("arm_thumb") 442 ]; 443 } 444 else if (compilerName == "ldc") 445 { 446 types ~= [ 447 ArchType("", "(compiler default)"), 448 ArchType("x86_64"), 449 ArchType("x86") 450 ]; 451 // https://github.com/dlang/dub/blob/master/source/dub/compilers/ldc.d#L80 452 types ~= [ 453 ArchType("aarch64"), 454 ArchType("powerpc64"), 455 ArchType("wasm32-unknown-unknown-wasm", "WebAssembly") 456 ]; 457 } 458 459 return types.data; 460 } 461 462 /// Returns the current selected arch type, or empty string for compiler default. 463 string archType() const @property 464 { 465 return _archType; 466 } 467 468 /// Selects a new arch type and updates the import paths accordingly 469 /// Returns: `false` if there are no import paths in the new arch type 470 bool setArchType(string type) 471 { 472 try 473 { 474 _platform = _compiler.determinePlatform(_settings, _compilerBinaryName, type); 475 } 476 catch (Exception e) 477 { 478 return false; 479 } 480 481 _archType = type; 482 return updateImportPaths(false); 483 } 484 485 /// Returns the current selected build type 486 string buildType() const @property 487 { 488 return _buildType; 489 } 490 491 /// Selects a new build type and updates the import paths accordingly 492 /// Returns: `false` if there are no import paths in the new build type 493 bool setBuildType(string type) 494 { 495 if (buildTypes.canFind(type)) 496 { 497 _buildType = type; 498 return updateImportPaths(false); 499 } 500 else 501 { 502 return false; 503 } 504 } 505 506 /// Returns the current selected compiler 507 string compiler() const @property 508 { 509 return _compilerBinaryName; 510 } 511 512 /// Selects a new compiler for building 513 /// Returns: `false` if the compiler does not exist or some setting is 514 /// invalid. 515 /// 516 /// If the current architecture does not exist with this compiler it will be 517 /// reset to the compiler default. (empty string) 518 bool setCompiler(string compiler) 519 { 520 try 521 { 522 _compilerBinaryName = compiler; 523 _compiler = getCompiler(compiler); // make sure it gets a valid compiler 524 } 525 catch (Exception e) 526 { 527 return false; 528 } 529 530 try 531 { 532 _platform = _compiler.determinePlatform(_settings, _compilerBinaryName, _archType); 533 } 534 catch (UnsupportedArchitectureException e) 535 { 536 if (_archType.length) 537 { 538 _archType = ""; 539 return setCompiler(compiler); 540 } 541 return false; 542 } 543 544 _settingsTemplate.getPlatformSettings(_settings, _platform, 545 _dub.project.rootPackage.path); 546 return _compiler !is null; 547 } 548 549 /// Returns the project name 550 string name() const @property 551 { 552 return _dub.projectName; 553 } 554 555 /// Returns the project path 556 auto path() const @property 557 { 558 return _dub.projectPath; 559 } 560 561 /// Returns whether there is a target set to build. If this is false then build will throw an exception. 562 bool canBuild() const @property 563 { 564 if (_settings.targetType == TargetType.none || _settings.targetType == TargetType.sourceLibrary 565 || !_dub.project.configurations.canFind(_configuration)) 566 return false; 567 return true; 568 } 569 570 /// Asynchroniously builds the project WITHOUT OUTPUT. This is intended for linting code and showing build errors quickly inside the IDE. 571 Future!(BuildIssue[]) build() 572 { 573 import std.process : thisProcessID; 574 import std.file : tempDir; 575 import std.random : uniform; 576 577 validateBuildConfiguration(); 578 579 // copy to this thread 580 auto compiler = _compiler; 581 auto buildPlatform = _platform; 582 583 GeneratorSettings settings; 584 settings.platform = buildPlatform; 585 settings.config = _configuration; 586 settings.buildType = _buildType; 587 settings.compiler = compiler; 588 settings.buildSettings = _settings; 589 590 auto ret = new typeof(return); 591 new Thread({ 592 try 593 { 594 auto issues = appender!(BuildIssue[]); 595 596 settings.compileCallback = (status, output) { 597 string[] lines = output.splitLines; 598 foreach (line; lines) 599 { 600 auto match = line.matchFirst(errorFormat); 601 if (match) 602 { 603 issues ~= BuildIssue(match[2].to!int, match[3].toOr!int(0), 604 match[1], match[4].to!ErrorType, match[5]); 605 } 606 else 607 { 608 auto contMatch = line.matchFirst(errorFormatCont); 609 if (issues.data.length && contMatch) 610 { 611 issues ~= BuildIssue(contMatch[2].to!int, 612 contMatch[3].toOr!int(1), contMatch[1], 613 issues.data[$ - 1].type, contMatch[4], true); 614 } 615 else if (line.canFind("is deprecated")) 616 { 617 auto deprMatch = line.matchFirst(deprecationFormat); 618 if (deprMatch) 619 { 620 issues ~= BuildIssue(deprMatch[2].to!int, deprMatch[3].toOr!int(1), 621 deprMatch[1], ErrorType.Deprecation, 622 deprMatch[4] ~ " is deprecated, use " ~ deprMatch[5] ~ " instead."); 623 } 624 } 625 } 626 } 627 }; 628 try 629 { 630 import workspaced.dub.lintgenerator : DubLintGenerator; 631 632 new DubLintGenerator(_dub.project).generate(settings); 633 } 634 catch (Exception e) 635 { 636 if (!e.msg.matchFirst(harmlessExceptionFormat)) 637 throw e; 638 } 639 ret.finish(issues.data); 640 } 641 catch (Throwable t) 642 { 643 ret.error(t); 644 } 645 }).start(); 646 return ret; 647 } 648 649 /// Converts the root package recipe to another format. 650 /// Params: 651 /// format = either "json" or "sdl". 652 string convertRecipe(string format) 653 { 654 import dub.recipe.io : serializePackageRecipe; 655 import std.array : appender; 656 657 auto dst = appender!string; 658 serializePackageRecipe(dst, _dub.project.rootPackage.rawRecipe, "dub." ~ format); 659 return dst.data; 660 } 661 662 /// Tries to find a suitable code byte range where a given dub build issue 663 /// applies to. 664 /// Returns: `[pos, pos]` if not found, otherwise range in bytes which might 665 /// not contain the position at all. 666 int[2] resolveDiagnosticRange(scope const(char)[] code, int position, 667 scope const(char)[] diagnostic) 668 { 669 import dparse.lexer : getTokensForParser, LexerConfig; 670 import dparse.parser : parseModule; 671 import dparse.rollback_allocator : RollbackAllocator; 672 import workspaced.dub.diagnostics : resolveDubDiagnosticRange; 673 674 LexerConfig config; 675 RollbackAllocator rba; 676 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 677 auto parsed = parseModule(tokens, "equal_finder.d", &rba); 678 679 return resolveDubDiagnosticRange(code, tokens, parsed, position, diagnostic); 680 } 681 682 private: 683 Dub _dub; 684 bool _dubRunning = false; 685 string _configuration; 686 string _archType = ""; 687 string _buildType = "debug"; 688 string _compilerBinaryName; 689 Compiler _compiler; 690 BuildSettingsTemplate _settingsTemplate; 691 BuildSettings _settings; 692 BuildPlatform _platform; 693 string[] _importPaths, _stringImportPaths, _importFiles, _versions, _debugVersions; 694 } 695 696 /// 697 enum ErrorType : ubyte 698 { 699 /// 700 Error = 0, 701 /// 702 Warning = 1, 703 /// 704 Deprecation = 2 705 } 706 707 /// Returned by build 708 struct BuildIssue 709 { 710 /// 711 int line, column; 712 /// 713 string file; 714 /// The error type (Error/Warning/Deprecation) outputted by dmd or inherited from the last error if this is additional information of the last issue. (indicated by cont) 715 ErrorType type; 716 /// 717 string text; 718 /// true if this is additional error information for the last error. 719 bool cont; 720 } 721 722 private enum ignoreCopy; // UDA for ignored values on copy 723 /// returned by rootPackageBuildSettings 724 struct PackageBuildSettings 725 { 726 /// construct from dub build settings 727 this(BuildSettings dubBuildSettings, string packagePath, string packageName, string recipePath) 728 { 729 foreach (i, ref val; this.tupleof) 730 { 731 alias attr = __traits(getAttributes, this.tupleof[i]); 732 static if (attr.length == 0 || !__traits(isSame, attr[0], ignoreCopy)) 733 { 734 enum name = __traits(identifier, this.tupleof[i]); 735 val = __traits(getMember, dubBuildSettings, name); 736 } 737 } 738 this.packagePath = packagePath; 739 this.packageName = packageName; 740 this.recipePath = recipePath; 741 742 if (!targetName.length) 743 targetName = packageName; 744 745 version (Windows) 746 targetName ~= ".exe"; 747 748 this.targetType = dubBuildSettings.targetType.to!string; 749 foreach (enumMember; __traits(allMembers, BuildOption)) 750 { 751 enum value = __traits(getMember, BuildOption, enumMember); 752 if (value != 0 && dubBuildSettings.options.opDispatch!enumMember) 753 this.buildOptions ~= enumMember; 754 } 755 foreach (enumMember; __traits(allMembers, BuildRequirement)) 756 { 757 enum value = __traits(getMember, BuildRequirement, enumMember); 758 if (value != 0 && dubBuildSettings.requirements.opDispatch!enumMember) 759 this.buildRequirements ~= enumMember; 760 } 761 } 762 763 @ignoreCopy 764 string packagePath; 765 @ignoreCopy 766 string packageName; 767 @ignoreCopy 768 string recipePath; 769 770 string targetPath; /// same as dub BuildSettings 771 string targetName; /// same as dub BuildSettings 772 string workingDirectory; /// same as dub BuildSettings 773 string mainSourceFile; /// same as dub BuildSettings 774 string[] dflags; /// same as dub BuildSettings 775 string[] lflags; /// same as dub BuildSettings 776 string[] libs; /// same as dub BuildSettings 777 string[] linkerFiles; /// same as dub BuildSettings 778 string[] sourceFiles; /// same as dub BuildSettings 779 string[] copyFiles; /// same as dub BuildSettings 780 string[] extraDependencyFiles; /// same as dub BuildSettings 781 string[] versions; /// same as dub BuildSettings 782 string[] debugVersions; /// same as dub BuildSettings 783 string[] versionFilters; /// same as dub BuildSettings 784 string[] debugVersionFilters; /// same as dub BuildSettings 785 string[] importPaths; /// same as dub BuildSettings 786 string[] stringImportPaths; /// same as dub BuildSettings 787 string[] importFiles; /// same as dub BuildSettings 788 string[] stringImportFiles; /// same as dub BuildSettings 789 string[] preGenerateCommands; /// same as dub BuildSettings 790 string[] postGenerateCommands; /// same as dub BuildSettings 791 string[] preBuildCommands; /// same as dub BuildSettings 792 string[] postBuildCommands; /// same as dub BuildSettings 793 string[] preRunCommands; /// same as dub BuildSettings 794 string[] postRunCommands; /// same as dub BuildSettings 795 796 @ignoreCopy: 797 798 string targetType; /// same as dub BuildSettings 799 string[] buildOptions; /// same as dub BuildSettings 800 string[] buildRequirements; /// same as dub BuildSettings 801 } 802 803 private: 804 805 T toOr(T)(string s, T defaultValue) 806 { 807 if (!s || !s.length) 808 return defaultValue; 809 return s.to!T; 810 } 811 812 enum harmlessExceptionFormat = ctRegex!(`failed with exit code`, "g"); 813 enum errorFormat = ctRegex!(`(.*?)\((\d+)(?:,(\d+))?\): (Deprecation|Warning|Error): (.*)`, "gi"); 814 enum errorFormatCont = ctRegex!(`(.*?)\((\d+)(?:,(\d+))?\):[ ]{6,}(.*)`, "g"); 815 enum deprecationFormat = ctRegex!( 816 `(.*?)\((\d+)(?:,(\d+))?\): (.*?) is deprecated, use (.*?) instead.$`, "g"); 817 818 struct DubPackageInfo 819 { 820 string[string] dependencies; 821 string ver; 822 string name; 823 string path; 824 string description; 825 string homepage; 826 const(string)[] authors; 827 string copyright; 828 string license; 829 DubPackageInfo[] subPackages; 830 831 void fill(in PackageRecipe recipe) 832 { 833 description = recipe.description; 834 homepage = recipe.homepage; 835 authors = recipe.authors; 836 copyright = recipe.copyright; 837 license = recipe.license; 838 839 foreach (subpackage; recipe.subPackages) 840 { 841 DubPackageInfo info; 842 info.ver = subpackage.recipe.version_; 843 info.name = subpackage.recipe.name; 844 info.path = subpackage.path; 845 info.fill(subpackage.recipe); 846 } 847 } 848 } 849 850 DubPackageInfo getInfo(in Package dep) 851 { 852 DubPackageInfo info; 853 info.name = dep.name; 854 info.ver = dep.version_.toString; 855 info.path = dep.path.toString; 856 info.fill(dep.recipe); 857 foreach (subDep; dep.getAllDependencies()) 858 { 859 info.dependencies[subDep.name] = subDep.spec.toString; 860 } 861 return info; 862 } 863 864 auto listDependencies(scope const Project project) 865 { 866 auto deps = project.dependencies; 867 DubPackageInfo[] dependencies; 868 if (deps is null) 869 return dependencies; 870 foreach (dep; deps) 871 { 872 dependencies ~= getInfo(dep); 873 } 874 return dependencies; 875 } 876 877 string[] listDependencies(scope const Package pkg) 878 { 879 auto deps = pkg.getAllDependencies(); 880 string[] dependencies; 881 if (deps is null) 882 return dependencies; 883 foreach (dep; deps) 884 dependencies ~= dep.name; 885 return dependencies; 886 } 887 888 /// 889 struct ArchType 890 { 891 /// Value to pass into other calls 892 string value; 893 /// UI label override or null if none 894 string label; 895 }