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