1 module dlangide.workspace.project; 2 3 import dlangide.workspace.workspace; 4 import dlangide.workspace.projectsettings; 5 import dlangui.core.logger; 6 import dlangui.core.collections; 7 import dlangui.core.settings; 8 import std.algorithm; 9 import std.array : empty; 10 import std.file; 11 import std.path; 12 import std.process; 13 import std.utf; 14 15 string[] includePath; 16 17 /// return true if filename matches rules for workspace file names 18 bool isProjectFile(in string filename) pure nothrow { 19 return filename.baseName.equal("dub.json") || filename.baseName.equal("DUB.JSON") || filename.baseName.equal("package.json") || 20 filename.baseName.equal("dub.sdl") || filename.baseName.equal("DUB.SDL"); 21 } 22 23 string toForwardSlashSeparator(in string filename) pure nothrow { 24 char[] res; 25 foreach(ch; filename) { 26 if (ch == '\\') 27 res ~= '/'; 28 else 29 res ~= ch; 30 } 31 return cast(string)res; 32 } 33 34 /// project item 35 class ProjectItem { 36 protected Project _project; 37 protected ProjectItem _parent; 38 protected string _filename; 39 protected dstring _name; 40 41 this(string filename) { 42 _filename = buildNormalizedPath(filename); 43 _name = toUTF32(baseName(_filename)); 44 } 45 46 this() { 47 } 48 49 @property ProjectItem parent() { return _parent; } 50 51 @property Project project() { return _project; } 52 53 @property void project(Project p) { _project = p; } 54 55 @property string filename() { return _filename; } 56 57 @property string directory() { 58 import std.path : dirName; 59 return _filename.dirName; 60 } 61 62 @property dstring name() { return _name; } 63 64 @property string name8() { 65 return _name.toUTF8; 66 } 67 68 /// returns true if item is folder 69 @property const bool isFolder() { return false; } 70 /// returns child object count 71 @property int childCount() { return 0; } 72 /// returns child item by index 73 ProjectItem child(int index) { return null; } 74 75 void refresh() { 76 } 77 78 ProjectSourceFile findSourceFile(string projectFileName, string fullFileName) { 79 if (fullFileName.equal(_filename)) 80 return cast(ProjectSourceFile)this; 81 if (project && projectFileName.equal(project.absoluteToRelativePath(_filename))) 82 return cast(ProjectSourceFile)this; 83 return null; 84 } 85 86 @property bool isDSourceFile() { 87 if (isFolder) 88 return false; 89 return filename.endsWith(".d") || filename.endsWith(".dd") || filename.endsWith(".dd") || filename.endsWith(".di") || filename.endsWith(".dh") || filename.endsWith(".ddoc"); 90 } 91 92 @property bool isJsonFile() { 93 if (isFolder) 94 return false; 95 return filename.endsWith(".json") || filename.endsWith(".JSON"); 96 } 97 98 @property bool isDMLFile() { 99 if (isFolder) 100 return false; 101 return filename.endsWith(".dml") || filename.endsWith(".DML"); 102 } 103 104 @property bool isXMLFile() { 105 if (isFolder) 106 return false; 107 return filename.endsWith(".xml") || filename.endsWith(".XML"); 108 } 109 } 110 111 /// Project folder 112 class ProjectFolder : ProjectItem { 113 protected ObjectList!ProjectItem _children; 114 115 this(string filename) { 116 super(filename); 117 } 118 119 @property override const bool isFolder() { 120 return true; 121 } 122 @property override int childCount() { 123 return _children.count; 124 } 125 /// returns child item by index 126 override ProjectItem child(int index) { 127 return _children[index]; 128 } 129 void addChild(ProjectItem item) { 130 _children.add(item); 131 item._parent = this; 132 item._project = _project; 133 } 134 ProjectItem childByPathName(string path) { 135 for (int i = 0; i < _children.count; i++) { 136 if (_children[i].filename.equal(path)) 137 return _children[i]; 138 } 139 return null; 140 } 141 ProjectItem childByName(dstring s) { 142 for (int i = 0; i < _children.count; i++) { 143 if (_children[i].name.equal(s)) 144 return _children[i]; 145 } 146 return null; 147 } 148 149 override ProjectSourceFile findSourceFile(string projectFileName, string fullFileName) { 150 for (int i = 0; i < _children.count; i++) { 151 if (ProjectSourceFile res = _children[i].findSourceFile(projectFileName, fullFileName)) 152 return res; 153 } 154 return null; 155 } 156 157 bool loadDir(string path) { 158 string src = relativeToAbsolutePath(path); 159 if (exists(src) && isDir(src)) { 160 ProjectFolder existing = cast(ProjectFolder)childByPathName(src); 161 if (existing) { 162 if (existing.isFolder) 163 existing.loadItems(); 164 return true; 165 } 166 auto dir = new ProjectFolder(src); 167 addChild(dir); 168 Log.d(" added project folder ", src); 169 dir.loadItems(); 170 return true; 171 } 172 return false; 173 } 174 175 bool loadFile(string path) { 176 string src = relativeToAbsolutePath(path); 177 if (exists(src) && isFile(src)) { 178 ProjectItem existing = childByPathName(src); 179 if (existing) 180 return true; 181 auto f = new ProjectSourceFile(src); 182 addChild(f); 183 Log.d(" added project file ", src); 184 return true; 185 } 186 return false; 187 } 188 189 void loadItems() { 190 bool[string] loaded; 191 string path = _filename; 192 if (exists(path) && isFile(path)) 193 path = dirName(path); 194 foreach(e; dirEntries(path, SpanMode.shallow)) { 195 string fn = baseName(e.name); 196 if (e.isDir) { 197 loadDir(fn); 198 loaded[fn] = true; 199 } else if (e.isFile) { 200 loadFile(fn); 201 loaded[fn] = true; 202 } 203 } 204 // removing non-reloaded items 205 for (int i = _children.count - 1; i >= 0; i--) { 206 if (!(toUTF8(_children[i].name) in loaded)) { 207 _children.remove(i); 208 } 209 } 210 sortItems(); 211 } 212 213 /// predicate for sorting project items 214 static bool compareProjectItemsLess(ProjectItem item1, ProjectItem item2) { 215 return ((item1.isFolder && !item2.isFolder) || ((item1.isFolder == item2.isFolder) && (item1.name < item2.name))); 216 } 217 218 void sortItems() { 219 import std.algorithm.sorting : sort; 220 sort!compareProjectItemsLess(_children.asArray); 221 } 222 223 string relativeToAbsolutePath(string path) { 224 if (isAbsolute(path)) 225 return path; 226 string fn = _filename; 227 if (exists(fn) && isFile(fn)) 228 fn = dirName(fn); 229 return buildNormalizedPath(fn, path); 230 } 231 232 override void refresh() { 233 loadItems(); 234 } 235 } 236 237 238 /// Project source file 239 class ProjectSourceFile : ProjectItem { 240 this(string filename) { 241 super(filename); 242 } 243 /// file path relative to project directory 244 @property string projectFilePath() { 245 return project.absoluteToRelativePath(filename); 246 } 247 248 void setFilename(string filename) { 249 _filename = buildNormalizedPath(filename); 250 _name = toUTF32(baseName(_filename)); 251 } 252 } 253 254 class WorkspaceItem { 255 protected string _filename; 256 protected string _dir; 257 protected dstring _name; 258 protected dstring _originalName; 259 protected dstring _description; 260 261 this(string fname = null) { 262 filename = fname; 263 } 264 265 /// file name of workspace item 266 @property string filename() { return _filename; } 267 268 /// workspace item directory 269 @property string dir() { return _dir; } 270 271 /// file name of workspace item 272 @property void filename(string fname) { 273 if (fname.length > 0) { 274 _filename = buildNormalizedPath(fname); 275 _dir = dirName(filename); 276 } else { 277 _filename = null; 278 _dir = null; 279 } 280 } 281 282 /// name 283 @property dstring name() { return _name; } 284 285 @property string name8() { 286 return _name.toUTF8; 287 } 288 289 /// name 290 @property void name(dstring s) { _name = s; } 291 292 /// description 293 @property dstring description() { return _description; } 294 /// description 295 @property void description(dstring s) { _description = s; } 296 297 /// load 298 bool load(string fname) { 299 // override it 300 return false; 301 } 302 303 bool save(string fname = null) { 304 return false; 305 } 306 } 307 308 /// Stores info about project configuration 309 struct ProjectConfiguration { 310 /// name used to build the project 311 string name; 312 /// type, for libraries one can run tests, for apps - execute them 313 Type type; 314 315 /// How to display default configuration in ui 316 immutable static string DEFAULT_NAME = "default"; 317 /// Default project configuration 318 immutable static ProjectConfiguration DEFAULT = ProjectConfiguration(DEFAULT_NAME, Type.Default); 319 320 /// Type of configuration 321 enum Type { 322 Default, 323 Executable, 324 Library 325 } 326 327 private static Type parseType(string s) 328 { 329 switch(s) 330 { 331 case "executable": return Type.Executable; 332 case "library": return Type.Library; 333 case "dynamicLibrary": return Type.Library; 334 case "staticLibrary": return Type.Library; 335 default: return Type.Default; 336 } 337 } 338 339 /// parsing from setting file 340 static ProjectConfiguration[] load(Setting s) 341 { 342 ProjectConfiguration[] res; 343 Setting configs = s.objectByPath("configurations"); 344 if(configs is null || configs.type != SettingType.ARRAY) { 345 res ~= DEFAULT; 346 return res; 347 } 348 349 foreach(conf; configs) { 350 if(!conf.isObject) continue; 351 Type t = Type.Default; 352 if(auto typeName = conf.getString("targetType")) 353 t = parseType(typeName); 354 if (string confName = conf.getString("name")) { 355 res ~= ProjectConfiguration(confName, t); 356 } 357 } 358 return res; 359 } 360 } 361 362 /// DLANGIDE D project 363 class Project : WorkspaceItem { 364 import dlangide.workspace.idesettings : IDESettings; 365 protected Workspace _workspace; 366 protected bool _opened; 367 protected ProjectFolder _items; 368 protected ProjectSourceFile _mainSourceFile; 369 protected SettingsFile _projectFile; 370 protected ProjectSettings _settingsFile; 371 protected bool _isDependency; 372 protected bool _isSubproject; 373 protected bool _isEmbeddedSubproject; 374 protected dstring _baseProjectName; 375 protected string _dependencyVersion; 376 377 protected string[] _sourcePaths; 378 protected ProjectConfiguration[] _configurations; 379 protected ProjectConfiguration _projectConfiguration = ProjectConfiguration.DEFAULT; 380 381 @property int projectConfigurationIndex() { 382 ProjectConfiguration config = projectConfiguration(); 383 foreach(i, value; _configurations) { 384 if (value.name == config.name) 385 return cast(int)i; 386 } 387 return 0; 388 } 389 390 @property ProjectConfiguration projectConfiguration() { 391 if (_configurations.length == 0) 392 return ProjectConfiguration.DEFAULT; 393 string configName = settings.projectConfiguration; 394 foreach(config; _configurations) { 395 if (configName == config.name) 396 return config; 397 } 398 return _configurations[0]; 399 } 400 401 @property void projectConfiguration(ProjectConfiguration config) { 402 settings.projectConfiguration = config.name; 403 settings.save(); 404 } 405 406 @property void projectConfiguration(string configName) { 407 foreach(name, config; _configurations) { 408 if (configName == config.name) { 409 settings.projectConfiguration = config.name; 410 settings.save(); 411 return; 412 } 413 } 414 } 415 416 this(Workspace ws, string fname = null, string dependencyVersion = null) { 417 super(fname); 418 _workspace = ws; 419 420 if (_workspace) { 421 foreach(obj; _workspace.includePath.array) 422 includePath ~= obj.str; 423 } 424 425 _items = new ProjectFolder(fname); 426 _dependencyVersion = dependencyVersion; 427 _isDependency = _dependencyVersion.length > 0; 428 _projectFile = new SettingsFile(fname); 429 } 430 431 void setBaseProject(Project p) { 432 if (p) { 433 _isSubproject = true; 434 _isDependency = p._isDependency; 435 _baseProjectName = p._originalName; 436 _dependencyVersion = p._dependencyVersion; 437 } else { 438 _isSubproject = false; 439 } 440 } 441 442 void setSubprojectJson(Setting s) { 443 if (!_projectFile) 444 _projectFile = new SettingsFile(); 445 _isEmbeddedSubproject = true; 446 _projectFile.replaceSetting(s.clone); 447 } 448 449 @property ProjectSettings settings() { 450 if (!_settingsFile) { 451 _settingsFile = new ProjectSettings(settingsFileName); 452 _settingsFile.updateDefaults(); 453 _settingsFile.load(); 454 _settingsFile.save(); 455 } 456 return _settingsFile; 457 } 458 459 @property string settingsFileName() { 460 return buildNormalizedPath(dir, toUTF8(name) ~ ".settings"); 461 } 462 463 @property bool isDependency() { return _isDependency; } 464 @property string dependencyVersion() { return _dependencyVersion; } 465 466 /// returns project configurations 467 @property const(ProjectConfiguration[]) configurations() const 468 { 469 return _configurations; 470 } 471 472 /// returns project configurations 473 @property dstring[] configurationNames() const 474 { 475 dstring[] res; 476 res.assumeSafeAppend; 477 foreach(conf; _configurations) 478 res ~= conf.name.toUTF32; 479 return res; 480 } 481 482 /// direct access to project file (json) 483 @property SettingsFile content() { return _projectFile; } 484 485 /// name 486 override @property dstring name() { 487 return super.name(); 488 } 489 490 /// name 491 override @property void name(dstring s) { 492 super.name(s); 493 _projectFile.setString("name", toUTF8(s)); 494 } 495 496 /// name 497 override @property dstring description() { 498 return super.description(); 499 } 500 501 /// name 502 override @property void description(dstring s) { 503 super.description(s); 504 _projectFile.setString("description", toUTF8(s)); 505 } 506 507 /// returns project's own source paths 508 @property string[] sourcePaths() { return _sourcePaths; } 509 /// returns project's current toolchain import paths 510 string[] builderSourcePaths(IDESettings ideSettings) { 511 string compilerName = settings.getToolchain(ideSettings); 512 if (!compilerName) 513 compilerName = "default"; 514 return compilerImportPathsCache.getImportPathsFor(compilerName); 515 } 516 517 /// returns first source folder for project or null if not found 518 ProjectFolder firstSourceFolder() { 519 for(int i = 0; i < _items.childCount; i++) { 520 if (_items.child(i).isFolder) 521 return cast(ProjectFolder)_items.child(i); 522 } 523 return null; 524 } 525 526 ProjectSourceFile findSourceFile(string projectFileName, string fullFileName) { 527 return _items ? _items.findSourceFile(projectFileName, fullFileName) : null; 528 } 529 530 private static void addUnique(ref string[] dst, string[] items) { 531 foreach(item; items) { 532 if (!canFind(dst, item)) 533 dst ~= item; 534 } 535 } 536 @property string[] importPaths(IDESettings ideSettings) { 537 string[] res; 538 addUnique(res, sourcePaths); 539 addUnique(res, builderSourcePaths(ideSettings)); 540 foreach(dep; _dependencies) { 541 addUnique(res, dep.sourcePaths); 542 } 543 return res; 544 } 545 546 string relativeToAbsolutePath(string path) { 547 if (isAbsolute(path)) 548 return path; 549 return buildNormalizedPath(_dir, path); 550 } 551 552 string absoluteToRelativePath(string path) { 553 if (!isAbsolute(path)) 554 return path; 555 return relativePath(path, _dir); 556 } 557 558 @property ProjectSourceFile mainSourceFile() { return _mainSourceFile; } 559 @property ProjectFolder items() { return _items; } 560 561 @property Workspace workspace() { return _workspace; } 562 563 @property void workspace(Workspace p) { _workspace = p; } 564 565 @property string defWorkspaceFile() { 566 return buildNormalizedPath(_filename.dirName, toUTF8(name) ~ WORKSPACE_EXTENSION); 567 } 568 569 @property bool isExecutable() { 570 // TODO: use targetType 571 return true; 572 } 573 574 /// return executable file name, or null if it's library project or executable is not found 575 @property string executableFileName() { 576 if (!isExecutable) 577 return null; 578 string exename = toUTF8(name); 579 exename = _projectFile.getString("targetName", exename); 580 // TODO: use targetName 581 version (Windows) { 582 exename = exename ~ ".exe"; 583 } 584 string targetPath = _projectFile.getString("targetPath", null); 585 string exePath; 586 if (targetPath.length) 587 exePath = buildNormalizedPath(_filename.dirName, targetPath, exename); // int $targetPath directory 588 else 589 exePath = buildNormalizedPath(_filename.dirName, exename); // in project directory 590 return exePath; 591 } 592 593 /// working directory for running and debugging project 594 @property string workingDirectory() { 595 // TODO: get from settings 596 return _filename.dirName; 597 } 598 599 /// commandline parameters for running and debugging project 600 @property string runArgs() { 601 // TODO: get from settings 602 return null; 603 } 604 605 @property bool runInExternalConsole() { 606 // TODO 607 return settings.runInExternalConsole; 608 } 609 610 ProjectFolder findItems(string[] srcPaths) { 611 auto folder = new ProjectFolder(_filename); 612 folder.project = this; 613 foreach(customPath; srcPaths) { 614 string path = relativeToAbsolutePath(customPath); 615 if (folder.loadDir(path)) 616 _sourcePaths ~= path; 617 } 618 return folder; 619 } 620 621 void refresh() { 622 for (int i = _items._children.count - 1; i >= 0; i--) { 623 if (_items._children[i].isFolder) 624 _items._children[i].refresh(); 625 } 626 } 627 628 void findMainSourceFile() { 629 string n = toUTF8(name); 630 string[] mainnames = ["app.d", "main.d", n ~ ".d"]; 631 foreach(sname; mainnames) { 632 _mainSourceFile = findSourceFileItem(buildNormalizedPath(_dir, "src", sname)); 633 if (_mainSourceFile) 634 break; 635 _mainSourceFile = findSourceFileItem(buildNormalizedPath(_dir, "source", sname)); 636 if (_mainSourceFile) 637 break; 638 } 639 } 640 641 /// tries to find source file in project, returns found project source file item, or null if not found 642 ProjectSourceFile findSourceFileItem(ProjectItem dir, string filename, bool fullFileName=true) { 643 foreach(i; 0 .. dir.childCount) { 644 ProjectItem item = dir.child(i); 645 if (item.isFolder) { 646 ProjectSourceFile res = findSourceFileItem(item, filename, fullFileName); 647 if (res) 648 return res; 649 } else { 650 auto res = cast(ProjectSourceFile)item; 651 if(res) 652 { 653 if(fullFileName && res.filename.equal(filename)) 654 return res; 655 else if (!fullFileName && res.filename.endsWith(filename)) 656 return res; 657 } 658 } 659 } 660 return null; 661 } 662 663 ProjectSourceFile findSourceFileItem(string filename, bool fullFileName=true) { 664 return findSourceFileItem(_items, filename, fullFileName); 665 } 666 667 protected Project[] _subPackages; 668 669 /// add item to string array ignoring duplicates 670 protected static void addUnique(ref string[] list, string item) { 671 foreach(s; list) 672 if (s == item) 673 return; 674 list ~= item; 675 } 676 677 /// add item to string array ignoring duplicates 678 protected void addRelativePathIfExists(ref string[] list, string item) { 679 item = relativeToAbsolutePath(item); 680 if (item.exists && item.isDir) 681 addUnique(list, item); 682 } 683 684 /// find source paths for project 685 protected string[] findSourcePaths() { 686 string[] res; 687 res.assumeSafeAppend; 688 string[] srcPaths = _projectFile.getStringArray("sourcePaths"); 689 foreach(s; srcPaths) 690 addRelativePathIfExists(res, s); 691 Setting configs = _projectFile.objectByPath("configurations"); 692 if (configs) { 693 for (int i = 0; i < configs.length; i++) { 694 Setting s = configs[i]; 695 if (s) { 696 string[] paths = s.getStringArray("sourcePaths"); 697 foreach(path; paths) 698 addRelativePathIfExists(res, path); 699 } 700 } 701 } 702 if (!res.length) { 703 addRelativePathIfExists(res, "src"); 704 addRelativePathIfExists(res, "source"); 705 } 706 707 return res; 708 } 709 710 void processSubpackages() { 711 import dlangui.core.files; 712 _subPackages.length = 0; 713 Setting subPackages = _projectFile.settingByPath("subPackages", SettingType.ARRAY, false); 714 if (subPackages) { 715 string p = _projectFile.filename.dirName; 716 for(int i = 0; i < subPackages.length; i++) { 717 Setting sp = subPackages[i]; 718 if (sp.isString) { 719 // string 720 string path = convertPathDelimiters(sp.str); 721 string relative = relativePath(path, p); 722 path = buildNormalizedPath(absolutePath(relative, p)); 723 //Log.d("Subproject path: ", path); 724 string fn = DubPackageFinder.findPackageFile(path); 725 //Log.d("Subproject file: ", fn); 726 Project prj = new Project(_workspace, fn); 727 prj.setBaseProject(this); 728 if (prj.load()) { 729 Log.d("Loaded subpackage from file: ", fn); 730 _subPackages ~= prj; 731 if (_workspace) 732 _workspace.addDependencyProject(prj); 733 } else { 734 Log.w("Failed to load subpackage from file: ", fn); 735 } 736 } else if (sp.isObject) { 737 // object - file inside base project dub.json 738 Log.d("Subpackage is JSON object"); 739 string subname = sp.getString("name"); 740 if (subname) { 741 Project prj = new Project(_workspace, _filename ~ "@" ~ subname); 742 prj.setBaseProject(this); 743 prj.setSubprojectJson(sp); 744 bool res = prj.processLoadedProject(); 745 if (res) { 746 Log.d("Added embedded subpackage ", subname); 747 _subPackages ~= prj; 748 if (_workspace) 749 _workspace.addDependencyProject(prj); 750 } else { 751 Log.w("Error while processing embedded subpackage"); 752 } 753 } 754 } 755 } 756 } 757 } 758 759 /// parse data from _projectFile after loading 760 bool processLoadedProject() { 761 // 762 _mainSourceFile = null; 763 try { 764 _name = toUTF32(_projectFile.getString("name")); 765 _originalName = _name; 766 if (_baseProjectName) { 767 _name = _baseProjectName ~ ":" ~ _name; 768 } 769 if (_isDependency) { 770 _name ~= "-"d; 771 _name ~= toUTF32(_dependencyVersion.startsWith("~") ? _dependencyVersion[1..$] : _dependencyVersion); 772 } 773 _description = toUTF32(_projectFile.getString("description")); 774 Log.d(" project name: ", _name); 775 Log.d(" project description: ", _description); 776 777 processSubpackages(); 778 779 string[] srcPaths = findSourcePaths(); 780 _sourcePaths = null; 781 _items = findItems(srcPaths); 782 findMainSourceFile(); 783 784 Log.i("Project source paths: ", sourcePaths); 785 //Log.i("Builder source paths: ", builderSourcePaths(_settings)); 786 if (!_isDependency) 787 loadSelections(); 788 789 _configurations = ProjectConfiguration.load(_projectFile); 790 Log.i("Project configurations: ", _configurations); 791 792 793 } catch (Exception e) { 794 Log.e("Cannot read project file", e); 795 return false; 796 } 797 _items.loadFile(filename); 798 return true; 799 } 800 801 override bool load(string fname = null) { 802 if (!_projectFile) 803 _projectFile = new SettingsFile(); 804 if (fname.length > 0) 805 filename = fname; 806 if (!_projectFile.load(_filename)) { 807 Log.e("failed to load project from file ", _filename); 808 return false; 809 } 810 Log.d("Reading project from file ", _filename); 811 return processLoadedProject(); 812 813 } 814 815 override bool save(string fname = null) { 816 if (_isEmbeddedSubproject) 817 return false; 818 if (fname !is null) 819 filename = fname; 820 assert(filename !is null); 821 return _projectFile.save(filename, true); 822 } 823 824 protected Project[] _dependencies; 825 @property Project[] dependencies() { return _dependencies; } 826 827 Project findDependencyProject(string filename) { 828 foreach(dep; _dependencies) { 829 if (dep.filename.equal(filename)) 830 return dep; 831 } 832 return null; 833 } 834 835 bool loadSelections() { 836 Project[] newdeps; 837 _dependencies.length = 0; 838 auto finder = new DubPackageFinder; 839 scope(exit) destroy(finder); 840 SettingsFile selectionsFile = new SettingsFile(buildNormalizedPath(_dir, "dub.selections.json")); 841 if (!selectionsFile.load()) { 842 _dependencies = newdeps; 843 return false; 844 } 845 Setting versions = selectionsFile.objectByPath("versions"); 846 if (!versions.isObject) { 847 _dependencies = newdeps; 848 return false; 849 } 850 string[string] versionMap = versions.strMap; 851 foreach(packageName, packageVersion; versionMap) { 852 string fn = finder.findPackage(packageName, packageVersion); 853 Log.d("dependency ", packageName, " ", packageVersion, " : ", fn ? fn : "NOT FOUND"); 854 if (fn) { 855 Project p = findDependencyProject(fn); 856 if (p) { 857 Log.d("Found existing dependency project ", fn); 858 newdeps ~= p; 859 continue; 860 } 861 p = new Project(_workspace, fn, packageVersion); 862 if (p.load()) { 863 newdeps ~= p; 864 if (_workspace) 865 _workspace.addDependencyProject(p); 866 } else { 867 Log.e("cannot load dependency package ", packageName, " ", packageVersion, " from file ", fn); 868 destroy(p); 869 } 870 } 871 } 872 _dependencies = newdeps; 873 return true; 874 } 875 } 876 877 class DubPackageFinder { 878 string systemDubPath; 879 string userDubPath; 880 string tempPath; 881 this() { 882 version(Windows){ 883 systemDubPath = buildNormalizedPath(environment.get("ProgramData"), "dub", "packages"); 884 userDubPath = buildNormalizedPath(environment.get("APPDATA"), "dub", "packages"); 885 tempPath = buildNormalizedPath(environment.get("TEMP"), "dub", "packages"); 886 } else version(Posix){ 887 systemDubPath = "/var/lib/dub/packages"; 888 userDubPath = buildNormalizedPath(environment.get("HOME"), ".dub", "packages"); 889 if(!userDubPath.isAbsolute) 890 userDubPath = buildNormalizedPath(getcwd(), userDubPath); 891 tempPath = "/tmp/packages"; 892 } 893 } 894 895 /// find package file (dub.json, package.json) in specified dir; returns absoulute path to found file or null if not found 896 static string findPackageFile(string pathName) { 897 string fn = buildNormalizedPath(pathName, "dub.json"); 898 if (fn.exists && fn.isFile) 899 return fn; 900 fn = buildNormalizedPath(pathName, "dub.sdl"); 901 if (fn.exists && fn.isFile) 902 return fn; 903 fn = buildNormalizedPath(pathName, "package.json"); 904 if (fn.exists && fn.isFile) 905 return fn; 906 return null; 907 } 908 909 protected string findPackage(string packageDir, string packageName, string packageVersion) { 910 string fullName = packageVersion.startsWith("~") ? packageName ~ "-" ~ packageVersion[1..$] : packageName ~ "-" ~ packageVersion; 911 string pathName = absolutePath(buildNormalizedPath(packageDir, fullName)); 912 if (pathName.exists && pathName.isDir) { 913 string fn = findPackageFile(pathName); 914 if (fn) 915 return fn; 916 // new DUB support - with package subdirectory 917 fn = findPackageFile(buildNormalizedPath(pathName, packageName)); 918 if (fn) 919 return fn; 920 } 921 return null; 922 } 923 924 string findPackage(string packageName, string packageVersion) { 925 string res = null; 926 res = findPackage(userDubPath, packageName, packageVersion); 927 if (res) 928 return res; 929 res = findPackage(systemDubPath, packageName, packageVersion); 930 return res; 931 } 932 } 933 934 bool isValidProjectName(in string s) pure { 935 if (s.empty) 936 return false; 937 return reduce!q{ a && (b == '_' || b == '-' || std.ascii.isAlphaNum(b)) }(true, s); 938 } 939 940 bool isValidModuleName(in string s) pure { 941 if (s.empty) 942 return false; 943 return reduce!q{ a && (b == '_' || std.ascii.isAlphaNum(b)) }(true, s); 944 } 945 946 bool isValidFileName(in string s) pure { 947 if (s.empty) 948 return false; 949 return reduce!q{ a && (b == '_' || b == '.' || b == '-' || std.ascii.isAlphaNum(b)) }(true, s); 950 } 951 952 unittest { 953 assert(!isValidProjectName("")); 954 assert(isValidProjectName("project")); 955 assert(isValidProjectName("cool_project")); 956 assert(isValidProjectName("project-2")); 957 assert(!isValidProjectName("project.png")); 958 assert(!isValidProjectName("[project]")); 959 assert(!isValidProjectName("<project/>")); 960 assert(!isValidModuleName("")); 961 assert(isValidModuleName("module")); 962 assert(isValidModuleName("awesome_module2")); 963 assert(!isValidModuleName("module-2")); 964 assert(!isValidModuleName("module.png")); 965 assert(!isValidModuleName("[module]")); 966 assert(!isValidModuleName("<module>")); 967 assert(!isValidFileName("")); 968 assert(isValidFileName("file")); 969 assert(isValidFileName("file_2")); 970 assert(isValidFileName("file-2")); 971 assert(isValidFileName("file.txt")); 972 assert(!isValidFileName("[file]")); 973 assert(!isValidFileName("<file>")); 974 } 975 976 class EditorBookmark { 977 string file; 978 string fullFilePath; 979 string projectFilePath; 980 int line; 981 string projectName; 982 } 983 984 985 string[] splitByLines(string s) { 986 string[] res; 987 int start = 0; 988 for(int i = 0; i <= s.length; i++) { 989 if (i == s.length) { 990 if (start < i) 991 res ~= s[start .. i]; 992 break; 993 } 994 if (s[i] == '\r' || s[i] == '\n') { 995 if (start < i) 996 res ~= s[start .. i]; 997 start = i + 1; 998 } 999 } 1000 return res; 1001 } 1002 1003 struct CompilerImportPathsCache { 1004 1005 private static class Entry { 1006 string[] list; 1007 } 1008 private Entry[string] _cache; 1009 1010 string[] getImportPathsFor(string compiler) { 1011 import dlangui.core.files : findExecutablePath; 1012 if (!compiler.length) 1013 return []; 1014 if (auto p = compiler in _cache) { 1015 // found in cache 1016 return p.list; 1017 } 1018 Log.d("Searching for compiler path: ", compiler); 1019 import std.path : isAbsolute; 1020 string compilerPath = compiler; 1021 if (compiler == "default") { 1022 // try to autodetect default compiler 1023 compilerPath = findExecutablePath("dmd"); 1024 if (!compilerPath) 1025 compilerPath = findExecutablePath("ldc"); 1026 if (!compilerPath) 1027 compilerPath = findExecutablePath("gdc"); 1028 } else if (compilerPath && !compilerPath.isAbsolute) 1029 compilerPath = findExecutablePath(compiler); 1030 string[] res; 1031 if (compilerPath) 1032 res = detectImportPathsForCompiler(compilerPath); 1033 else 1034 Log.w("Compiler executable not found for `", compiler, "`"); 1035 Entry newItem = new Entry(); 1036 newItem.list = res; 1037 _cache[compiler] = newItem; 1038 return res; 1039 } 1040 } 1041 1042 __gshared CompilerImportPathsCache compilerImportPathsCache; 1043 1044 string[] detectImportPathsForCompiler(string compiler) { 1045 string[] res; 1046 import std.process : pipeProcess, Redirect, wait; 1047 import std..string : startsWith, indexOf; 1048 import std.path : buildNormalizedPath; 1049 import std.file : write, remove; 1050 import dlangui.core.files; 1051 try { 1052 string sourcefilename = appDataPath(".dlangide") ~ PATH_DELIMITER ~ "tmp_dummy_file_to_get_import_paths.d"; 1053 write(sourcefilename, "import module_that_does_not_exist;\n"); 1054 auto pipes = pipeProcess([compiler, sourcefilename], Redirect.stdin | Redirect.stdout | Redirect.stderrToStdout, null, Config.suppressConsole); 1055 char[4096] buffer; 1056 char[] s = pipes.stdout.rawRead(buffer); 1057 wait(pipes.pid); 1058 //auto ls = execute([compiler, sourcefilename]); 1059 remove(sourcefilename); 1060 //string s = ls.output; 1061 string[] lines = splitByLines(cast(string)s); 1062 debug Log.d("compiler output:\n", s); 1063 foreach(line; lines) { 1064 if (line.startsWith("import path[")) { 1065 auto p = line.indexOf("] = "); 1066 if (p > 0) { 1067 line = line[p + 4 .. $]; 1068 string path = line.buildNormalizedPath; 1069 debug Log.d("import path found: `", path, "`"); 1070 res ~= path; 1071 } 1072 } 1073 } 1074 return res; 1075 } catch (Exception e) { 1076 return null; 1077 } 1078 }