1 /** 2 Generator for VisualD project files 3 4 Copyright: © 2012-2013 Matthias Dondorff 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Matthias Dondorff 7 */ 8 module dub.generators.visuald; 9 10 import dub.compilers.compiler; 11 import dub.generators.generator; 12 import dub.internal.utils; 13 import dub.internal.vibecompat.core.file; 14 import dub.internal.vibecompat.core.log; 15 import dub.package_; 16 import dub.packagemanager; 17 import dub.project; 18 19 import std.algorithm; 20 import std.array; 21 import std.conv; 22 import std.format; 23 import std.uuid; 24 import std.exception; 25 26 27 // Dubbing is developing dub... 28 //version = DUBBING; 29 30 // TODO: handle pre/post build commands 31 32 33 class VisualDGenerator : ProjectGenerator { 34 private { 35 Project m_app; 36 PackageManager m_pkgMgr; 37 string[string] m_projectUuids; 38 bool m_combinedProject; 39 } 40 41 this(Project app, PackageManager mgr, bool combined_project) 42 { 43 m_combinedProject = combined_project; 44 m_app = app; 45 m_pkgMgr = mgr; 46 } 47 48 void generateProject(GeneratorSettings settings) 49 { 50 auto buildsettings = settings.buildSettings; 51 m_app.addBuildSettings(buildsettings, settings.platform, settings.config); 52 53 prepareGeneration(buildsettings); 54 55 logDebug("About to generate projects for %s, with %s direct dependencies.", m_app.mainPackage().name, m_app.mainPackage().dependencies().length); 56 generateProjects(m_app.mainPackage(), settings); 57 generateSolution(settings); 58 logInfo("VisualD project generated."); 59 60 finalizeGeneration(buildsettings, true); 61 } 62 63 private { 64 void generateSolution(GeneratorSettings settings) 65 { 66 auto ret = appender!(char[])(); 67 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 68 69 // Solution header 70 ret.formattedWrite(" 71 Microsoft Visual Studio Solution File, Format Version 11.00 72 # Visual Studio 2010"); 73 74 generateSolutionEntry(ret, m_app.mainPackage, settings); 75 if (!m_combinedProject) { 76 performOnDependencies(m_app.mainPackage, configs, (pack){ 77 generateSolutionEntry(ret, pack, settings); 78 }); 79 } 80 81 // Global section contains configurations 82 ret.formattedWrite(" 83 Global 84 GlobalSection(SolutionConfigurationPlatforms) = preSolution 85 Debug|Win32 = Debug|Win32 86 Release|Win32 = Release|Win32 87 Unittest|Win32 = Unittest|Win32 88 EndGlobalSection 89 GlobalSection(ProjectConfigurationPlatforms) = postSolution"); 90 91 generateSolutionConfig(ret, m_app.mainPackage()); 92 93 // TODO: for all dependencies 94 95 ret.formattedWrite(" 96 GlobalSection(SolutionProperties) = preSolution 97 HideSolutionNode = FALSE 98 EndGlobalSection 99 EndGlobal"); 100 101 // Writing solution file 102 logDebug("About to write to .sln file with %s bytes", to!string(ret.data().length)); 103 auto sln = openFile(solutionFileName(), FileMode.CreateTrunc); 104 scope(exit) sln.close(); 105 sln.put(ret.data()); 106 sln.flush(); 107 } 108 109 void generateSolutionEntry(Appender!(char[]) ret, const Package pack, GeneratorSettings settings) 110 { 111 auto projUuid = generateUUID(); 112 auto projName = pack.name; 113 auto projPath = projFileName(pack); 114 auto projectUuid = guid(projName); 115 116 // Write project header, like so 117 // Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "derelict", "..\inbase\source\derelict.visualdproj", "{905EF5DA-649E-45F9-9C15-6630AA815ACB}" 118 ret.formattedWrite("\nProject(\"%s\") = \"%s\", \"%s\", \"%s\"", 119 projUuid, projName, projPath, projectUuid); 120 121 if (!m_combinedProject) { 122 void addDepsRec(in Package p) 123 { 124 foreach(id, dependency; p.dependencies) { 125 auto deppack = m_app.getDependency(id, true); 126 if (!deppack) continue; 127 if (isHeaderOnlyPackage(deppack, settings)) { 128 addDepsRec(deppack); 129 } else if (!m_app.isRedundantDependency(p, deppack)) { 130 // TODO: clarify what "uuid = uuid" should mean 131 auto uuid = guid(id); 132 ret.formattedWrite("\n %s = %s", uuid, uuid); 133 } 134 } 135 } 136 137 if(pack.dependencies.length > 0) { 138 ret.formattedWrite(" 139 ProjectSection(ProjectDependencies) = postProject"); 140 addDepsRec(pack); 141 ret.formattedWrite(" 142 EndProjectSection"); 143 } 144 } 145 146 ret.formattedWrite("\nEndProject"); 147 } 148 149 void generateSolutionConfig(Appender!(char[]) ret, const Package pack) { 150 const string[] sub = [ "ActiveCfg", "Build.0" ]; 151 const string[] conf = [ "Debug|Win32", "Release|Win32" /*, "Unittest|Win32" */]; 152 auto projectUuid = guid(pack.name()); 153 foreach(c; conf) 154 foreach(s; sub) 155 formattedWrite(ret, "\n\t\t%s.%s.%s = %s", to!string(projectUuid), c, s, c); 156 } 157 158 void generateProjects(const Package main, GeneratorSettings settings) { 159 160 // TODO: cyclic check 161 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 162 163 generateProj(main, settings); 164 165 if (!m_combinedProject) { 166 bool[string] generatedProjects; 167 generatedProjects[main.name] = true; 168 performOnDependencies(main, configs, (const Package dependency) { 169 if(dependency.name in generatedProjects) 170 return; 171 generateProj(dependency, settings); 172 } ); 173 } 174 } 175 176 bool isHeaderOnlyPackage(in Package pack, in GeneratorSettings settings) 177 const { 178 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 179 auto pbuildsettings = pack.getBuildSettings(settings.platform, configs[pack.name]); 180 if (!pbuildsettings.sourceFiles.any!(f => f.endsWith(".d"))()) 181 return true; 182 return false; 183 } 184 185 void generateProj(const Package pack, GeneratorSettings settings) 186 { 187 int i = 0; 188 auto ret = appender!(char[])(); 189 190 auto projName = pack.name; 191 auto project_file_dir = m_app.mainPackage.path ~ projFileName(pack).parentPath; 192 ret.put("<DProject>\n"); 193 ret.formattedWrite(" <ProjectGuid>%s</ProjectGuid>\n", guid(projName)); 194 195 // Several configurations (debug, release, unittest) 196 generateProjectConfiguration(ret, pack, "debug", settings); 197 generateProjectConfiguration(ret, pack, "release", settings); 198 generateProjectConfiguration(ret, pack, "unittest", settings); 199 200 // Add all files 201 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 202 auto files = pack.getBuildSettings(settings.platform, configs[pack.name]); 203 bool[SourceFile] sourceFiles; 204 void addSourceFile(Path file_path, Path structure_path, bool build) 205 { 206 SourceFile sf; 207 sf.filePath = file_path; 208 sf.structurePath = structure_path; 209 if (build) { 210 sf.build = false; 211 if (sf in sourceFiles) sourceFiles.remove(sf); 212 } else { 213 sf.build = true; 214 if (sf in sourceFiles) return; 215 } 216 sf.build = build; 217 sourceFiles[sf] = true; 218 } 219 if (m_combinedProject) { 220 bool[const(Package)] basePackagesAdded; 221 222 // add all package.json files to the project 223 // and all source files 224 performOnDependencies(pack, configs, (prj) { 225 void addFile(string s, bool build) { 226 auto sp = Path(s); 227 if( !sp.absolute ) sp = prj.path ~ sp; 228 // regroup in Folder by base package 229 addSourceFile(sp.relativeTo(project_file_dir), Path(prj.basePackage().name) ~ sp.relativeTo(prj.path), build); 230 } 231 232 string[] prjFiles; 233 234 // Avoid multiples package.json when using sub-packages. 235 // Only add the package info file if no other package/sub-package from the same base package 236 // has been seen yet. 237 { 238 const(Package) base = prj.basePackage(); 239 240 if (base !in basePackagesAdded) { 241 prjFiles ~= prj.packageInfoFile.toNativeString(); 242 basePackagesAdded[base] = true; 243 } 244 } 245 246 auto settings = prj.getBuildSettings(settings.platform, configs[prj.name]); 247 foreach (f; prjFiles) addFile(f, false); 248 foreach (f; settings.sourceFiles) addFile(f, true); 249 foreach (f; settings.importFiles) addFile(f, false); 250 foreach (f; settings.stringImportFiles) addFile(f, false); 251 }); 252 } 253 254 void addFile(string s, bool build) { 255 auto sp = Path(s); 256 if( !sp.absolute ) sp = pack.path ~ sp; 257 addSourceFile(sp.relativeTo(project_file_dir), sp.relativeTo(pack.path), build); 258 } 259 addFile(pack.packageInfoFile.toNativeString(), false); 260 foreach(s; files.sourceFiles) addFile(s, true); 261 foreach(s; files.importFiles) addFile(s, false); 262 foreach(s; files.stringImportFiles) addFile(s, false); 263 264 // Create folders and files 265 ret.formattedWrite(" <Folder name=\"%s\">", getPackageFileName(pack)); 266 Path lastFolder; 267 foreach(source; sortedSources(sourceFiles.keys)) { 268 logDebug("source looking at %s", source.structurePath); 269 auto cur = source.structurePath[0 .. source.structurePath.length-1]; 270 if(lastFolder != cur) { 271 size_t same = 0; 272 foreach(idx; 0..min(lastFolder.length, cur.length)) 273 if(lastFolder[idx] != cur[idx]) break; 274 else same = idx+1; 275 276 const decrease = lastFolder.length - min(lastFolder.length, same); 277 const increase = cur.length - min(cur.length, same); 278 279 foreach(unused; 0..decrease) 280 ret.put("\n </Folder>"); 281 foreach(idx; 0..increase) 282 ret.formattedWrite("\n <Folder name=\"%s\">", cur[same + idx].toString()); 283 lastFolder = cur; 284 } 285 ret.formattedWrite("\n <File %spath=\"%s\" />", source.build ? "" : "tool=\"None\" ", source.filePath.toNativeString()); 286 } 287 // Finalize all open folders 288 foreach(unused; 0..lastFolder.length) 289 ret.put("\n </Folder>"); 290 ret.put("\n </Folder>\n</DProject>"); 291 292 logDebug("About to write to '%s.visualdproj' file %s bytes", getPackageFileName(pack), ret.data().length); 293 auto proj = openFile(projFileName(pack), FileMode.CreateTrunc); 294 scope(exit) proj.close(); 295 proj.put(ret.data()); 296 proj.flush(); 297 } 298 299 void generateProjectConfiguration(Appender!(char[]) ret, const Package pack, string type, GeneratorSettings settings) 300 { 301 auto project_file_dir = m_app.mainPackage.path ~ projFileName(pack).parentPath; 302 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 303 auto buildsettings = settings.buildSettings; 304 auto pbuildsettings = pack.getBuildSettings(settings.platform, configs[pack.name]); 305 m_app.addBuildSettings(buildsettings, settings.platform, settings.config, pack); 306 m_app.addBuildTypeSettings(buildsettings, settings.platform, type); 307 settings.compiler.extractBuildOptions(buildsettings); 308 309 string[] getSettings(string setting)(){ return __traits(getMember, buildsettings, setting); } 310 string[] getPathSettings(string setting)() 311 { 312 auto settings = getSettings!setting(); 313 auto ret = new string[settings.length]; 314 foreach (i; 0 .. settings.length) ret[i] = '"' ~ (Path(settings[i]).relativeTo(project_file_dir)).toNativeString() ~ '"'; 315 return ret; 316 } 317 318 foreach(architecture; settings.platform.architecture) { 319 string arch; 320 switch(architecture) { 321 default: logWarn("Unsupported platform('%s'), defaulting to x86", architecture); goto case; 322 case "x86": arch = "Win32"; break; 323 case "x86_64": arch = "x64"; break; 324 } 325 ret.formattedWrite(" <Config name=\"%s\" platform=\"%s\">\n", to!string(type), arch); 326 327 // FIXME: handle compiler options in an abstract way instead of searching for DMD specific flags 328 329 // debug and optimize setting 330 ret.formattedWrite(" <symdebug>%s</symdebug>\n", buildsettings.options & BuildOptions.debugInfo ? "1" : "0"); 331 ret.formattedWrite(" <optimize>%s</optimize>\n", buildsettings.options & BuildOptions.optimize ? "1" : "0"); 332 ret.formattedWrite(" <useInline>%s</useInline>\n", buildsettings.options & BuildOptions.inline ? "1" : "0"); 333 ret.formattedWrite(" <release>%s</release>\n", buildsettings.options & BuildOptions.releaseMode ? "1" : "0"); 334 335 // Lib or exe? 336 enum 337 { 338 Executable = 0, 339 StaticLib = 1, 340 DynamicLib = 2 341 } 342 343 int output_type = StaticLib; // library 344 string output_ext = "lib"; 345 if (pbuildsettings.targetType == TargetType.executable) 346 { 347 output_type = Executable; 348 output_ext = "exe"; 349 } 350 else if (pbuildsettings.targetType == TargetType.dynamicLibrary) 351 { 352 output_type = DynamicLib; 353 output_ext = "dll"; 354 } 355 string debugSuffix = type == "debug" ? "_d" : ""; 356 auto bin_path = pack is m_app.mainPackage ? Path(pbuildsettings.targetPath) : Path(".dub/lib/"); 357 bin_path.endsWithSlash = true; 358 ret.formattedWrite(" <lib>%s</lib>\n", output_type); 359 ret.formattedWrite(" <exefile>%s%s%s.%s</exefile>\n", bin_path.toNativeString(), pbuildsettings.targetName, debugSuffix, output_ext); 360 361 // include paths and string imports 362 string imports = join(getPathSettings!"importPaths"(), " "); 363 string stringImports = join(getPathSettings!"stringImportPaths"(), " "); 364 ret.formattedWrite(" <imppath>%s</imppath>\n", imports); 365 ret.formattedWrite(" <fileImppath>%s</fileImppath>\n", stringImports); 366 367 ret.formattedWrite(" <program>%s</program>\n", "$(DMDInstallDir)windows\\bin\\dmd.exe"); // FIXME: use the actually selected compiler! 368 ret.formattedWrite(" <additionalOptions>%s</additionalOptions>\n", getSettings!"dflags"().join(" ")); 369 370 // Add version identifiers 371 string versions = join(getSettings!"versions"(), " "); 372 ret.formattedWrite(" <versionids>%s</versionids>\n", versions); 373 374 // Add libraries, system libs need to be suffixed by ".lib". 375 string linkLibs = join(map!(a => a~".lib")(getSettings!"libs"()), " "); 376 string addLinkFiles = join(getSettings!"sourceFiles"().filter!(s => s.endsWith(".lib"))(), " "); 377 if (output_type != StaticLib) ret.formattedWrite(" <libfiles>%s %s phobos.lib</libfiles>\n", linkLibs, addLinkFiles); 378 379 // Unittests 380 ret.formattedWrite(" <useUnitTests>%s</useUnitTests>\n", buildsettings.options & BuildOptions.unittests ? "1" : "0"); 381 382 // compute directory for intermediate files (need dummy/ because of how -op determines the resulting path) 383 auto relpackpath = pack.path.relativeTo(project_file_dir); 384 uint ndummy = 0; 385 foreach (i; 0 .. relpackpath.length) 386 if (relpackpath[i] == "..") ndummy++; 387 string intersubdir = (ndummy*2 > relpackpath.length ? replicate("dummy/", ndummy*2-relpackpath.length) : "") ~ getPackageFileName(pack); 388 389 ret.put(" <obj>0</obj>\n"); 390 ret.put(" <link>0</link>\n"); 391 ret.put(" <subsystem>0</subsystem>\n"); 392 ret.put(" <multiobj>0</multiobj>\n"); 393 ret.put(" <singleFileCompilation>2</singleFileCompilation>\n"); 394 ret.put(" <oneobj>0</oneobj>\n"); 395 ret.put(" <trace>0</trace>\n"); 396 ret.put(" <quiet>0</quiet>\n"); 397 ret.formattedWrite(" <verbose>%s</verbose>\n", buildsettings.options & BuildOptions.verbose ? "1" : "0"); 398 ret.put(" <vtls>0</vtls>\n"); 399 ret.put(" <cpu>0</cpu>\n"); 400 ret.formattedWrite(" <isX86_64>%s</isX86_64>\n", arch == "x64" ? 1 : 0); 401 ret.put(" <isLinux>0</isLinux>\n"); 402 ret.put(" <isOSX>0</isOSX>\n"); 403 ret.put(" <isWindows>0</isWindows>\n"); 404 ret.put(" <isFreeBSD>0</isFreeBSD>\n"); 405 ret.put(" <isSolaris>0</isSolaris>\n"); 406 ret.put(" <scheduler>0</scheduler>\n"); 407 ret.put(" <useDeprecated>0</useDeprecated>\n"); 408 ret.put(" <useAssert>0</useAssert>\n"); 409 ret.put(" <useInvariants>0</useInvariants>\n"); 410 ret.put(" <useIn>0</useIn>\n"); 411 ret.put(" <useOut>0</useOut>\n"); 412 ret.put(" <useArrayBounds>0</useArrayBounds>\n"); 413 ret.formattedWrite(" <noboundscheck>%s</noboundscheck>\n", buildsettings.options & BuildOptions.noBoundsChecks ? "1" : "0"); 414 ret.put(" <useSwitchError>0</useSwitchError>\n"); 415 ret.put(" <preservePaths>1</preservePaths>\n"); 416 ret.formattedWrite(" <warnings>%s</warnings>\n", buildsettings.options & BuildOptions.warningsAsErrors ? "1" : "0"); 417 ret.formattedWrite(" <infowarnings>%s</infowarnings>\n", buildsettings.options & BuildOptions.warnings ? "1" : "0"); 418 ret.formattedWrite(" <checkProperty>%s</checkProperty>\n", buildsettings.options & BuildOptions.property ? "1" : "0"); 419 ret.formattedWrite(" <genStackFrame>%s</genStackFrame>\n", buildsettings.options & BuildOptions.alwaysStackFrame ? "1" : "0"); 420 ret.put(" <pic>0</pic>\n"); 421 ret.formattedWrite(" <cov>%s</cov>\n", buildsettings.options & BuildOptions.coverage ? "1" : "0"); 422 ret.put(" <nofloat>0</nofloat>\n"); 423 ret.put(" <Dversion>2</Dversion>\n"); 424 ret.formattedWrite(" <ignoreUnsupportedPragmas>%s</ignoreUnsupportedPragmas>\n", buildsettings.options & BuildOptions.ignoreUnknownPragmas ? "1" : "0"); 425 ret.formattedWrite(" <compiler>%s</compiler>\n", settings.compiler.name == "ldc" ? 2 : settings.compiler.name == "gdc" ? 1 : 0); 426 ret.formattedWrite(" <otherDMD>0</otherDMD>\n"); 427 ret.formattedWrite(" <outdir>%s</outdir>\n", bin_path.toNativeString()); 428 ret.formattedWrite(" <objdir>.dub/obj/%s/%s</objdir>\n", to!string(type), intersubdir); 429 ret.put(" <objname />\n"); 430 ret.put(" <libname />\n"); 431 ret.put(" <doDocComments>0</doDocComments>\n"); 432 ret.put(" <docdir />\n"); 433 ret.put(" <docname />\n"); 434 ret.put(" <modules_ddoc />\n"); 435 ret.put(" <ddocfiles />\n"); 436 ret.put(" <doHdrGeneration>0</doHdrGeneration>\n"); 437 ret.put(" <hdrdir />\n"); 438 ret.put(" <hdrname />\n"); 439 ret.put(" <doXGeneration>1</doXGeneration>\n"); 440 ret.put(" <xfilename>$(IntDir)\\$(TargetName).json</xfilename>\n"); 441 ret.put(" <debuglevel>0</debuglevel>\n"); 442 ret.put(" <versionlevel>0</versionlevel>\n"); 443 ret.put(" <debugids />\n"); 444 ret.put(" <dump_source>0</dump_source>\n"); 445 ret.put(" <mapverbosity>0</mapverbosity>\n"); 446 ret.put(" <createImplib>0</createImplib>\n"); 447 ret.put(" <defaultlibname />\n"); 448 ret.put(" <debuglibname />\n"); 449 ret.put(" <moduleDepsFile />\n"); 450 ret.put(" <run>0</run>\n"); 451 ret.put(" <runargs />\n"); 452 ret.put(" <runCv2pdb>1</runCv2pdb>\n"); 453 ret.put(" <pathCv2pdb>$(VisualDInstallDir)cv2pdb\\cv2pdb.exe</pathCv2pdb>\n"); 454 ret.put(" <cv2pdbPre2043>0</cv2pdbPre2043>\n"); 455 ret.put(" <cv2pdbNoDemangle>0</cv2pdbNoDemangle>\n"); 456 ret.put(" <cv2pdbEnumType>0</cv2pdbEnumType>\n"); 457 ret.put(" <cv2pdbOptions />\n"); 458 ret.put(" <objfiles />\n"); 459 ret.put(" <linkswitches />\n"); 460 ret.put(" <libpaths />\n"); 461 ret.put(" <deffile />\n"); 462 ret.put(" <resfile />\n"); 463 ret.put(" <preBuildCommand />\n"); 464 ret.put(" <postBuildCommand />\n"); 465 ret.put(" <filesToClean>*.obj;*.cmd;*.build;*.dep</filesToClean>\n"); 466 ret.put(" </Config>\n"); 467 } // foreach(architecture) 468 } 469 470 void performOnDependencies(const Package main, string[string] configs, void delegate(const Package pack) op) 471 { 472 foreach (p; m_app.getTopologicalPackageList(false, main, configs)) { 473 if (p is main) continue; 474 op(p); 475 } 476 } 477 478 string generateUUID() const { 479 return "{" ~ randomUUID().toString() ~ "}"; 480 } 481 482 string guid(string projectName) { 483 if(projectName !in m_projectUuids) 484 m_projectUuids[projectName] = generateUUID(); 485 return m_projectUuids[projectName]; 486 } 487 488 auto solutionFileName() const { 489 version(DUBBING) return getPackageFileName(m_app.mainPackage()) ~ ".dubbed.sln"; 490 else return getPackageFileName(m_app.mainPackage()) ~ ".sln"; 491 } 492 493 Path projFileName(ref const Package pack) const { 494 auto basepath = Path(".");//Path(".dub/"); 495 version(DUBBING) return basepath ~ (getPackageFileName(pack) ~ ".dubbed.visualdproj"); 496 else return basepath ~ (getPackageFileName(pack) ~ ".visualdproj"); 497 } 498 } 499 500 // TODO: nice folders 501 struct SourceFile { 502 Path structurePath; 503 Path filePath; 504 bool build = true; 505 506 hash_t toHash() const nothrow @trusted { return structurePath.toHash() ^ filePath.toHash() ^ (build * 0x1f3e7b2c); } 507 int opCmp(ref const SourceFile rhs) const { return sortOrder(this, rhs); } 508 // "a < b" for folder structures (deepest folder first, else lexical) 509 private final static int sortOrder(ref const SourceFile a, ref const SourceFile b) { 510 enforce(!a.structurePath.empty()); 511 enforce(!b.structurePath.empty()); 512 auto as = a.structurePath; 513 auto bs = b.structurePath; 514 515 // Check for different folders, compare folders only (omit last one). 516 for(uint idx=0; idx<min(as.length-1, bs.length-1); ++idx) 517 if(as[idx] != bs[idx]) 518 return as[idx].opCmp(bs[idx]); 519 520 if(as.length != bs.length) { 521 // If length differ, the longer one is "smaller", that is more 522 // specialized and will be put out first. 523 return as.length > bs.length? -1 : 1; 524 } 525 else { 526 // Both paths indicate files in the same directory, use lexical 527 // ordering for those. 528 return as.head.opCmp(bs.head); 529 } 530 } 531 } 532 533 auto sortedSources(SourceFile[] sources) { 534 return sort(sources); 535 } 536 537 unittest { 538 SourceFile[] sfs = [ 539 { Path("b/file.d"), Path("") }, 540 { Path("b/b/fileA.d"), Path("") }, 541 { Path("a/file.d"), Path("") }, 542 { Path("b/b/fileB.d"), Path("") }, 543 { Path("b/b/b/fileA.d"), Path("") }, 544 { Path("b/c/fileA.d"), Path("") }, 545 ]; 546 auto sorted = sort(sfs); 547 SourceFile[] sortedSfs; 548 foreach(sr; sorted) 549 sortedSfs ~= sr; 550 assert(sortedSfs[0].structurePath == Path("a/file.d"), "1"); 551 assert(sortedSfs[1].structurePath == Path("b/b/b/fileA.d"), "2"); 552 assert(sortedSfs[2].structurePath == Path("b/b/fileA.d"), "3"); 553 assert(sortedSfs[3].structurePath == Path("b/b/fileB.d"), "4"); 554 assert(sortedSfs[4].structurePath == Path("b/c/fileA.d"), "5"); 555 assert(sortedSfs[5].structurePath == Path("b/file.d"), "6"); 556 } 557 } 558 559 private string getPackageFileName(in Package pack) 560 { 561 return pack.name.replace(":", "_"); 562 }