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.logging; 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.exception; 23 import std.format; 24 import std.string : format; 25 import std.uuid; 26 27 28 // Dubbing is developing dub... 29 //version = DUBBING; 30 31 // TODO: handle pre/post build commands 32 33 34 class VisualDGenerator : ProjectGenerator { 35 private { 36 string[string] m_projectUuids; 37 } 38 39 this(Project project) 40 { 41 super(project); 42 } 43 44 override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) 45 { 46 logDebug("About to generate projects for %s, with %s direct dependencies.", m_project.rootPackage.name, m_project.rootPackage.getAllDependencies().length); 47 generateProjectFiles(settings, targets); 48 generateSolutionFile(settings, targets); 49 } 50 51 private { 52 void generateSolutionFile(GeneratorSettings settings, in TargetInfo[string] targets) 53 { 54 auto ret = appender!(char[])(); 55 auto configs = m_project.getPackageConfigs(settings.platform, settings.config); 56 auto some_uuid = generateUUID(); 57 58 // Solution header 59 ret.put("Microsoft Visual Studio Solution File, Format Version 11.00\n"); 60 ret.put("# Visual Studio 2010\n"); 61 62 bool[string] visited; 63 void generateSolutionEntry(string pack) { 64 if (pack in visited) return; 65 visited[pack] = true; 66 67 auto ti = targets[pack]; 68 69 auto uuid = guid(pack); 70 ret.formattedWrite("Project(\"%s\") = \"%s\", \"%s\", \"%s\"\n", 71 some_uuid, pack, projFileName(pack), uuid); 72 73 if (ti.linkDependencies.length && ti.buildSettings.targetType != TargetType.staticLibrary) { 74 ret.put("\tProjectSection(ProjectDependencies) = postProject\n"); 75 foreach (d; ti.linkDependencies) 76 if (!isHeaderOnlyPackage(d, targets)) { 77 // TODO: clarify what "uuid = uuid" should mean 78 ret.formattedWrite("\t\t%s = %s\n", guid(d), guid(d)); 79 } 80 ret.put("\tEndProjectSection\n"); 81 } 82 83 ret.put("EndProject\n"); 84 85 foreach (d; ti.dependencies) generateSolutionEntry(d); 86 } 87 88 auto mainpack = m_project.rootPackage.name; 89 90 generateSolutionEntry(mainpack); 91 92 // Global section contains configurations 93 ret.put("Global\n"); 94 ret.put("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n"); 95 ret.formattedWrite("\t\t%s|%s = %s|%s\n", 96 settings.buildType, 97 settings.platform.architecture[0].vsArchitecture, 98 settings.buildType, 99 settings.platform.architecture[0].vsArchitecture); 100 ret.put("\tEndGlobalSection\n"); 101 ret.put("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n"); 102 103 const string[] sub = ["ActiveCfg", "Build.0"]; 104 const string[] conf = [settings.buildType~"|"~settings.platform.architecture[0].vsArchitecture]; 105 foreach (t; targets.byKey) 106 foreach (c; conf) 107 foreach (s; sub) 108 formattedWrite(ret, "\t\t%s.%s.%s = %s\n", guid(t), c, s, c); 109 110 // TODO: for all dependencies 111 ret.put("\tEndGlobalSection\n"); 112 113 ret.put("\tGlobalSection(SolutionProperties) = preSolution\n"); 114 ret.put("\t\tHideSolutionNode = FALSE\n"); 115 ret.put("\tEndGlobalSection\n"); 116 ret.put("EndGlobal\n"); 117 118 // Writing solution file 119 logDebug("About to write to .sln file with %s bytes", to!string(ret.data.length)); 120 NativePath(solutionFileName()).writeFile(ret.data); 121 logInfo("Generated", Color.green, "%s (solution)", solutionFileName()); 122 } 123 124 125 void generateProjectFiles(GeneratorSettings settings, in TargetInfo[string] targets) 126 { 127 bool[string] visited; 128 void performRec(string name) { 129 if (name in visited) return; 130 visited[name] = true; 131 generateProjectFile(name, settings, targets); 132 foreach (d; targets[name].dependencies) 133 performRec(d); 134 } 135 136 performRec(m_project.rootPackage.name); 137 } 138 139 bool isHeaderOnlyPackage(string pack, in TargetInfo[string] targets) 140 const { 141 auto buildsettings = targets[pack].buildSettings; 142 if (!buildsettings.sourceFiles.any!(f => f.endsWith(".d"))()) 143 return true; 144 return false; 145 } 146 147 void generateProjectFile(string packname, GeneratorSettings settings, in TargetInfo[string] targets) 148 { 149 import dub.compilers.utils : isLinkerFile; 150 151 auto ret = appender!(char[])(); 152 153 auto root_package_path = m_project.rootPackage.path; 154 auto basepath = NativePath(".dub/"); 155 if (!isWritableDir(basepath, true)) 156 throw new Exception(".dub is not writeable"); 157 ret.put("<DProject>\n"); 158 ret.formattedWrite(" <ProjectGuid>%s</ProjectGuid>\n", guid(packname)); 159 160 // Several configurations (debug, release, unittest) 161 generateProjectConfiguration(ret, packname, settings.buildType, settings, targets); 162 //generateProjectConfiguration(ret, packname, "release", settings, targets); 163 //generateProjectConfiguration(ret, packname, "unittest", settings, targets); 164 165 // Add all files 166 auto files = targets[packname].buildSettings; 167 SourceFile[string] sourceFiles; 168 void addSourceFile(NativePath file_path, NativePath structure_path, SourceFile.Action action) 169 { 170 auto key = file_path.toString(); 171 auto sf = sourceFiles.get(key, SourceFile.init); 172 sf.filePath = file_path; 173 if (sf.action == SourceFile.Action.none) { 174 sf.action = action; 175 sf.structurePath = structure_path; 176 } 177 sourceFiles[key] = sf; 178 } 179 180 void addFile(string s, SourceFile.Action action) { 181 auto sp = NativePath(s); 182 assert(sp.absolute, format("Source path in %s expected to be absolute: %s", packname, s)); 183 //if( !sp.absolute ) sp = pack.path ~ sp; 184 addSourceFile(sp.relativeTo(settings.toolWorkingDirectory ~ basepath), determineStructurePath(sp, targets[packname]), action); 185 } 186 187 foreach (p; targets[packname].packages) 188 if (!p.recipePath.empty) 189 addFile(p.recipePath.toNativeString(), SourceFile.Action.none); 190 191 if (files.targetType == TargetType.staticLibrary) 192 foreach(s; files.sourceFiles.filter!(s => !isLinkerFile(settings.platform, s))) addFile(s, SourceFile.Action.build); 193 else 194 foreach(s; files.sourceFiles.filter!(s => !s.endsWith(".lib"))) addFile(s, SourceFile.Action.build); 195 196 foreach(s; files.importFiles) addFile(s, SourceFile.Action.none); 197 foreach(s; files.stringImportFiles) addFile(s, SourceFile.Action.none); 198 findFilesMatchingGlobs(root_package_path, files.copyFiles, s => addFile(s, SourceFile.Action.copy)); 199 findFilesMatchingGlobs(root_package_path, files.extraDependencyFiles, s => addFile(s, SourceFile.Action.none)); 200 201 // Create folders and files 202 ret.formattedWrite(" <Folder name=\"%s\">", getPackageFileName(packname)); 203 typeof(NativePath.init.head)[] lastFolder; 204 foreach(source; sortedSources(sourceFiles.values)) { 205 logDebug("source looking at %s", source.structurePath); 206 auto cur = source.structurePath.parentPath.bySegment.array; 207 if(lastFolder != cur) { 208 size_t same = 0; 209 foreach(idx; 0..min(lastFolder.length, cur.length)) 210 if(lastFolder[idx] != cur[idx]) break; 211 else same = idx+1; 212 213 const decrease = lastFolder.length - min(lastFolder.length, same); 214 const increase = cur.length - min(cur.length, same); 215 216 foreach(unused; 0..decrease) 217 ret.put("\n </Folder>"); 218 foreach(idx; 0..increase) 219 ret.formattedWrite("\n <Folder name=\"%s\">", cur[same + idx].name); 220 lastFolder = cur; 221 } 222 final switch (source.action) with (SourceFile.Action) 223 { 224 case none: 225 ret.formattedWrite("\n <File path=\"%s\" tool=\"None\" />", source.filePath.toNativeString()); 226 break; 227 case build: 228 ret.formattedWrite("\n <File path=\"%s\" />", source.filePath.toNativeString()); 229 break; 230 case copy: 231 ret.formattedWrite("\n <File customcmd=\"copy /Y $(InputPath) $(TargetDir)\" path=\"%s\" tool=\"Custom\" />", source.filePath.toNativeString()); 232 break; 233 } 234 } 235 // Finalize all open folders 236 foreach(unused; 0..lastFolder.length) 237 ret.put("\n </Folder>"); 238 ret.put("\n </Folder>\n</DProject>"); 239 240 logDebug("About to write to '%s.visualdproj' file %s bytes", getPackageFileName(packname), ret.data.length); 241 projFileName(packname).writeFile(ret.data); 242 } 243 244 void generateProjectConfiguration(Appender!(char[]) ret, string pack, string type, GeneratorSettings settings, in TargetInfo[string] targets) 245 { 246 auto cwd = settings.toolWorkingDirectory; 247 auto buildsettings = targets[pack].buildSettings.dup; 248 auto basepath = NativePath(".dub/"); 249 250 string[] getSettings(string setting)(){ return __traits(getMember, buildsettings, setting); } 251 string[] getPathSettings(string setting)() 252 { 253 auto settings = getSettings!setting(); 254 auto ret = new string[settings.length]; 255 foreach (i; 0 .. settings.length) { 256 // \" is interpreted as an escaped " by cmd.exe, so we need to avoid that 257 auto p = NativePath(settings[i]).relativeTo(cwd ~ basepath); 258 p.endsWithSlash = false; 259 ret[i] = '"' ~ p.toNativeString() ~ '"'; 260 } 261 return ret; 262 } 263 264 if (buildsettings.targetType == TargetType.none) 265 return; 266 267 foreach(architecture; settings.platform.architecture) { 268 auto arch = architecture.vsArchitecture; 269 ret.formattedWrite(" <Config name=\"%s\" platform=\"%s\">\n", to!string(type), arch); 270 271 // debug and optimize setting 272 ret.formattedWrite(" <symdebug>%s</symdebug>\n", buildsettings.options & BuildOption.debugInfo ? "1" : "0"); 273 ret.formattedWrite(" <optimize>%s</optimize>\n", buildsettings.options & BuildOption.optimize ? "1" : "0"); 274 ret.formattedWrite(" <useInline>%s</useInline>\n", buildsettings.options & BuildOption.inline ? "1" : "0"); 275 ret.formattedWrite(" <release>%s</release>\n", buildsettings.options & BuildOption.releaseMode ? "1" : "0"); 276 277 // Lib or exe? 278 enum 279 { 280 Executable = 0, 281 StaticLib = 1, 282 DynamicLib = 2 283 } 284 285 int output_type = StaticLib; // library 286 string output_ext = "lib"; 287 if (buildsettings.targetType == TargetType.executable) 288 { 289 output_type = Executable; 290 output_ext = "exe"; 291 } 292 else if (buildsettings.targetType == TargetType.dynamicLibrary) 293 { 294 output_type = DynamicLib; 295 output_ext = "dll"; 296 } 297 auto bin_path = pack == m_project.rootPackage.name ? NativePath(buildsettings.targetPath) : NativePath("lib/"); 298 bin_path.endsWithSlash = true; 299 ret.formattedWrite(" <lib>%s</lib>\n", output_type); 300 ret.formattedWrite(" <exefile>%s%s.%s</exefile>\n", bin_path.toNativeString(), buildsettings.targetName, output_ext); 301 302 // include paths and string imports 303 string imports = join(getPathSettings!"importPaths"(), " "); 304 string cimports = join(getPathSettings!"cImportPaths"(), " "); 305 string stringImports = join(getPathSettings!"stringImportPaths"(), " "); 306 string combinedImports = join([imports, cimports], " "); 307 ret.formattedWrite(" <imppath>%s</imppath>\n", combinedImports); 308 ret.formattedWrite(" <fileImppath>%s</fileImppath>\n", stringImports); 309 310 ret.formattedWrite(" <program>%s</program>\n", "$(DMDInstallDir)windows\\bin\\dmd.exe"); // FIXME: use the actually selected compiler! 311 ret.formattedWrite(" <additionalOptions>%s</additionalOptions>\n", getSettings!"dflags"().join(" ")); 312 313 // Add version identifiers 314 string versions = join(getSettings!"versions"(), " "); 315 ret.formattedWrite(" <versionids>%s</versionids>\n", versions); 316 317 // Add libraries, system libs need to be suffixed by ".lib". 318 string linkLibs = join(map!(a => a~".lib")(getSettings!"libs"()), " "); 319 string addLinkFiles = join(getSettings!"sourceFiles"().filter!(s => s.endsWith(".lib"))(), " "); 320 if (arch == "x86") addLinkFiles ~= " phobos.lib"; 321 if (output_type != StaticLib) ret.formattedWrite(" <libfiles>%s %s</libfiles>\n", linkLibs, addLinkFiles); 322 323 // Unittests 324 ret.formattedWrite(" <useUnitTests>%s</useUnitTests>\n", buildsettings.options & BuildOption.unittests ? "1" : "0"); 325 326 // Better C 327 ret.formattedWrite(" <betterC>%s</betterC>\n", buildsettings.options & BuildOption.betterC ? "1" : "0"); 328 329 // compute directory for intermediate files (need dummy/ because of how -op determines the resulting path) 330 size_t ndummy = 0; 331 foreach (f; buildsettings.sourceFiles) { 332 auto rpath = NativePath(f).relativeTo(cwd ~ basepath); 333 size_t nd = 0; 334 foreach (s; rpath.bySegment) 335 if (s == "..") 336 nd++; 337 if (nd > ndummy) ndummy = nd; 338 } 339 string intersubdir = replicate("dummy/", ndummy) ~ getPackageFileName(pack); 340 341 ret.put(" <obj>0</obj>\n"); 342 ret.put(" <link>0</link>\n"); 343 ret.put(" <subsystem>0</subsystem>\n"); 344 ret.put(" <multiobj>0</multiobj>\n"); 345 int singlefilemode; 346 final switch (settings.buildMode) with (BuildMode) { 347 case separate: singlefilemode = 2; break; 348 case allAtOnce: singlefilemode = 0; break; 349 case singleFile: singlefilemode = 1; break; 350 //case compileOnly: singlefilemode = 3; break; 351 } 352 ret.formattedWrite(" <singleFileCompilation>%s</singleFileCompilation>\n", singlefilemode); 353 ret.formattedWrite(" <mscoff>%s</mscoff>", buildsettings.dflags.canFind("-m32mscoff") ? "1" : "0"); 354 ret.put(" <oneobj>0</oneobj>\n"); 355 ret.put(" <trace>0</trace>\n"); 356 ret.put(" <quiet>0</quiet>\n"); 357 ret.formattedWrite(" <verbose>%s</verbose>\n", buildsettings.options & BuildOption.verbose ? "1" : "0"); 358 ret.put(" <vtls>0</vtls>\n"); 359 ret.put(" <cpu>0</cpu>\n"); 360 ret.formattedWrite(" <isX86_64>%s</isX86_64>\n", arch == "x64" ? 1 : 0); 361 ret.put(" <isLinux>0</isLinux>\n"); 362 ret.put(" <isOSX>0</isOSX>\n"); 363 ret.put(" <isWindows>0</isWindows>\n"); 364 ret.put(" <isFreeBSD>0</isFreeBSD>\n"); 365 ret.put(" <isSolaris>0</isSolaris>\n"); 366 ret.put(" <isDragonFlyBSD>0</isDragonFlyBSD>\n"); 367 ret.put(" <scheduler>0</scheduler>\n"); 368 ret.put(" <useDeprecated>0</useDeprecated>\n"); 369 ret.put(" <useAssert>0</useAssert>\n"); 370 ret.put(" <useInvariants>0</useInvariants>\n"); 371 ret.put(" <useIn>0</useIn>\n"); 372 ret.put(" <useOut>0</useOut>\n"); 373 ret.put(" <useArrayBounds>0</useArrayBounds>\n"); 374 ret.formattedWrite(" <noboundscheck>%s</noboundscheck>\n", buildsettings.options & BuildOption.noBoundsCheck ? "1" : "0"); 375 ret.put(" <useSwitchError>0</useSwitchError>\n"); 376 ret.put(" <preservePaths>1</preservePaths>\n"); 377 ret.formattedWrite(" <warnings>%s</warnings>\n", buildsettings.options & BuildOption.warningsAsErrors ? "1" : "0"); 378 ret.formattedWrite(" <infowarnings>%s</infowarnings>\n", buildsettings.options & BuildOption.warnings ? "1" : "0"); 379 ret.formattedWrite(" <checkProperty>%s</checkProperty>\n", buildsettings.options & BuildOption.property ? "1" : "0"); 380 ret.formattedWrite(" <genStackFrame>%s</genStackFrame>\n", buildsettings.options & BuildOption.alwaysStackFrame ? "1" : "0"); 381 ret.put(" <pic>0</pic>\n"); 382 ret.formattedWrite(" <cov>%s</cov>\n", buildsettings.options & BuildOption.coverage ? "1" : "0"); 383 ret.put(" <nofloat>0</nofloat>\n"); 384 ret.put(" <Dversion>2</Dversion>\n"); 385 ret.formattedWrite(" <ignoreUnsupportedPragmas>%s</ignoreUnsupportedPragmas>\n", buildsettings.options & BuildOption.ignoreUnknownPragmas ? "1" : "0"); 386 ret.formattedWrite(" <compiler>%s</compiler>\n", settings.compiler.name == "ldc" ? 2 : settings.compiler.name == "gdc" ? 1 : 0); 387 ret.formattedWrite(" <otherDMD>0</otherDMD>\n"); 388 ret.formattedWrite(" <outdir>%s</outdir>\n", bin_path.toNativeString()); 389 ret.formattedWrite(" <objdir>obj/%s/%s</objdir>\n", to!string(type), intersubdir); 390 ret.put(" <objname />\n"); 391 ret.put(" <libname />\n"); 392 ret.put(" <doDocComments>0</doDocComments>\n"); 393 ret.put(" <docdir />\n"); 394 ret.put(" <docname />\n"); 395 ret.put(" <modules_ddoc />\n"); 396 ret.put(" <ddocfiles />\n"); 397 ret.put(" <doHdrGeneration>0</doHdrGeneration>\n"); 398 ret.put(" <hdrdir />\n"); 399 ret.put(" <hdrname />\n"); 400 ret.put(" <doXGeneration>1</doXGeneration>\n"); 401 ret.put(" <xfilename>$(IntDir)\\$(TargetName).json</xfilename>\n"); 402 ret.put(" <debuglevel>0</debuglevel>\n"); 403 ret.put(" <versionlevel>0</versionlevel>\n"); 404 ret.put(" <debugids />\n"); 405 ret.put(" <dump_source>0</dump_source>\n"); 406 ret.put(" <mapverbosity>0</mapverbosity>\n"); 407 ret.put(" <createImplib>0</createImplib>\n"); 408 ret.put(" <defaultlibname />\n"); 409 ret.put(" <debuglibname />\n"); 410 ret.put(" <moduleDepsFile />\n"); 411 ret.put(" <run>0</run>\n"); 412 ret.put(" <runargs />\n"); 413 ret.put(" <runCv2pdb>1</runCv2pdb>\n"); 414 ret.put(" <pathCv2pdb>$(VisualDInstallDir)cv2pdb\\cv2pdb.exe</pathCv2pdb>\n"); 415 ret.put(" <cv2pdbPre2043>0</cv2pdbPre2043>\n"); 416 ret.put(" <cv2pdbNoDemangle>0</cv2pdbNoDemangle>\n"); 417 ret.put(" <cv2pdbEnumType>0</cv2pdbEnumType>\n"); 418 ret.put(" <cv2pdbOptions />\n"); 419 ret.put(" <objfiles />\n"); 420 ret.put(" <linkswitches />\n"); 421 ret.put(" <libpaths />\n"); 422 ret.put(" <deffile />\n"); 423 ret.put(" <resfile />\n"); 424 auto wdir = NativePath(buildsettings.workingDirectory); 425 if (!wdir.absolute) wdir = m_project.rootPackage.path ~ wdir; 426 ret.formattedWrite(" <debugworkingdir>%s</debugworkingdir>\n", 427 wdir.relativeTo(cwd ~ basepath).toNativeString()); 428 ret.put(" <preBuildCommand />\n"); 429 ret.put(" <postBuildCommand />\n"); 430 ret.put(" <filesToClean>*.obj;*.cmd;*.build;*.dep</filesToClean>\n"); 431 ret.put(" </Config>\n"); 432 } // foreach(architecture) 433 } 434 435 void performOnDependencies(const Package main, string[string] configs, void delegate(const Package pack) op) 436 { 437 foreach (p; m_project.getTopologicalPackageList(false, main, configs)) { 438 if (p is main) continue; 439 op(p); 440 } 441 } 442 443 string generateUUID() const { 444 import std.string; 445 return "{" ~ toUpper(randomUUID().toString()) ~ "}"; 446 } 447 448 string guid(string projectName) { 449 if(projectName !in m_projectUuids) 450 m_projectUuids[projectName] = generateUUID(); 451 return m_projectUuids[projectName]; 452 } 453 454 auto solutionFileName() const { 455 version(DUBBING) return getPackageFileName(m_project.rootPackage) ~ ".dubbed.sln"; 456 else return getPackageFileName(m_project.rootPackage.name) ~ ".sln"; 457 } 458 459 NativePath projFileName(string pack) const { 460 auto basepath = NativePath(".dub/"); 461 version(DUBBING) return basepath ~ (getPackageFileName(pack) ~ ".dubbed.visualdproj"); 462 else return basepath ~ (getPackageFileName(pack) ~ ".visualdproj"); 463 } 464 } 465 466 // TODO: nice folders 467 private struct SourceFile { 468 NativePath structurePath; 469 NativePath filePath; 470 enum Action { none, build, copy }; 471 Action action = Action.none; 472 473 size_t toHash() const nothrow @trusted { return structurePath.toHash() ^ filePath.toHash() ^ (action * 0x1f3e7b2c); } 474 int opCmp(ref const SourceFile rhs) const { return sortOrder(this, rhs); } 475 // "a < b" for folder structures (deepest folder first, else lexical) 476 private final static int sortOrder(ref const SourceFile a, ref const SourceFile b) { 477 assert(!a.structurePath.empty); 478 assert(!b.structurePath.empty); 479 static if (is(typeof(a.structurePath.nodes))) { // vibe.d < 0.8.2 480 auto as = a.structurePath.nodes; 481 auto bs = b.structurePath.nodes; 482 } else { 483 auto as = a.structurePath.bySegment.array; 484 auto bs = b.structurePath.bySegment.array; 485 } 486 487 // Check for different folders, compare folders only (omit last one). 488 for(uint idx=0; idx<min(as.length-1, bs.length-1); ++idx) 489 if(as[idx] != bs[idx]) 490 return as[idx].name.cmp(bs[idx].name); 491 492 if(as.length != bs.length) { 493 // If length differ, the longer one is "smaller", that is more 494 // specialized and will be put out first. 495 return as.length > bs.length? -1 : 1; 496 } 497 else { 498 // Both paths indicate files in the same directory, use lexical 499 // ordering for those. 500 return as[$-1].name.cmp(bs[$-1].name); 501 } 502 } 503 } 504 505 private auto sortedSources(SourceFile[] sources) { 506 return sort(sources); 507 } 508 509 unittest { 510 SourceFile[] sfs = [ 511 { NativePath("b/file.d"), NativePath("") }, 512 { NativePath("b/b/fileA.d"), NativePath("") }, 513 { NativePath("a/file.d"), NativePath("") }, 514 { NativePath("b/b/fileB.d"), NativePath("") }, 515 { NativePath("b/b/b/fileA.d"), NativePath("") }, 516 { NativePath("b/c/fileA.d"), NativePath("") }, 517 ]; 518 auto sorted = sort(sfs); 519 SourceFile[] sortedSfs; 520 foreach(sr; sorted) 521 sortedSfs ~= sr; 522 assert(sortedSfs[0].structurePath == NativePath("a/file.d"), "1"); 523 assert(sortedSfs[1].structurePath == NativePath("b/b/b/fileA.d"), "2"); 524 assert(sortedSfs[2].structurePath == NativePath("b/b/fileA.d"), "3"); 525 assert(sortedSfs[3].structurePath == NativePath("b/b/fileB.d"), "4"); 526 assert(sortedSfs[4].structurePath == NativePath("b/c/fileA.d"), "5"); 527 assert(sortedSfs[5].structurePath == NativePath("b/file.d"), "6"); 528 } 529 } 530 531 private NativePath determineStructurePath(NativePath file_path, in ProjectGenerator.TargetInfo target) 532 { 533 foreach (p; target.packages) { 534 if (file_path.startsWith(p.path)) 535 return NativePath(getPackageFileName(p.name)) ~ file_path.relativeTo(p.path); 536 } 537 return NativePath("misc/") ~ file_path.head; 538 } 539 540 private string getPackageFileName(string pack) 541 { 542 return pack.replace(":", "_"); 543 } 544 545 private @property string vsArchitecture(string architecture) 546 { 547 switch(architecture) { 548 default: logWarn("Unsupported platform('%s'), defaulting to x86", architecture); goto case; 549 case "x86", "x86_mscoff": return "Win32"; 550 case "x86_64": return "x64"; 551 } 552 }