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.json : JSONType, JSONValue; 12 import std.parallelism; 13 import std.regex; 14 import std.stdio; 15 import std.string; 16 17 import painlessjson : toJSON, fromJSON; 18 19 import workspaced.api; 20 21 import dub.description; 22 import dub.dub; 23 import dub.package_; 24 import dub.project; 25 26 import dub.compilers.buildsettings; 27 import dub.compilers.compiler; 28 import dub.dependency; 29 import dub.generators.build; 30 import dub.generators.generator; 31 32 import dub.internal.vibecompat.core.log; 33 import dub.internal.vibecompat.inet.url; 34 35 import dub.recipe.io; 36 37 @component("dub") 38 class DubComponent : ComponentWrapper 39 { 40 mixin DefaultComponentWrapper; 41 42 static void registered() 43 { 44 setLogLevel(LogLevel.none); 45 } 46 47 protected void load() 48 { 49 if (!refInstance) 50 throw new Exception("dub requires to be instanced"); 51 52 if (config.get!bool("dub", "registerImportProvider", true)) 53 importPathProvider = &imports; 54 if (config.get!bool("dub", "registerStringImportProvider", true)) 55 stringImportPathProvider = &stringImports; 56 if (config.get!bool("dub", "registerImportFilesProvider", false)) 57 importFilesProvider = &fileImports; 58 if (config.get!bool("dub", "registerProjectVersionsProvider", true)) 59 projectVersionsProvider = &versions; 60 if (config.get!bool("dub", "registerDebugSpecificationsProvider", true)) 61 debugSpecificationsProvider = &debugVersions; 62 63 try 64 { 65 start(); 66 67 _configuration = _dub.project.getDefaultConfiguration(_platform); 68 if (!_dub.project.configurations.canFind(_configuration)) 69 { 70 stderr.writeln("Dub Error: No configuration available"); 71 workspaced.broadcast(refInstance, JSONValue([ 72 "type": JSONValue("warning"), 73 "component": JSONValue("dub"), 74 "detail": JSONValue("invalid-default-config") 75 ])); 76 } 77 else 78 updateImportPaths(false); 79 } 80 catch (Exception e) 81 { 82 if (!_dub || !_dub.project) 83 throw e; 84 stderr.writeln("Dub Error (ignored): ", e); 85 } 86 /*catch (AssertError e) 87 { 88 if (!_dub || !_dub.project) 89 throw e; 90 stderr.writeln("Dub Error (ignored): ", e); 91 }*/ 92 } 93 94 private void start() 95 { 96 _dubRunning = false; 97 _dub = new Dub(instance.cwd, null, SkipPackageSuppliers.none); 98 _dub.packageManager.getOrLoadPackage(NativePath(instance.cwd)); 99 _dub.loadPackage(); 100 _dub.project.validate(); 101 102 // mark all packages as optional so we don't crash 103 int missingPackages; 104 auto optionalified = optionalifyPackages; 105 foreach (ref pkg; _dub.project.getTopologicalPackageList()) 106 { 107 optionalifyRecipe(pkg); 108 foreach (dep; pkg.getAllDependencies() 109 .filter!(a => optionalified.canFind(a.name))) 110 { 111 auto d = _dub.project.getDependency(dep.name, true); 112 if (!d) 113 missingPackages++; 114 else 115 optionalifyRecipe(d); 116 } 117 } 118 119 if (!_compilerBinaryName.length) 120 _compilerBinaryName = _dub.defaultCompiler; 121 setCompiler(_compilerBinaryName); 122 123 _settingsTemplate = cast() _dub.project.rootPackage.getBuildSettings(); 124 125 if (missingPackages > 0) 126 { 127 upgrade(false); 128 optionalifyPackages(); 129 } 130 131 _dubRunning = true; 132 } 133 134 private string[] optionalifyPackages() 135 { 136 bool[Package] visited; 137 string[] optionalified; 138 foreach (pkg; _dub.project.dependencies) 139 optionalified ~= optionalifyRecipe(cast() pkg); 140 return optionalified; 141 } 142 143 private string[] optionalifyRecipe(Package pkg) 144 { 145 string[] optionalified; 146 foreach (key, ref value; pkg.recipe.buildSettings.dependencies) 147 { 148 if (!value.optional) 149 { 150 value.optional = true; 151 value.default_ = true; 152 optionalified ~= key; 153 } 154 } 155 foreach (ref config; pkg.recipe.configurations) 156 foreach (key, ref value; config.buildSettings.dependencies) 157 { 158 if (!value.optional) 159 { 160 value.optional = true; 161 value.default_ = true; 162 optionalified ~= key; 163 } 164 } 165 return optionalified; 166 } 167 168 private void restart() 169 { 170 _dub.destroy(); 171 _dubRunning = false; 172 start(); 173 } 174 175 bool isRunning() 176 { 177 return _dub !is null 178 && _dub.project !is null 179 && _dub.project.rootPackage !is null 180 && _dubRunning; 181 } 182 183 /// Reloads the dub.json or dub.sdl file from the cwd 184 /// Returns: `false` if there are no import paths available 185 Future!bool update() 186 { 187 restart(); 188 mixin(gthreadsAsyncProxy!`updateImportPaths(false)`); 189 } 190 191 bool updateImportPaths(bool restartDub = true) 192 { 193 validateConfiguration(); 194 195 if (restartDub) 196 restart(); 197 198 GeneratorSettings settings; 199 settings.platform = _platform; 200 settings.config = _configuration; 201 settings.buildType = _buildType; 202 settings.compiler = _compiler; 203 settings.buildSettings = _settings; 204 settings.buildSettings.addOptions(BuildOption.syntaxOnly); 205 settings.combined = true; 206 settings.run = false; 207 208 try 209 { 210 auto paths = _dub.project.listBuildSettings(settings, [ 211 "import-paths", "string-import-paths", "source-files", "versions", "debug-versions" 212 ], ListBuildSettingsFormat.listNul); 213 _importPaths = paths[0].split('\0'); 214 _stringImportPaths = paths[1].split('\0'); 215 _importFiles = paths[2].split('\0'); 216 _versions = paths[3].split('\0'); 217 _debugVersions = paths[4].split('\0'); 218 return _importPaths.length > 0 || _importFiles.length > 0; 219 } 220 catch (Exception e) 221 { 222 workspaced.broadcast(refInstance, JSONValue([ 223 "type": JSONValue("error"), 224 "component": JSONValue("dub"), 225 "detail": JSONValue("Error while listing import paths: " ~ e.toString) 226 ])); 227 _importPaths = []; 228 _stringImportPaths = []; 229 return false; 230 } 231 } 232 233 /// Calls `dub upgrade` 234 void upgrade(bool save = true) 235 { 236 if (save) 237 _dub.upgrade(UpgradeOptions.select | UpgradeOptions.upgrade); 238 else 239 _dub.upgrade(UpgradeOptions.noSaveSelections); 240 } 241 242 /// Throws if configuration is invalid, otherwise does nothing. 243 void validateConfiguration() const 244 { 245 if (!_dub.project.configurations.canFind(_configuration)) 246 throw new Exception("Cannot use dub with invalid configuration"); 247 } 248 249 /// Throws if configuration is invalid or targetType is none or source library, otherwise does nothing. 250 void validateBuildConfiguration() 251 { 252 if (!_dub.project.configurations.canFind(_configuration)) 253 throw new Exception("Cannot use dub with invalid configuration"); 254 if (_settings.targetType == TargetType.none) 255 throw new Exception("Cannot build with dub with targetType == none"); 256 if (_settings.targetType == TargetType.sourceLibrary) 257 throw new Exception("Cannot build with dub with targetType == sourceLibrary"); 258 } 259 260 /// 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. 261 /// Returns: `[{dependencies: string[string], ver: string, name: string}]` 262 auto dependencies() @property const 263 { 264 validateConfiguration(); 265 266 return listDependencies(_dub.project); 267 } 268 269 /// Lists dependencies of the root package. This can be used as a base to create a tree structure. 270 string[] rootDependencies() @property const 271 { 272 validateConfiguration(); 273 274 return listDependencies(_dub.project.rootPackage); 275 } 276 277 /// Returns the path to the root package recipe (dub.json/dub.sdl) 278 /// 279 /// Note that this can be empty if the package is not in the local file system. 280 string recipePath() @property 281 { 282 return _dub.project.rootPackage.recipePath.toString; 283 } 284 285 /// Re-parses the package recipe on the file system and returns if the syntax is valid. 286 /// Returns: empty string/null if no error occured, error message if an error occured. 287 string validateRecipeSyntaxOnFileSystem() 288 { 289 auto p = recipePath; 290 if (!p.length) 291 return "Package is not in local file system"; 292 293 try 294 { 295 readPackageRecipe(p); 296 return null; 297 } 298 catch (Exception e) 299 { 300 return e.msg; 301 } 302 } 303 304 /// Lists all import paths 305 string[] imports() @property nothrow 306 { 307 return _importPaths; 308 } 309 310 /// Lists all string import paths 311 string[] stringImports() @property nothrow 312 { 313 return _stringImportPaths; 314 } 315 316 /// Lists all import paths to files 317 string[] fileImports() @property nothrow 318 { 319 return _importFiles; 320 } 321 322 /// Lists the currently defined versions 323 string[] versions() @property nothrow 324 { 325 return _versions; 326 } 327 328 /// Lists the currently defined debug versions (debug specifications) 329 string[] debugVersions() @property nothrow 330 { 331 return _debugVersions; 332 } 333 334 /// Lists all configurations defined in the package description 335 string[] configurations() @property 336 { 337 return _dub.project.configurations; 338 } 339 340 PackageBuildSettings rootPackageBuildSettings() @property 341 { 342 auto pkg = _dub.project.rootPackage; 343 BuildSettings settings = pkg.getBuildSettings(_platform, _configuration); 344 return PackageBuildSettings(settings, 345 pkg.path.toString, 346 pkg.name, 347 _dub.project.rootPackage.recipePath.toNativeString()); 348 } 349 350 /// 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") 351 string[] buildTypes() const @property 352 { 353 string[] types = [ 354 "plain", "debug", "release", "release-debug", "release-nobounds", 355 "unittest", "docs", "ddox", "profile", "profile-gc", "cov", "unittest-cov" 356 ]; 357 foreach (type, info; _dub.project.rootPackage.recipe.buildTypes) 358 types ~= type; 359 return types; 360 } 361 362 /// Gets the current selected configuration 363 string configuration() const @property 364 { 365 return _configuration; 366 } 367 368 /// Selects a new configuration and updates the import paths accordingly 369 /// Returns: `false` if there are no import paths in the new configuration 370 bool setConfiguration(string configuration) 371 { 372 if (!_dub.project.configurations.canFind(configuration)) 373 return false; 374 _configuration = configuration; 375 _settingsTemplate = cast() _dub.project.rootPackage.getBuildSettings(configuration); 376 return updateImportPaths(false); 377 } 378 379 /// List all possible arch types for current set compiler 380 string[] archTypes() const @property 381 { 382 auto types = appender!(string[]); 383 types ~= ["x86_64", "x86"]; 384 385 string compilerName = _compiler.name; 386 387 if (compilerName == "dmd") 388 { 389 // https://github.com/dlang/dub/blob/master/source/dub/compilers/dmd.d#L110 390 version (Windows) 391 { 392 types ~= ["x86_omf", "x86_mscoff"]; 393 } 394 } 395 else if (compilerName == "gdc") 396 { 397 // https://github.com/dlang/dub/blob/master/source/dub/compilers/gdc.d#L69 398 types ~= ["arm", "arm_thumb"]; 399 } 400 else if (compilerName == "ldc") 401 { 402 // https://github.com/dlang/dub/blob/master/source/dub/compilers/ldc.d#L80 403 types ~= ["aarch64", "powerpc64"]; 404 } 405 406 return types.data; 407 } 408 409 /// ditto 410 ArchType[] extendedArchTypes() const @property 411 { 412 auto types = appender!(ArchType[]); 413 string compilerName = _compiler.name; 414 415 if (compilerName == "dmd") 416 { 417 types ~= [ 418 ArchType("", "(compiler default)"), 419 ArchType("x86_64"), 420 ArchType("x86") 421 ]; 422 // https://github.com/dlang/dub/blob/master/source/dub/compilers/dmd.d#L110 423 version (Windows) 424 { 425 types ~= [ArchType("x86_omf"), ArchType("x86_mscoff")]; 426 } 427 } 428 else if (compilerName == "gdc") 429 { 430 // https://github.com/dlang/dub/blob/master/source/dub/compilers/gdc.d#L69 431 types ~= [ 432 ArchType("", "(compiler default)"), 433 ArchType("x86_64", "64-bit (current platform)"), 434 ArchType("x86", "32-bit (current platform)"), 435 ArchType("arm"), 436 ArchType("arm_thumb") 437 ]; 438 } 439 else if (compilerName == "ldc") 440 { 441 types ~= [ 442 ArchType("", "(compiler default)"), 443 ArchType("x86_64"), 444 ArchType("x86") 445 ]; 446 // https://github.com/dlang/dub/blob/master/source/dub/compilers/ldc.d#L80 447 types ~= [ 448 ArchType("aarch64"), 449 ArchType("powerpc64"), 450 ArchType("wasm32-unknown-unknown-wasm", "WebAssembly") 451 ]; 452 } 453 454 return types.data; 455 } 456 457 /// Returns the current selected arch type, or empty string for compiler default. 458 string archType() const @property 459 { 460 return _archType; 461 } 462 463 /// Selects a new arch type and updates the import paths accordingly 464 /// Returns: `false` if there are no import paths in the new arch type 465 bool setArchType(JSONValue request) 466 { 467 enforce(request.type == JSONType.object && "arch-type" in request, "arch-type not in request"); 468 auto type = request["arch-type"].fromJSON!string; 469 470 try 471 { 472 _platform = _compiler.determinePlatform(_settings, _compilerBinaryName, type); 473 } 474 catch (Exception e) 475 { 476 return false; 477 } 478 479 _archType = type; 480 return updateImportPaths(false); 481 } 482 483 /// Returns the current selected build type 484 string buildType() const @property 485 { 486 return _buildType; 487 } 488 489 /// Selects a new build type and updates the import paths accordingly 490 /// Returns: `false` if there are no import paths in the new build type 491 bool setBuildType(JSONValue request) 492 { 493 enforce(request.type == JSONType.object && "build-type" in request, "build-type not in request"); 494 auto type = request["build-type"].fromJSON!string; 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 /// returned by rootPackageBuildSettings 723 struct PackageBuildSettings 724 { 725 /// construct from dub build settings 726 this(BuildSettings dubBuildSettings, string packagePath, string packageName, string recipePath) 727 { 728 foreach (i, ref val; this.tupleof[0 .. __IGNORE_TRAIL]) 729 { 730 enum name = __traits(identifier, this.tupleof[i]); 731 static if (__traits(hasMember, dubBuildSettings, name)) 732 val = __traits(getMember, dubBuildSettings, name); 733 } 734 this.packagePath = packagePath; 735 this.packageName = packageName; 736 this.recipePath = recipePath; 737 738 if (!targetName.length) 739 targetName = packageName; 740 741 version (Windows) 742 targetName ~= ".exe"; 743 744 this.targetType = dubBuildSettings.targetType.to!string; 745 foreach (enumMember; __traits(allMembers, BuildOption)) 746 { 747 enum value = __traits(getMember, BuildOption, enumMember); 748 if (value != 0 && dubBuildSettings.options.opDispatch!enumMember) 749 this.buildOptions ~= enumMember; 750 } 751 foreach (enumMember; __traits(allMembers, BuildRequirement)) 752 { 753 enum value = __traits(getMember, BuildRequirement, enumMember); 754 if (value != 0 && dubBuildSettings.requirements.opDispatch!enumMember) 755 this.buildRequirements ~= enumMember; 756 } 757 } 758 759 string packagePath; 760 string packageName; 761 string recipePath; 762 763 string targetPath; /// same as dub BuildSettings 764 string targetName; /// same as dub BuildSettings 765 string workingDirectory; /// same as dub BuildSettings 766 string mainSourceFile; /// same as dub BuildSettings 767 string[] dflags; /// same as dub BuildSettings 768 string[] lflags; /// same as dub BuildSettings 769 string[] libs; /// same as dub BuildSettings 770 string[] linkerFiles; /// same as dub BuildSettings 771 string[] sourceFiles; /// same as dub BuildSettings 772 string[] copyFiles; /// same as dub BuildSettings 773 string[] extraDependencyFiles; /// same as dub BuildSettings 774 string[] versions; /// same as dub BuildSettings 775 string[] debugVersions; /// same as dub BuildSettings 776 string[] versionFilters; /// same as dub BuildSettings 777 string[] debugVersionFilters; /// same as dub BuildSettings 778 string[] importPaths; /// same as dub BuildSettings 779 string[] stringImportPaths; /// same as dub BuildSettings 780 string[] importFiles; /// same as dub BuildSettings 781 string[] stringImportFiles; /// same as dub BuildSettings 782 string[] preGenerateCommands; /// same as dub BuildSettings 783 string[] postGenerateCommands; /// same as dub BuildSettings 784 string[] preBuildCommands; /// same as dub BuildSettings 785 string[] postBuildCommands; /// same as dub BuildSettings 786 string[] preRunCommands; /// same as dub BuildSettings 787 string[] postRunCommands; /// same as dub BuildSettings 788 789 private enum __IGNORE_TRAIL = 2; // number of ignored settings below this line 790 791 string targetType; /// same as dub BuildSettings 792 string[] buildOptions; /// same as dub BuildSettings 793 string[] buildRequirements; /// same as dub BuildSettings 794 } 795 796 private: 797 798 T toOr(T)(string s, T defaultValue) 799 { 800 if (!s || !s.length) 801 return defaultValue; 802 return s.to!T; 803 } 804 805 enum harmlessExceptionFormat = ctRegex!(`failed with exit code`, "g"); 806 enum errorFormat = ctRegex!(`(.*?)\((\d+)(?:,(\d+))?\): (Deprecation|Warning|Error): (.*)`, "gi"); 807 enum errorFormatCont = ctRegex!(`(.*?)\((\d+)(?:,(\d+))?\):[ ]{6,}(.*)`, "g"); 808 enum deprecationFormat = ctRegex!( 809 `(.*?)\((\d+)(?:,(\d+))?\): (.*?) is deprecated, use (.*?) instead.$`, "g"); 810 811 struct DubPackageInfo 812 { 813 string[string] dependencies; 814 string ver; 815 string name; 816 string path; 817 string description; 818 string homepage; 819 const(string)[] authors; 820 string copyright; 821 string license; 822 DubPackageInfo[] subPackages; 823 824 void fill(in PackageRecipe recipe) 825 { 826 description = recipe.description; 827 homepage = recipe.homepage; 828 authors = recipe.authors; 829 copyright = recipe.copyright; 830 license = recipe.license; 831 832 foreach (subpackage; recipe.subPackages) 833 { 834 DubPackageInfo info; 835 info.ver = subpackage.recipe.version_; 836 info.name = subpackage.recipe.name; 837 info.path = subpackage.path; 838 info.fill(subpackage.recipe); 839 } 840 } 841 } 842 843 DubPackageInfo getInfo(in Package dep) 844 { 845 DubPackageInfo info; 846 info.name = dep.name; 847 info.ver = dep.version_.toString; 848 info.path = dep.path.toString; 849 info.fill(dep.recipe); 850 foreach (subDep; dep.getAllDependencies()) 851 { 852 info.dependencies[subDep.name] = subDep.spec.toString; 853 } 854 return info; 855 } 856 857 auto listDependencies(scope const Project project) 858 { 859 auto deps = project.dependencies; 860 DubPackageInfo[] dependencies; 861 if (deps is null) 862 return dependencies; 863 foreach (dep; deps) 864 { 865 dependencies ~= getInfo(dep); 866 } 867 return dependencies; 868 } 869 870 string[] listDependencies(scope const Package pkg) 871 { 872 auto deps = pkg.getAllDependencies(); 873 string[] dependencies; 874 if (deps is null) 875 return dependencies; 876 foreach (dep; deps) 877 dependencies ~= dep.name; 878 return dependencies; 879 } 880 881 /// 882 struct ArchType 883 { 884 /// Value to pass into other calls 885 string value; 886 /// UI label override or null if none 887 string label; 888 }